MATLAB: Smooth curves on tilted edges - Edge Detection (Polyfit or fitPolynomialRANSAC?) - matlab

i have images with bands. The bands are not always 100% straight and not always 100% horizontal. They can be tilted and bent. Each band has a constant width.
I need the width of each band on the image and show the edges of each band as smooth as possible:
Example_edge_detection.png Example_straight.png Example_tilted.png
I have already a solution for 100% straight-horizontal bands. But i dont know how to do it with tilted bands. Maybe Polyfit or houghlines are needed? Please help me. Thank You!
Here the working code for the straight-horizontal bands!
clc;
close all;
clear;
workspace;
format long g;
format compact;
fontSize = 20;
folder = pwd;
baseFileName = 'Example_straight.png';
grayImage = imread(baseFileName);
[rows, columns, numberOfColorChannels] = size(grayImage);
if numberOfColorChannels > 1
grayImage = min(grayImage, [], 3);
end
hFig = gcf;
hFig.WindowState = 'maximized';
grayImage = adapthisteq(grayImage);
verticalProfile = mean(grayImage, 2);
threshold = 118;
binaryImage = imfill(grayImage < threshold, 'holes');
% Take the 3 largest blobs
binaryImage = bwareafilt(binaryImage, 3);
% Snip off small tendrils using imopen()
binaryImage = imopen(binaryImage, true(1, 3));
% Take the 3 largest blobs
binaryImage = bwareafilt(binaryImage, 3);
subplot(1, 1, 1);
imshow(baseFileName);
hFig = gcf;
hFig.WindowState = 'maximized'; %
axis('on', 'image');
title('Edge Detection', 'FontSize', fontSize, 'Interpreter', 'None');
binaryProfile = (verticalProfile > threshold)';
bandStarts = strfind(binaryProfile, [0, 1]);
bandStops = strfind(binaryProfile, [1, 0]);
for k = 1 : length(bandStarts)
yline(bandStarts(k), 'Color', 'r', 'LineWidth', 1);
yline(bandStops(k), 'Color', 'r', 'LineWidth', 1);
end

It looks like your algorithm currently just does a simple threshold to find horizontal lines, where the threshold is applied to the difference in the mean horizontal pixel value between two adjacent rows. If that's good enough to get the job done, that's good enough for me.
As for non-horizontal lines, maybe you can try a routine like this to simply straighten out the image first.

Related

How can I detect the defect in this image of a semiconductor?

So I have this image of a semiconductor wafer and it has a defect in it which I need to detect using Matlab. I should detect only it and not its background. I need to also measure its perimeter and area.
So far I have this code where I convert the original image to a binary image and then use dilation on it and then try to get its contour. When getting the perimeter and the area, I receive the outcome not only for the defect but for the rest of the image which is not what I want. How can I extract the defect only so I can get only its area and parameter.
The image:
fig3 = imread('figure3.png');
imshow(fig3);
title('Original image', 'FontSize', 18);
%Gray image
fig3Gray = rgb2gray(fig3);
%Binary image
BW3 = imbinarize(fig3Gray,0.5);
imshow(BW3);
title('Binary image', 'FontSize', 18);
se3 = strel('square',5);
%Dilation image
dilated3 = imdilate(BW3,sr);
imshow(dilated3);
title('Dilated image', 'FontSize', 18);
minus3 = ~(BW3-dilated3);
imshow(minus3);
title('Contour image', 'FontSize', 18);
imshowpair(minus3,BW3,'montage');
%Perimeter and Area calculation
Coon3 = bwconncomp(~BW3)
ANS3 = length(Coon3.PixelIdxList{1});
OUTPUT3 = regionprops(Coon3,'Perimeter','Area');
P3 = OUTPUT3.Perimeter
Area3 = OUTPUT3.Area
Let's start by reading the image and converting it to binary. Note that I have lowered the threshold to eliminate unwanted details.
clear; close all; clc
fig3 = imread('XEQ59.png');
imshow(fig3);
title('Original image', 'FontSize', 18);
%Gray image
fig3Gray = rgb2gray(fig3);
%Binary image
BW3 = imbinarize(fig3Gray, 0.2); % lowered threshold
figure; imshow(BW3)
title('binary image')
Now we move on to find the coordinate of the defect. To do this, we define a structuring element that defines our desired shape se.
We are looking for the parts in the image that match with se. For a given coordinate to match, the surrounding area must exactly be se.
Note that gray values here are ignored, they can be either white or black.
se is manually defined where 1 represents white, -1 represents black, and 0 represents ignored pixels.
% hit-miss
se = [1, 1, -1*ones(1,5), ones(1, 3); ...
ones(6,1), -1*ones(6), zeros(6,2), ones(6,1); ...
ones(3,2), zeros(3,1), -1*ones(3,6), ones(3,1)];
figure; imshow(uint8(255/2*(se+1)), 'InitialMagnification', 3000)
title('structuring element')
Applying hit-miss operation to find the position of the defect:
pos = bwhitmiss(BW3, se);
figure; imshow(pos)
title('position of defect')
input('Press enter to continue...')
Now that we have the position, we grow that particular pixel position until it grows no more, in order to obtain defect.
% get the defect
close all; clc
def = pos;
last_def = zeros(size(def));
while ~isequal(def, last_def)
last_def = def;
def = ~BW3 & imdilate(def, ones(3));
imshow(def)
title('defect')
pause(0.1)
end
Calculating the area and the perimeter:
% area
area = sum(def(:))
% perimeter
vert = imdilate(def, [1; 1; 1]) - def;
horz = imdilate(def, [1 1 1]) - def;
perimeter = sum(vert(:)) + sum(horz(:))
area =
102
perimeter =
54
This question is much more difficult then your previous question.
Following solution uses an iterative approach (two iterations).
Includes heuristic about the difference from the cluster to it's neighbors.
Includes a heuristic that the cluster can't be too tall or too long.
Please read the comments:
clear
fig3 = imread('figure3.png');
fig3Gray = rgb2gray(fig3);
fig3Gray = im2double(fig3Gray); %Convert from uint8 to double (MATLAB math works in double).
%figure;imshow(fig3Gray);
%Apply median filter with large radius.
Med = medfilt2(fig3Gray, [51, 51], 'symmetric');
%figure;imshow(Med);
D = abs(fig3Gray - Med);
%figure;imshow(D);impixelinfo
BW = imbinarize(D, 0.3);
%figure;imshow(BW);impixelinfo
Coon = bwconncomp(BW);
fig3GrayMasked = fig3Gray;
%Cover the tall clusters and the long clusters.
for i = 1:length(Coon)
C = Coon.PixelIdxList{i}; %Cluster coordinates.
[Y, X] = ind2sub(size(fig3Gray), C); %Convert to x,y indices.
is_tall = (max(Y) - min(Y)) > 50; %true if cluster is tall.
is_wide = (max(X) - min(X)) > 50; %true if cluster is wide.
%Replace tall and long clusters by pixels from median image.
if ((is_tall) || (is_wide))
fig3GrayMasked(C) = Med(C);
end
end
%figure;imshow(fig3GrayMasked);impixelinfo
%Second iteration: search largest cluster on fig3GrayMasked image.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Med = medfilt2(fig3GrayMasked, [51, 51], 'symmetric');
D = abs(fig3GrayMasked - Med);
%figure;imshow(D);impixelinfo
BW = imbinarize(D, 0.3);
%figure;imshow(BW);impixelinfo
Coon = bwconncomp(BW);
%Find index of largest cluster in list of clusters Coon.PixelIdxList
[~, i] = max(cellfun(#numel, Coon.PixelIdxList));
%Get the indices of the largest cluster
C = Coon.PixelIdxList{i};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%Paint cluster in yellow color (just for fun).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
BW = zeros(size(BW), 'logical');
BW(C) = 1;
Y = im2uint8(cat(3, ones(size(BW)), ones(size(BW)), zeros(size(BW))));
fig3(cat(3, BW, BW, BW)) = Y(cat(3, BW, BW, BW));
figure;imshow(fig3)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Result:

How can I extract text from image respecting the distance between words?

I am trying to extract text from an image of printed text. Along with the text itself, I am interested in detecting the spaces between the words as well. The spaces between words are not consistent (deliberate) and that is something I want detected.
To achieved this I first had to extract the text lines. I achieved that using the projection profile code attached (code copied from one of the answers by ImageAnalyst).
One way I thought of achieving this was by counting the number of white pixels between the words, if I know the number of pixels taken by a single space (say n), I could just determine the number of spaces by dividing the white pixels between the words by this 'n' to get the number of spaces.
I tried that but it did not go as planned, the results are very conflicting, even when compared against known ground truth values. Determining a baseline of every text line is proving to be difficult, for a single space between two words I am getting different pixel count. This is because as counting the white pixels from letter d to b is different from counting the white pixels from c to s (the white pixels within the curve of c is also sometimes counted.)
Any guidance or suggestions would be greatly appreciated.
Thank you
code I used :
clc;
close all;
clear;
fontSize = 16;
img = imread('Lines.png');
[rows, columns, dim] = size(img);
if dim > 1
grayImage = img(:, :, 2);
end
% Display the original gray scale image.
subplot(3, 3, 1);
imshow(grayImage, []);
title('Original image', 'FontSize', fontSize);
axis on;
set(gcf, 'Units', 'Normalized', 'OuterPosition', [0 0 1 1]); % Enlarge figure to full screen.
% Threshold the image.
binaryImage = grayImage < 210;
% Get rid of small areaas of 14 pixels or less
binaryImage = ~bwareaopen(binaryImage, 15);
subplot(2, 3, 2);
imshow(binaryImage);
title('Binary Image', 'FontSize', fontSize);
axis on;
% Vertical Profile
verticalProfile = sum(binaryImage, 2);
subplot(3, 3, [2 3]);
plot(verticalProfile, 'b');
grid on;
title('Vertical Profile', 'FontSize', fontSize);
rowsWithText = verticalProfile < 600;
% Find top and bottom lines
topLines = find(diff(rowsWithText) == 1);
bottomLines = find(diff(rowsWithText) == -1);
for j = 1 : length(topLines)
topRow = topLines(j);
bottomRow = bottomLines(j);
thisLine = binaryImage(topRow:bottomRow, :);
subplot(3, 3, [4 5 6]);
imshow(thisLine, []);
axis on;
caption = sprintf('Line %d of the text', j);
title(caption, 'FontSize', fontSize);
% Horizontal profile
horizontalProfile = sum(thisLine, 1);
subplot(3, 3, [7 8 9]);
plot(horizontalProfile);
grid on;
caption = sprintf('Horizontal Profile of Line %d of the text', j);
title(caption, 'FontSize', fontSize);
promptMessage = sprintf('lines %d', j);
titleBarCaption = 'Continue?';
button = questdlg(promptMessage, titleBarCaption, 'Continue', 'Cancel', 'Continue');
if strcmpi(button, 'Cancel')
return;
end
end
msgbox('Done!');
picture : Image of my program
Try playing around with something like this:
% define min length of gap between words; play around with this value
min_gap_length = 5;
% define wordStarts/wordEnds like you've defined topLines/bottomLines
wordStarts = find(diff(horizontalProfile) == -1);
wordEnds = find(diff(horizontalProfile) == 1);
if wordStarts(1) > wordEnds(1)
wordStarts = [1, wordStarts];
end
if wordEnds(end) < wordStarts(end)
wordEnds = [wordEnds, length(horizontalProfile)];
end
% restrict wordStarts/wordEnds to gaps of over min_gap_length
I = wordStarts(2:end) - wordEnds(1:end-1) > min_gap_length;
wordStarts = wordStarts([true, I]);
wordEnds = wordEnds([I, true]);

Bad contour lines when plotting gridded data

I have heavily edited data from an underwater glider that measures temperature, conductivity, and pressure. From that we get salinity, density and sigma (sigma = density - 1000). The instrument did not sample very well, hence the heavy editing. I need to make "pretty" contour plots for a poster. This is not for a publication so I am not worried about the amount of filtering or smoothing.
I am having trouble with the contour lines of sigma (density), please see below.
The black contour lines should trace the filled contours of sigma but they look very bad. The data was binned by 1 m before any gridding is done. here is the code used to generate the plots.
Here is the code used to generate this image
% load data
load Matlab_data
maxy = 50;
t = 1;
Tstep = t./24./60; % Grid data on t minute time intervals
X1 = time(1)-Tstep:Tstep:time(end)+Tstep;
Y1 = 0:1:maxy; % Grid data on 1 meter depth intervals
ygrid = [' Depth grid: ' num2str(diff(Y1(1:2)))];
xgrid = [' Time grid: ' num2str(diff(X1(1:2)))];
[X,Y]= meshgrid(X1,Y1);
bad_vals = isnan(sal) .* ~isfinite(sal) .* isnan(press); % don't include NaNs or funky imaginary salinities in the contours
vals = find(bad_vals == 0);
Zd = griddata(time(vals),depth(vals),density(vals),X,Y);
Zt = griddata(time(vals),depth(vals),temp(vals),X,Y);
Zs = griddata(time(vals),depth(vals),sal(vals),X,Y);
Zst = griddata(time(vals),depth(vals),sigmat(vals),X,Y);
% Interpolate over gaps
vst = interp1gap(sigmat);
vs = interp1gap(sal);
% Grid interpolated salinity and sigma data
Zst_interp = griddata(time(vals),depth(vals),vst(vals),X,Y);
Zs_interp = griddata(time(vals),depth(vals),vs(vals),X,Y);
%% Contour Plot
% Set up the figure
figure3=figure('Position', [2000, 50, 1500, 500]);
clf
colormap(jet);
% Temperature
ax1 = subplot(2, 1,1);
[c,h] = contourf(X,Y,Zst,50,'linestyle','none'); %,[4:.5:9],'linestyle','none');
cRange = caxis;
hold on
[c2,h2] = contour(X,Y,Zst,[24 24.5 25],'color','k'); %,[22:.5:26.5],'linewidth',1.5,'color','k');
clabel(c2,h2,'fontsize',10,'labelspacing',150);
set(h2,'linewidth',1)
hc = colorbar;
colormap(jet);
datetick('x','mm/dd','keeplimits','keepticks');
grid on;
box on
pos = get(gca,'position');
set(gca,'YDir','reverse')%,'position',[pos(1) pos(2) pos(3)-.06 pos(4)]);
set(gca,'xlim',[time(1)+.5./24 time(end)-.5./24],...
'ylim',[0 maxy],'fontsize',8,'xminortick','on','yminortick','on');
set(get(hc,'ylabel'),'string','Sigma-Theta (kg m^-^3)','rotation',270,'verticalalignment','bottom');
ylabel('Ocean Depth (m)');
xlabel('Date');
%title(['Sigma Theta (kg m^-^3) -' strrep(tag,'_','\_')], 'fontweight', 'bold','FontSize',12)
title(['Sigma Theta (kg m^-^3) :' ygrid xgrid ], 'fontweight', 'bold','FontSize',12)
ax2 = subplot(2, 1,2);
%h=pcolor(X,Y,Zst);
[c,h] = contourf(X,Y,Zst_interp,50,'linestyle','none'); %,[4:.5:9],'linestyle','none');
shading interp
hc = colorbar;
cRange = caxis;
hold on
[c2,h2] = contour(X,Y,Zst_interp,[24 24.5 25],'color','k'); %,[22:.5:26.5],'linewidth',1.5,'color','k');
clabel(c2,h2,'fontsize',10,'labelspacing',150);
set(h2,'linewidth',1)
hc = colorbar;
colormap(jet);
caxis(cRange);
datetick('x','mm/dd','keeplimits','keepticks');
grid on;
box on
pos = get(gca,'position');
set(gca,'YDir','reverse')%,'position',[pos(1) pos(2) pos(3)-.06 pos(4)]);
set(gca,'xlim',[time(1)+.5./24 time(end)-.5./24],...
'ylim',[0 maxy],'fontsize',8,'xminortick','on','yminortick','on');
set(get(hc,'ylabel'),'string','Sigma-Theta (kg m^-^3)','rotation',270,'verticalalignment','bottom');
ylabel('Ocean Depth (m)');
xlabel('Date');
% title(['Sigma Theta (kg m^-^3) -' strrep(tag,'_','\_')], 'fontweight', 'bold','FontSize',12)
title(['Sigma Theta interp1gap (kg m^-^3) :' ygrid xgrid ], 'fontweight', 'bold','FontSize',12)
Please help! I have TWO issues... the first and most obvious is the ugly black contour lines, these should "flow" around the filled contours nicely. If anyone has experience or suggestions of how to smooth these please pass it along, with code if possible.
The second issue lies in the bottom plot, I need to fill the gaps in data (due to editing original bad data). I used the function interp1gap, available on the File exchange but it also interpolates the data to deeper depths, which I do not want. I just want the gaps to be filled in, such as by choosing their horizontal neighbors and filling in.
Please let me know if you have any suggestions for fixing this! I have attached the data (Matlab_data.mat) and it includes time, depth, sal (salinity), sigmat, press, temp, and density.
Is this an issue of gridding? Please be as specific as possible and any code and figures would be greatly appreciated if you have time to look into this.
The data is available drop box https://www.dropbox.com/s/mjd8f9bzdvwddk5/Matlab_data.mat?dl=0
Thank you very much in advance!

How to set axis for multiple subplots of pictures in Matlab

clear all; close all; clc;
A = im2double(imread('cameraman.jpg'));
figure(1)
imshow(A)
C = chunking(A,400,400) % separates picture;
[m n] = size(C);
k = 1;
figure(1)
for i = 1:m
for j = 1:n
subplot(m,n,k)
imshow(C{i,j})
axis off;
k = k + 1;
end
end
So In the above code, I am trying to separate a picture into 400x400 pixel chunks. Since the image is not a multiple of 400x400, it will have unequal sections on the border and bottom right corner (still a square image). However, when I use subplot, it resizes the last chunk to be the same size. I tried playing around with get and set position but it gives that the width and height for each subplot is the same?![enter image description here][1]
http://imgur.com/2VUYZr1
You want to resize the axes if you have less than 400 pixels to display. You should store the handle to each subplot and then resize it if it needs to be smaller.
Your call to subplot should look like this:
h = subplot(m,n,k);
num_rows = size(C{i,j}, 1);
num_cols = size(C{i,j}, 2);
set(h, 'units', 'pixels')
old_axes_pos = get(h, 'position');
new_axes_pos = old_axes_pos;
if num_cols < 400
new_axes_pos(3) = num_cols; % Make this axes narrower
end
% If the figure cannot be full height
if num_rows < 400
new_axes_pos(4) = num_rows; % Make this axes shorter
new_axes_pos(2) = old_axes_pos(2) + (400 - num_rows); % Move the bottom up
end
set(h, 'position', new_axes_pos) % Change the size of the figure

Drawing the major and minor axis of an elliptical object in MATLAB

This program currently inputs an image of a coin, thresholds it, binarizes it, and finds the major and minor axis lengths of the segmented elliptical using the regionprops function. How do I output a subplot where I draw the axes used to calculate the 'MajorAxisLength' and 'MinorAxisLength' over the original image?
I have appended my code for your perusal.
% Read in the image.
folder = 'C:\Documents and Settings\user\My Documents\MATLAB\Work';
baseFileName = 'coin2.jpg';
fullFileName = fullfile(folder, baseFileName);
fullFileName = fullfile(folder, baseFileName);
if ~exist(fullFileName, 'file')
fullFileName = baseFileName; % No path this time.
if ~exist(fullFileName, 'file')
%Alert user.
errorMessage = sprintf('Error: %s does not exist.', fullFileName);
uiwait(warndlg(errorMessage));
return;
end
end
rgbImage = imread(fullFileName);
% Get the dimensions of the image. numberOfColorBands should be = 3.
[rows columns numberOfColorBands] = size(rgbImage);
% Display the original color image.
subplot(2, 3, 1);
imshow(rgbImage, []);
title('Original color Image', 'FontSize', fontSize);
% Enlarge figure to full screen.
set(gcf, 'Position', get(0,'Screensize'));
% Extract the individual red color channel.
redChannel = rgbImage(:, :, 1);
% Display the red channel image.
subplot(2, 3, 2);
imshow(redChannel, []);
title('Red Channel Image', 'FontSize', fontSize);
% Binarize it
binaryImage = redChannel < 100;
% Display the image.
subplot(2, 3, 3);
imshow(binaryImage, []);
title('Thresholded Image', 'FontSize', fontSize);
binaryImage = imfill(binaryImage, 'holes');
labeledImage = bwlabel(binaryImage);
area_measurements = regionprops(labeledImage,'Area');
allAreas = [area_measurements.Area];
biggestBlobIndex = find(allAreas == max(allAreas));
keeperBlobsImage = ismember(labeledImage, biggestBlobIndex);
measurements = regionprops(keeperBlobsImage,'MajorAxisLength','MinorAxisLength')
% Display the original color image with outline.
subplot(2, 3, 4);
imshow(rgbImage);
hold on;
title('Original Color Image with Outline', 'FontSize',fontSize);
boundaries = bwboundaries(keeperBlobsImage);
blobBoundary = boundaries{1};
plot(blobBoundary(:,2), blobBoundary(:,1), 'g-', 'LineWidth', 1);
hold off;
I had the same task as you for some project I did 2 years ago. I've modified the code I used then for you below. It involved calculating the covariance matrix for the datapoints and finding their eigenvalues/eigenvectors. Note here that because of circular symmetry, the minor and major axis will be somewhat "random". Also note that I have made the image binary in a very naïve way to keep the code simple.
% Load data and make bw
clear all;close all; clc;
set(0,'Defaultfigurewindowstyle','docked')
I = imread('american_eagle_gold_coin.jpg');
Ibw = im2bw(I,0.95);
Ibw = not(Ibw);
figure(1);clf
imagesc(Ibw);colormap(gray)
%% Calculate axis and draw
[M N] = size(Ibw);
[X Y] = meshgrid(1:N,1:M);
%Mass and mass center
m = sum(sum(Ibw));
x0 = sum(sum(Ibw.*X))/m;
y0 = sum(sum(Ibw.*Y))/m;
%Covariance matrix elements
Mxx = sum(sum((X-x0).^2.*Ibw))/m;
Myy = sum(sum((Y-y0).^2.*Ibw))/m;
Mxy = sum(sum((Y-y0).*(X-x0).*Ibw))/m;
MM = [Mxx Mxy; Mxy Myy];
[U S V] = svd(MM);
W = V(:,1)/sign(V(1,1)); %Extremal directions (normalized to have first coordinate positive)
H = V(:,2);
W = 2*sqrt(S(1,1))*W; %Scaling of extremal directions to give ellipsis half axis
H = 2*sqrt(S(2,2))*H;
figure(1)
hold on
plot(x0,y0,'r*');
quiver(x0,y0,W(1),H(1),'r')
quiver(x0,y0,W(2),H(2),'r')
hold off
Look at the documentation for the Orientation attribute that regionprops() can return to you.
This gives the angle between the positive x-axis and the major axis of the ellipse. You should be able to derive the equation for the major axis line in terms of that angle, and then just make a grid of x-axis points, and compute the major axis line's value for all the points in your grid, then just plot it like you would plot any other curve in MATLAB.
To do the same for the minor axis, just note that it will be 90 degrees further counter-clockwise from the major axis, then repeat the step above.
Usually one does it with computing eigenvectors, as explained in the Wikipedia article Image moment under 'examples'. That would be the correct way.
But I wonder, if you know the centroid and the boundingbox from MATLAB, then the endpoint of the major axis must be in the upper left or upper right corner. So checking (apart from noise) these two corners, if there are pixel, would give you the major axis. The minor axis then is just orthogonal to it with respect to the centroid.
Sorry for not having MATLAB code ready.
The reasoning is not that wrong, but not so good either, using the orientation as written above is better ;)