So I have a binary matrix in Matlab.
It is basically a blob (pixels of value 1) surrounded by a neutral background (value 0).
I want to figure out whether this blob is simply connected or not.
Figure below is a straightforward example.
How can this be achieved?
Notably I understand that every path in a pixelated image can be created by choosing from 4 adjacent elements (up, down, left, right) or 8 adjacent elements etc - it doesn't matter in this case.
Code
%// Assuming bw1 is the input binary matrix
[L,num] = bwlabel( ~bw1 );
counts = sum(bsxfun(#eq,L(:),1:num));
[~,ind] = max(counts);
bw2 = ~(L==ind);
%// Output decision
[L,num] = bwlabel( bw1 );
if ~nnz(bw1~=bw2) && num==1
disp('Yes it is a simply connected blob.')
else
disp('Nope, not a simply connected blob.')
end
Related
I am trying to compute contour of a binary image. Currently i identify the first non zero and the last non zero pixel in the image through looping. Is there a better way? i have encountered few functions:
imcontour(I)
bwtraceboundary(bw,P,fstep,conn,n,dir)
But the first doesn't return the x and y coordinates of the contour. The second function requires a seed point which i cannot provide. An example of the image is shown below. Thanks.
I'm surprised you didn't see bwperim. Did you not try bwperim? This finds the perimeter pixels of all closed objects that are white in a binary image. Using your image directly from StackOverflow:
im = im2bw(imread('http://i.stack.imgur.com/yAZ5L.png'));
out = bwperim(im);
imshow(out);
We get:
#rayryeng have already provided the correct answer. As another approach (might be that bwperim performs this operations internally) boundaries of a binary image can be obtained by calculating the difference between the dilated and the eroded image.
For a given image:
im = im2bw(imread('http://i.stack.imgur.com/yAZ5L.png'));
and a given binary structural element:
selem = ones(3,3); %// square, 8-Negihbours
% selem = [0 1 0; 1 0 1; 0 1 0]; %// cross, 4-Neighbours
The contour of the object can be extracted as:
out = imerode(im, selem) ~= imdilate(im, selem);
Here, however, the boundary is thicker than using bwperim, as the pixels are masked in both inside and outside of the object.
I had the same problem, stumbled across this question and just wanted to add that imcontour(Img); does return a matrix. The first row contains the x-values, the second row contains the y-values.
contour = imcontour(Img); x = contour(1,:); y = contour(2,:);
But I would discard the first column.
I was trying to decrease the number of points of a detected edge of an image but I didn't obtain a good result. I want the result to contain exactly 200 pixels of the edges, but those points must be well chosen so the shape remain very clear. How can I do this?
Here's an example image of what I'm working with:
Here are some results that I have received with the code I wrote:
Code written
function y = echantillonnage(x)
contour_image = x;
[lignes,colonnes]=size(x);
n = nombre/200;
contour_image_200px = contour_image;
ok=0;
for i=1:lignes
for j=1:colonnes
if (contour_image_200px(i,j)>0 )
ok=ok+1;
if ( mod(ok,round(n))>0 )
contour_image_200px(i,j)=0;
end
end
end
end
figure,imshow(contour_image_200px);
%résultat
y = contour_image_200px;
end
What you can do is use bwboundaries to trace the boundaries of the objects / edges then sample from those array of points to decrease the number of edge points. The tracing is done in clockwise order, so you are sure that when you subsample from this array, you will get a semblance of order. However, bwboundaries also returns both outer and inner contour boundaries, so you only need a certain amount of traces from the output. We will talk about that later.
bwboundaries works for multiple objects, so all you'd have to do is iterate through each object, sample the edge points and write that to an output result. Note that using bwboundaries doesn't even require edges to be found... as long as the object is clean then it isn't necessary. However, I'm not sure as to the purpose of what you're doing, so let's just operate on the edge detected result.
Let's say we had the following example:
>> A = false(256, 256);
>> A(100:170,100:170) = true;
>> A(3:40,3:40) = true;
>> A(190:220,200:230) = true;
>> imshow(A)
We get this image:
If we performed an edge detection:
>> B = edge(A, 'canny');
>> imshow(B);
We would get this:
Now, if you want to do the subsampling, you would call bwboundaries this way:
[bound,L,N] = bwboundaries(B);
bwboundaries returns a cell array bound of boundaries where each cell is a N x 2 array of spatial coordinates that define the boundary. The first column are the row locations and the second column are the column locations of the boundary points. L is a label matrix that tells you which point each boundary belongs to. We don't need this for your purposes but I might as well talk about it. N is the most important parameter. This defines how many object boundaries there are. This also tells you that the first N cells of bound tells you that those belong to the outer object boundaries.
As such, you can do the following to subsample your edge points and put them into a new matrix, assuming that your edge image is stored in B. Also, you stated that you want to have 200 points per edge. Let's define that parameter as num_edge_points. However, if you have edges that are less than this amount, then I will assume that you'll just want to have all of the edge points selected.
out = false(size(B)); %// Initialize output image
num_edge_points = 200; %// Define number of edge points
%// For each object boundary
for idx = 1 : N
boundary = bound{idx}; %// Get boundary
%// Determine how many points we have
num_pts = size(boundary,1);
%// Generate indices for sampling the boundary
%// If there are less than the minimum, just choose this amount
if num_pts < num_edge_points
ind = 1:num_pts;
else
ind = floor(linspace(1,num_pts,num_edge_points));
end
%// Subsample the edge points
pts = boundary(ind,:);
%// Mark points in output
out(sub2ind(size(B), pts(:,1), pts(:,2))) = true;
end
out will contain your edge image subsampled. To illustrate that we got this right, let's create a new RGB image where we have the edges and the subsampled edge points on top of each other where the subsampled edges are in red:
out_red = 255*uint8(B);
out_greenblue = out_red;
out_greenblue(out) = 0;
out_rgb = cat(3, out_red, out_greenblue, out_greenblue);
imshow(out_rgb);
This is what we get (zoomed-in):
As you can see, the top and bottom rectangles have the full edge points show as there were less than 200 edge points. However, the one in the middle is sparsely sampled as there are more than 200 edge points, but now we are only displaying 200 of them.
If you would like a function to help you facilitate this, you can use the following. I've essentially copied all of the code above and the input is a binary image with edges and the output is a binary image with the subsampled edges:
function [out] = subsample_edge(B)
%// Obtain boundaries for edge image
[bound,L,N] = bwboundaries(B);
out = false(size(B)); %// Initialize output image
num_edge_points = 200; %// Define number of edge points
%// For each object boundary
for idx = 1 : N
boundary = bound{idx}; %// Get boundary
%// Determine how many points we have
num_pts = size(boundary,1);
%// Generate indices for sampling the boundary
%// If there are less than the minimum, just choose this amount
if num_pts < num_edge_points
ind = 1:num_pts;
else
ind = floor(linspace(1,num_pts,num_edge_points));
end
%// Subsample the edge points
pts = boundary(ind,:);
%// Mark points in output
out(sub2ind(size(B), pts(:,1), pts(:,2))) = true;
end
end
If you want to call this function, simply do:
out = subsample_edge(B);
So I have a binary matrix in Matlab.
It is basically a blob (pixels of value 1) surrounded by a neutral background (value 0).
I want to figure out whether this blob is simply connected or not.
Figure below is a straightforward example.
How can this be achieved?
Notably I understand that every path in a pixelated image can be created by choosing from 4 adjacent elements (up, down, left, right) or 8 adjacent elements etc - it doesn't matter in this case.
Code
%// Assuming bw1 is the input binary matrix
[L,num] = bwlabel( ~bw1 );
counts = sum(bsxfun(#eq,L(:),1:num));
[~,ind] = max(counts);
bw2 = ~(L==ind);
%// Output decision
[L,num] = bwlabel( bw1 );
if ~nnz(bw1~=bw2) && num==1
disp('Yes it is a simply connected blob.')
else
disp('Nope, not a simply connected blob.')
end
Using the code below I can draw a bounding box around every connected component.
bw_normal2 = im2bw(img, graythresh(img));
bw22 = imcomplement(bw_normal2);
bw3 = bwmorph(bw22, 'dilate');
[label2,n2] = bwlabel(bw3);
stats2 = regionprops(label2, {'Area', 'BoundingBox'});
area2 = stats2.Area;
idx = find((0 <= area2) & (area2 <= 6000))
BW2 = ismember(label2,idx);
figure, imshow(BW2)
imshow(img);
for j=1:n
hold on
rectangle('Position',[stats2(j).BoundingBox(1),stats2(j).BoundingBox(2),stats2(j).BoundingBox(3),stats2(j).BoundingBox(4)],...
'EdgeColor','r','LineWidth',2 );
end
Rather than making a bounding box around every connected component is it possible to randomly colour them?
Why don't you just use label2? That's already a matrix with each individual component given a different value. Then apply one of the built in colormaps, or produce a colormap which forces the background (zero) values to be white or black as you prefer.
imshow(label2,[])
colormap(lines)
A biologist friend of mine asked me if I could help him make a program to count the squama (is this the right translation?) of lizards.
He sent me some images and I tried some things on Matlab. For some images it's much harder than other, for example when there are darker(black) regions. At least with my method. I'm sure I can get some useful help here. How should I improve this? Have I taken the right approach?
These are some of the images.
I got the best results by following Image Processing and Counting using MATLAB. It's basically turning the image into Black and white and then threshold it. But I did add a bit of erosion.
Here's the code:
img0=imread('C:...\pic.png');
img1=rgb2gray(img0);
%The output image BW replaces all pixels in the input image with luminance greater than level with the value 1 (white) and replaces all other pixels with the value 0 (black). Specify level in the range [0,1].
img2=im2bw(img1,0.65);%(img1,graythresh(img1));
imshow(img2)
figure;
%erode
se = strel('line',6,0);
img2 = imerode(img2,se);
se = strel('line',6,90);
img2 = imerode(img2,se);
imshow(img2)
figure;
imshow(img1, 'InitialMag', 'fit')
% Make a truecolor all-green image. I use this later to overlay it on top of the original image to show which elements were counted (with green)
green = cat(3, zeros(size(img1)),ones(size(img1)), zeros(size(img1)));
hold on
h = imshow(green);
hold off
%counts the elements now defined by black spots on the image
[B,L,N,A] = bwboundaries(img2);
%imshow(img2); hold on;
set(h, 'AlphaData', img2)
text(10,10,strcat('\color{green}Objects Found:',num2str(length(B))))
figure;
%this produces a new image showing each counted element and its count id on top of it.
imshow(img2); hold on;
colors=['b' 'g' 'r' 'c' 'm' 'y'];
for k=1:length(B),
boundary = B{k};
cidx = mod(k,length(colors))+1;
plot(boundary(:,2), boundary(:,1), colors(cidx),'LineWidth',2);
%randomize text position for better visibility
rndRow = ceil(length(boundary)/(mod(rand*k,7)+1));
col = boundary(rndRow,2); row = boundary(rndRow,1);
h = text(col+1, row-1, num2str(L(row,col)));
set(h,'Color',colors(cidx),'FontSize',14,'FontWeight','bold');
end
figure;
spy(A);
And these are some of the results. One the top-left corner you can see how many were counted.
Also, I think it's useful to have the counted elements marked in green so at least the user can know which ones have to be counted manually.
There is one route you should consider: watershed segmentation. Here is a quick and dirty example with your first image (it assumes you have the IP toolbox):
raw=rgb2gray(imread('lCeL8.jpg'));
Icomp = imcomplement(raw);
I3 = imhmin(Icomp,20);
L = watershed(I3);
%%
imagesc(L);
axis image
Result shown with a colormap:
You can then count the cells as follows:
count = numel(unique(L));
One of the advantages is that it can be directly fed to regionprops and give you all the nice details about the individual 'squama':
r=regionprops(L, 'All');
imshow(raw);
for k=2:numel(r)
if r(k).Area>100 % I chose 100 to filter out the objects with a small are.
rectangle('Position',r(k).BoundingBox, 'LineWidth',1, 'EdgeColor','b', 'Curvature', [1 1]);
end
end
Which you could use to monitor over/under segmentation:
Note: special thanks to #jucestain for helping with the proper access to the fields in the r structure here