Image Processing Live Script PDF
Image Processing Live Script PDF
This introduction to MATLAB image processing illustrates how to import, display, export, crop, access and
transform images, and how to find circles and edges in images. Many words in this Live Script are clickable.
Try clicking images.
Author: D. Carlsmith
imformats
The default (no arguments) imformats command lists the names of the MATLAB functions used to read and
write each kind of image file. We can view any function using type. This is useful for drilling into MATLAB
supplied functions to see exactly what they do.
type readbmp
1
% See also IMREAD, IMWRITE, IMFINFO.
info = imbmpinfo(filename);
map = info.Colormap;
X = readbmpdata(info);
return;
The function readbmp calls imbmpinfo to extract file information and the colormap and then a function that
appears to read the actual data. Drill in by type-ing that function.
type readbmpdata
function X = readbmpdata(info)
%READBMPDATA Read bitmap data
% X = readbmpdata(INFO) reads image data from a BMP file. INFO is a
% structure returned by IMBMPINFO. X is a uint8 array that is 2-D for
% 1-bit, 4-bit, and 8-bit image data. X is M-by-N-by-3 for 24-bit and
% 32-bit image data.
fid = info.FileID;
offset = info.ImageDataOffset;
width = info.Width;
height = info.Height;
status = fseek(fid,offset,'bof');
if status==-1
error(message('Spcuilib:scopes:ErrorInvalidDataOffset'));
end
switch info.CompressionType
case 'none'
switch info.BitDepth
case 1
X = logical(bmpReadData1(fid, width, height));
case 4
X = bmpReadData4(fid, width, height);
case 8
X = bmpReadData8(fid, width, height);
case 16
X = bmpReadData16(fid, width, height);
case 24
X = bmpReadData24(fid, width, height);
case 32
X = bmpReadData32(fid, width, height);
end
2
X = bmpReadData8RLE(fid, width, height);
case 'bitfields'
error(message('Spcuilib:scopes:ErrorBitfieldCompressionUnsupported'));
end
%%%
%%% bmpReadData8 --- read 8-bit bitmap data
%%%
function X = bmpReadData8(fid, width, height)
% NOTE: BMP files are stored so that scanlines use a multiple of 4 bytes.
paddedWidth = 4*ceil(width/4);
X = fread(fid,paddedWidth*abs(height),'*uint8');
count = length(X);
if (count ~= paddedWidth*abs(height))
warning(message('Spcuilib:scopes:WarnInvalidBMP'));
% Fill in the missing values with zeros.
X(paddedWidth*abs(height)) = 0;
end
if height>=0
X = rot90(reshape(X, paddedWidth, height));
else
X =reshape(X, paddedWidth, abs(height))';
end
if (paddedWidth ~= width)
X = X(:,1:width);
end
%%%
%%% bmpReadData8RLE --- read 8-bit RLE-compressed bitmap data
%%%
function X = bmpReadData8RLE(fid, width, height)
% NOTE: BMP files are stored so that scanlines use a multiple of 4 bytes.
paddedWidth = 4*ceil(width/4);
inBuffer = fread(fid,'*uint8');
if height>=0
X = rot90(X);
else
X = X';
end
if (paddedWidth ~= width)
X = X(:,1:width);
end
3
%%%
%%% bmpReadData4 --- read 4-bit bitmap data
%%%
function X = bmpReadData4(fid, width, height)
% NOTE: BMP files are stored so that scanlines use a multiple of 4 bytes.
paddedWidth = 8*ceil(width/8);
numBytes = paddedWidth * abs(height) / 2; % evenly divides because of padding
XX = fread(fid,numBytes,'*uint8');
count = length(XX);
if (count ~= numBytes)
warning(message('Spcuilib:scopes:WarnInvalidBMP'));
% Fill in the missing values with zeros.
X(numBytes) = 0;
end
XX = reshape(XX, paddedWidth / 2, abs(height));
if height>=0
X = rot90(X);
else
X = X';
end
if (paddedWidth ~= width)
X = X(:,1:width);
end
%%%
%%% bmpReadData4RLE --- read 4-bit RLE-compressed bitmap data
%%%
function X = bmpReadData4RLE(fid, width, height)
% NOTE: BMP files are stored so that scanlines use a multiple of 4 bytes.
paddedWidth = 8*ceil(width/8);
%numBytes = paddedWidth * abs(height) / 2; % evenly divides because of padding
inBuffer = fread(fid,'*uint8');
if height>=0
X = rot90(bmpdrle(inBuffer, paddedWidth, abs(height), 'rle4'));
else
X = bmpdrle(inBuffer, paddedWidth, abs(height), 'rle4')';
end
if (paddedWidth ~= width)
X = X(:,1:width);
end
%%%
%%% bmpReadData1 --- read 1-bit bitmap data
%%%
function X = bmpReadData1(fid, width, height)
% NOTE: BMP files are stored so that scanlines use a multiple of 4 bytes.
paddedWidth = 32*ceil(width/32);
numPixels = paddedWidth * abs(height); % evenly divides because of padding
4
[X, count] = fread(fid,paddedWidth*abs(height),'*ubit1');
if (count ~= numPixels)
if height>=0
X = rot90(X);
else
X = X';
end
if (paddedWidth ~= width)
X = X(:,1:width);
end
%%%
%%% bmpReadData16 --- read 16-bit grayscale data
%%%
function X = bmpReadData16(fid, width, height)
% NOTE: BMP files are stored so that scanlines use a multiple of 4 bytes.
paddedWidth = 4*ceil(width/4);
X = fread(fid,paddedWidth*abs(height),'*uint16');
count = length(X);
if (count ~= paddedWidth*abs(height))
warning(message('Spcuilib:scopes:WarnInvalidBMP'));
% Fill in the missing values with zeros.
X(paddedWidth*abs(height)) = 0;
end
if height>=0
X = rot90(reshape(X, paddedWidth, height));
else
X = reshape(X, paddedWidth, abs(height))';
end
if (paddedWidth ~= width)
X = X(:,1:width);
end
%%%
%%% bmpReadData24 --- read 24-bit bitmap data
%%%
function RGB = bmpReadData24(fid, width, height)
% NOTE: BMP files are stored so that scanlines use a multiple of 4 bytes.
byteWidth = 3*width;
paddedByteWidth = 4*ceil(byteWidth/4);
numBytes = paddedByteWidth * abs(height);
X = fread(fid,numBytes,'*uint8');
count = length(X);
if (count ~= numBytes)
warning(message('Spcuilib:scopes:WarnInvalidBMP'));
% Fill in the missing values with zeros.
X(numBytes) = 0;
5
end
if height>=0
X = rot90(reshape(X, paddedByteWidth, abs(height)));
else
X = reshape(X, paddedByteWidth, abs(height))';
end
if (paddedByteWidth ~= byteWidth)
X = X(:,1:byteWidth);
end
%%%
%%% bmpReadData32 --- read 32-bit bitmap data
%%%
function RGB = bmpReadData32(fid, width, height)
% NOTE: BMP files are stored so that scanlines use a multiple of 4 bytes.
byteWidth = 4*width;
paddedByteWidth = 4*ceil(byteWidth/4);
numBytes = paddedByteWidth * abs(height);
X = fread(fid,numBytes,'*uint8');
count = length(X);
if (count ~= numBytes)
warning(message('Spcuilib:scopes:WarnInvalidBMP'));
% Fill in the missing values with zeros.
X(numBytes) = 0;
end
if height>=0
X = rot90(reshape(X, paddedByteWidth, abs(height)));
else
X = reshape(X, paddedByteWidth, abs(height))';
end
if (paddedByteWidth ~= byteWidth)
X = X(:,1:byteWidth);
end
We are down in the weeds. Any computer file is essentially a list of (possibly not physicially contiguous)
words, the size of which is defined by the physical architecture of the computer. A 64-bit word for example
contains eight 8-bit bytes. A pixel value, say some red pixel value created generated by the sensor readout
chip, is conventionally subject to so-called gamma correction, a nonlinear transformation to an 8-bit value.
Eight of these can fit into one 64-bit word. Different formats pack the pixel values in different ways into the
bytes in the words of a file. One requires different codes to unpack the different formats in any general
graphics application. MATLAB core libraries provide such functions for common formats. Thanks MATLAB.
If you have to work with some uncommon format used in some science application, for example data from
a huge array of sensors at the focal point of a new telescope in space, you will have to hunt up or write your
own unpacking algorithm, or find code to convert to some standard format such as FITS used in the industry.
6
Open an image file from the internet using imread. We first create a character vector to hold the filename.
If we use a URL, imread will recognize that. Else imread will attempt to find a file with that name in your
local path.
Try this: Use your own URL to play with a different image.
fln= 'https://live.staticflickr.com/5055/5447218318_a1ce473203_o_d.jpg';
RGB=imread(fln);
Try this: If you hover you cursor over a variable, MATLAB will display information about the variable. Hover
over the variable fln above.
MATLAB imread used the file extension to invoke the appropriate image unpacking functions and filled and
returned an array which we caught and named RGB. Thanks MATLAB!
Tip: This next line is commented out to suppress inclusion of a large file in Live Script which can slow it
down. If Live Script slows down, try execting it from the command line not the Live Script Editor. If executed
from the comand line, you can terminate execution with a control_C. Also, you can clear all Live Script output
under the view menu to clean up a situation.
% imshow(RGB)
flninfo=imfinfo(fln)
7
DigitalCamera: [1×1 struct]
GPSInfo: [1×1 struct]
ExifThumbnail: [1×1 struct]
Most image files include provenance information using standards like EXIF. We discovered this
image, lifted from the internet, was taken with a Nikon D5000 and processed with Photoshop. The
function imfinfo returned a MATLAB structure array that itself contains structure arrays for DigitalCamera
and GPS information and a Thumbnail (tiny preview).
Try this: You can drill into a structure by double clicking its name in the workspace pane.
flninfo.DigitalCamera
8
whos RGB
imwrite(RGB,'balls.jpg','jpg')
RGBsize=size(RGB)
RGBsize = 1×3
2848 4288 3
Crop an image
Create indices corresponding to horizontal and vertical middle fraction of image.
Try this: Customize the horizontal column (h1, h2) and vertical row (v1,v2) ranges to create your own
cropped image.
n=4;h1=int16(RGBsize(1)*(1/2-1/(2*n)));h2=int16(RGBsize(1)*(1/2+1/(2*n)));
v1=int16(RGBsize(2)*(1/2-1/(2*n)));v2=int16(RGBsize(2)*(1/2+1/(2*n)));
Create smaller cropped image, sufficient to illustrate image processing, simply by addressing ranges of pixels
using the colon operator.
RGBcropped=RGB(h1:h2,v1:v2,:);
imshow(RGBcropped)
9
Access and transform color
Adjust the contrast of the cropped image, specifying contrast limits to imadjust.
This operation is a nonlinear scaling of values for each channel using a conventional gamma correction of
the form developed to compress image sensor values into 8 bits. Different sensors have different
relative sensitivites in sampling light as RGB values. To approximately reproduce color correctly, different
display technologies need to convert digital image values to yet different technology-dependent RGB
values to drive light emitting elements. Several standards are recognized to facilitate the optimal transfer of
information.
gamma=[2.,2.,2.];
RGB2 = imadjust(RGBcropped,[.2 .2 .2; 1 1 1],[],gamma);
figure
imshow(RGB2)
10
Show montage of three different color channels.
R=RGBcropped(:,:,1);G=RGBcropped(:,:,2);B=RGBcropped(:,:,3);
montage({R,G,B,RGBcropped})
title('Montage showing three color channels in color image')
Histogram the values of the R, G, and B channels. We choose a different display color for each channel. You
may notice the display color for R might not look exactly 'red' to you on your display. Maybe your display
colors are not correctly set.
11
histogram(R,'facecolor',[1 0 0]); hold on;
histogram(B,'facecolor',[0 1 0]);
histogram(G,'facecolor',[0 0 1]);
title('Histogram of R, G, and B values in color image')
xlabel('Channel value (dimensionless)')
ylabel('Number of pixels in channel value bin')
hold off
threshold=50;
darkmask=RGBcropped(:,:,1)<threshold&RGBcropped(:,:,2)<threshold...
&RGBcropped(:,:,3)<threshold;
dark=ones(size(RGBcropped(:,:,1) ));
dark(darkmask)=0;
figure
imshow(dark)
title('Regions of image with values below threshold')
12
Color space transformations
Convert the RGB image to the HSV colorspace by using the rgb2hsv function. H (hue) starts at pure R
and increases with R dominant towards G then with G dominant towards B and ends at pure B. Assume
RGB are normalized to 1. S (saturation) is S=1-MIN(RGB)/MAX(RGB) so R=G=1/2, B=0 has maximum S.
V(value)=MAX(RGB) of the dominate RGB color. The transformation does not distinguish between raw RGB
and intensity compressed (gamma-corrected) RGB values.
See https://en.wikipedia.org/wiki/HSL_and_HSV .
hsvImage = rgb2hsv(RGBcropped);
Split the HSV image into its component hue, saturation, and value channels with imsplit.
[h,s,v] = imsplit(hsvImage);
montage({h,s,v,hsvImage})
title('Montage of HSV values in an HSV color image')
13
Segment image using findcircles.
The MATLAB function imfindcircles requires values for several search parameters including a range of
radii. In using this function on a particular image, you will need to choose appropriate values so the function
neither converts every speck of pixels into a circle nor searches only for circles larger than any in the image.
The following invocation of imfindcircles uses several arguments. The first is the image array itself, the
secnd a range of radii, the rest invoked by name. To understand a function call like this, you must study the
documentation for the function. Search for it using help or use and internet search.
MATLAB supports functions with a variable number of arguments and a variable number of returned
quantities. The documentation of each function shows you the various ways the function can be used,
starting with some minimal form. We will use a certain choice of parameters and catch the locations of the
centers of the circles found, the corresponding radii, and corresponding values describing the quality of each
found circle. The balls in theimage are all nearly at the same distance so we can use a narrow range of radii.
Try this: Vary the parameter values and methods used in circle finding to see if you can find more circles.
In naive use, circle finding proceeds by converting the image to grayscale (total intensity), ignoring color.
Different algorithms might first segment by color, but looking closely you will see that light reflected from one
ball onto its neighbors make that approach less than straight forward.
X=RGBcropped;
%X=hsvImage;
[centers,radii,metric] = imfindcircles(X,[75 77],'ObjectPolarity',...
'bright','Sensitivity',1,'EdgeThreshold',0.4,'Method','TwoStage');
nbins=20;
14
histogram(radii, nbins)
title('Distribution of circle radius in image')
xlabel('Radius (pixels)'); ylabel('Number of circles in radius bin')
histogram(metric,nbins);
title('Distribution of circle metric values')
xlabel('Metric value (dimensionless)')
ylabel('Number of circles i metric bin')
15
Circles are returned in decreasing order of quality and value of quality metric. Select circles with large metric
values using a logical mask.
range=1:length(radii(metric>0.03));
The preceding nested functions construct is typical MATLAB. To understand it, start with the innermost
function call metric>0.03. Firstly, < is an elementary function called an operator function that,
like + and -, has always two arguments/inputs. Rather than write c=Plus(a,b), we can write c=a
+b with + understood to operate on its immediate left and immediate right arguments. Similarly, < is an
elementary function. Now try
>>test=metric>0.03
at the command line, a function call equivalent to test = gt(metric,0.03) which hasthe
form output=function(input arguments). The returned output that we have caught as test is a logical
array of the same dimesion as metric and its elements are logical true or false upon if the corresponding
elements of the array metric satisfies the condition. The vector radii has the same size as metric. An
expression of the form radii(select) when select is a collection of indices returns a reduced size array
containing the elements of radii with the indices specified in select. For example radii(1:5) returns the
first 5 elements. When select is a logical array, radii(select) returns the elements of radii corresponding
to those indices for which select is logical true. So selectradii=radii(metric>0.03) is a select array of
radii satisfying the selection criterion. Now length(selectradii) is the length of that select array and
range=1:length(radii(metric>0.03));
Now imfindcircles returns center locations, radii, and metric sorted by metric in decreasing order so
the following statements produce reduced lists of these quantities containing values for which the metric is
greater than 0.03.
centersStrong = centers(range,:);
radiiStrong = radii(range);
metricStrong = metric(range);
Here the colon stands for all values of the 2nd index in centers.
There is simpler way to do this without relying on sorting by imfindcircles, namely to address the arrays
using the logical condition array itself. See Find Array Elements that Meet a Condition.
mask= metric>0.03;
radiiStrong2=radii(mask);
isequal(radiiStrong, radiiStrong2)
ans = logical
1
16
Superpose the highest metric circle perimeters atop the original image. One observes the metric favors
nonoccluded balls.
imshow(X);
viscircles(centersStrong, radiiStrong,'EdgeColor','b');
title('Highest metric circles')
BW=rgb2gray(RGBcropped);
imshow(BW)
title('Grayscale version of image')
17
Find edges in the gray scale image with the Canny algorithm which uses a derivative of a Gaussian filter to
find edges with two thresholds. See edge. Other algorithms are available.
[imgCanny,threshOut] = edge(BW,'Canny',0.12019,1.23);
imshow(imgCanny)
title('Edges in image')
18
'Sensitivity', 0.9870, ...
'EdgeThreshold', 0.09, ...
'Method', 'TwoStage', ...
'ObjectPolarity', 'Bright');
histogram(radii)
title('Distribution of circle radii amongst edges')
xlabel('Radius (pixels')
ylabel('Number of circles in radius bin')
histogram(metric)
19
Visualize the strongest circles found in the canny edge data.
imshow(imgCanny);
range=1:length(radii(metric>0.03));
centersStrong = centers(range,:);
radiiStrong = radii(range);
metricStrong= metric(range);
viscircles(centersStrong, radiiStrong,'EdgeColor','b');
title('Circles found amongst edges in image')
20