Fill gaps in binary leaf image occured from segmentation preserving leaf teeth shape - matlab

after leaf segmentation i got the following binary image:
Is there a way to fill the gaps caused by the similiarity of the veins with the background? I've tried to use imclose, or imdilate etc but it affects teeth shape. I can't find out how to fill these gaps without affecting teeth shape.

You may try bwfill(I, 'hols'), with out without imclose:
I = imbinarize(rgb2gray(imread('leaf.jpg')));
I = I(3:end-4, 1:end-8); %Remove white frame
J = imclose(I, ones(2)); %Minor affect the teeth shape (result looks better with imclose).
K = bwfill(J, 'hols'); %Fill the black hols
Result:
In case you want to fill the "vein gaps", you can try the following approach:
I = imbinarize(rgb2gray(imread('leaf.jpg')));
I = I(3:end-4, 1:end-8); %Remove white frame
I = bwfill(I, 'hols'); %Fill small black hols.
J = imerode(imdilate(I, strel('disk',5)), strel('disk',10)); %Dilate with radius 5 and erode with 10
T = (I == 0) & (J == 1); %Create mask with 1 where I is black and J is white "vein mask".
K = I;
K(T) = 1; %Fill "vein mask" in I with white.
K = bwfill(K, 'hols'); %Fill small black hols (fill tiny holds left).
Result:

Related

How to remove white annotations from image?

I try to remove the white annotations of this image (the numbers and arrows), as well as the black grid, with MATLAB:
I tried to compute, for each pixel, the mode of neighbors, but this process is very slow and I get poor results.
How can I obtain an image like this one?
Thank you for your time.
The general name for such a task is inpainting. If you search for that you will find better methods than what I'm showing here. This is no more than a proof of concept. I'm using DIPimage 3 (because I'm an author and it's easy for me to use).
First we need to create a mask for the regions that we want to remove (inpaint). It is easy to find pixels where all three channels have a high value (white) or a low value (black):
img = readim('https://i.stack.imgur.com/16r9N.png');
% Find a mask for the areas to remove
whitemask = min(img,'tensor') > 50;
blackmask = max(img,'tensor') < 30;
mask = whitemask | blackmask;
This mask doesn't capture all of the black grid, if we increase the threshold we will also remove the dark region of sea off the coast of Spain. And it also captures the white outline of the coasts. We can do a little bit better than this with some additional filtering:
% Find a mask for the areas to remove
whitemask = min(img,'tensor') > 50;
whitemask = whitemask - pathopening(whitemask,50);
blackmask = max(img,'tensor');
blackmask2 = blackmask < 80;
blackmask2 = blackmask2 - areaopening(blackmask2,6);
blackmask = blackmask < 30 | blackmask2;
mask = whitemask | blackmask;
This produces the following mask:
Still far from perfect, but a good start for our proof of concept.
One simple inpainting method uses normalized convolution: using the inverse of the mask we made, convolve the image multiplied by the mask, and convolve the mask separately. The ratio of these two results is a smoothed image that doesn't take the masked pixels into account. Finally, we replace the pixels in the original image under the mask with the values from this normalized convolution:
% Solution 1: normalized convolution
smooth = gaussf(img * ~mask, 2) / gaussf(~mask, 2);
img(mask) = smooth(mask);
An alternative solution applies a closing on the image multiplied by the mask (note that this multiplication makes the pixels we don't want completely black; the closing will spread the surrounding colors over the black areas):
% Solution 2: morphology
smooth = iterate('closing',img * ~mask, 13);
img(mask) = smooth(mask);

Why can't I colour my segmented region from the original image

I have the following code:
close all;
star = imread('/Users/name/Desktop/folder/pics/OnTheBeach.png');
blrtype = fspecial('average',[3 3]);
blurred = imfilter(star, blrtype);
[rows,cols,planes] = size(star);
R = star(:,:,1); G = star(:,:,2); B = star(:,:,3);
starS = zeros(rows,cols);
ind = find(R > 190 & R < 240 & G > 100 & G < 170 & B > 20 & B < 160);
starS(ind) = 1;
K = imfill(starS,'holes');
stats = regionprops(logical(K), 'Area', 'Solidity');
ind = ([stats.Area] > 250 & [stats.Solidity] > 0.1);
L = bwlabel(K);
result = ismember(L,find(ind));
Up to this point I load an image, blur to filter out some noise, do colour segmentation to find the specific objects which fall in that range, then create a binary image that has value 1 for the object's colour, and 0 for all other stuff. Finally I do region filtering to remove any clutter that was left in the image so I'm only left with the objects I'm looking for.
Now I want to recolour the original image based on the segmentation mask to change the colour of the starfish. I want to create Red,Green,Blue channels, assign value to them then lay the mask over the image. (To have red starfishes for example)
red = star;
red(starS) = starS(:,:,255);
green = star;
green(starS) = starS(:,:,0);
blue = star;
blue(starS) = star(:,:,0);
out = cat(3, red, green, blue);
imshow(out);
This gives me an error: Index exceeds matrix dimensions.
Error in Project4 (line 28)
red(starS) = starS(:,:,255);
What is wrong with my current approach?
Your code is kinda confusing... I don't understand whether the mask you want to use is starS or result since both look like 2d indexers. In your second code snippet you used starS, but the mask you posted in your question is result.
Anyway, no matter what your desired mask is, all you have to do is to use the imoverlay function. Here is a small example based on your code:
out = imoverlay(star,result,[1 0 0]);
imshow(out);
and here is the output:
If the opaque mask of imoverlay suggested by Tommaso is not what you're after, you can modify the RGB values of the input to cast a hue over the selected pixels without saturating them. It is only slightly more involved.
I = find(result);
gives you an index of the pixels in the 2D image. However, star is 3D. Those indices will point at the same pixels, but only at the first 2D slice. That is, if I points at pixel (x,y), it is equivalently pointing to pixel (x,y,1). That is the red component of the pixel. To index (x,y,2) and (x,y,2), the green and blue components, you need to increment I by numel(result) and 2*numel(result). That is, star(I) accesses the red component of the selected pixels, star(I+numel(result)) accesses the green component, and star(I+2*numel(result)) accesses the blue component.
Now that we can access these values, how do we modify their color?
This is what imoverlay does:
I = find(result);
out = star;
out(I) = 255; % red channel
I = I + numel(result);
out(I) = 0; % green channel
I = I + numel(result);
out(I) = 0; % blue channel
Instead, you can increase the brightness of the red proportionally, and decrease the green and blue. This will change the hue, increase saturation, and preserve the changes in intensity within the stars. I suggest the gamma function, because it will not cause strong saturation artefacts:
I = find(result);
out = double(star)/255;
out(I) = out(I).^0.5; % red channel
I = I + numel(result);
out(I) = out(I).^1.5; % green channel
I = I + numel(result);
out(I) = out(I).^1.5; % blue channel
imshow(out)
By increasing the 1.5 and decreasing the 0.5 you can make the effect stronger.

Matlab radial gradient image

I am attempting to create a radial gradient image to look like the following using Matlab. The image needs to be of size 640*640*3 as I have to blend it with another image of that size. I have written the following code but the image that prints out is simply a grey circle on a black background with no fading around the edges.
p = zeros(640,640,3);
for i=1:640
for j=1:640
d = sqrt((i-320)^2+(j-320)^2);
if d < 640/3
p(i,j,:) = .5;
elseif d > 1280/3
p(i,j,:) = 0;
else
p(i,j,:) = (1 + cos(3*pi)*(d-640/3))/4;
end
end
end
imshow(p);
Any help would be greatly appreciated as I am new to Matlab.
Change:
p(i,j,:) = (1 + cos(3*pi)*(d-640/3))/4;
to
p(i,j,:) = .5-( (.5-0)*(d-640/3)/(640/3)) ;
This is an example of linear interpolation, where the grey value from the inner rim drops linearly to the background.
You can try other equations to have different kinds of gradient fading!
If you look more closely on your third case (which by the way should be a simple else instead of elseif), you can see that you have
= (1 + cos(3*pi))*...
Since cos(3*pi) = -1, this will always be 0, thus making all pixels within that range black. I assume that you would want a "d" in there somewhere.

Stretching an ellipse in an image to form a circle

I want to stretch an elliptical object in an image until it forms a circle. My program currently inputs an image with an elliptical object (eg. coin at an angle), thresholds and binarizes it, isolates the region of interest using edge-detect/bwboundaries(), and performs regionprops() to calculate major/minor axis lengths.
Essentially, I want to use the 'MajorAxisLength' as the diameter and stretch the object on the minor axis to form a circle. Any suggestions on how I should approach this would be greatly appreciated. I have appended some code for your perusal (unfortunately I don't have enough reputation to upload an image, the binarized image looks like a white ellipse on a black background).
EDIT: I'd also like to apply this technique to the gray-scale version of the image, to examine what the stretch looks like.
code snippet:
rgbImage = imread(fullFileName);
redChannel = rgbImage(:, :, 1);
binaryImage = redChannel < 90;
labeledImage = bwlabel(binaryImage);
area_measurements = regionprops(labeledImage,'Area');
allAreas = [area_measurements.Area];
biggestBlobIndex = find(allAreas == max(allAreas));
keeperBlobsImage = ismember(labeledImage, biggestBlobIndex);
measurements = regionprops(keeperBlobsImage,'Area','MajorAxisLength','MinorAxisLength')
You know the diameter of the circle and you know the center is the location where the major and minor axes intersect. Thus, just compute the radius r from the diameter, and for every pixel in your image, check to see if that pixel's Euclidean distance from the cirlce's center is less than r. If so, color the pixel white. Otherwise, leave it alone.
[M,N] = size(redChannel);
new_image = zeros(M,N);
for ii=1:M
for jj=1:N
if( sqrt((jj-center_x)^2 + (ii-center_y)^2) <= radius )
new_image(ii,jj) = 1.0;
end
end
end
This can probably be optimzed by using the meshgrid function combined with logical indices to avoid the loops.
I finally managed to figure out the transform required thanks to a lot of help on the matlab forums. I thought I'd post it here, in case anyone else needed it.
stats = regionprops(keeperBlobsImage, 'MajorAxisLength','MinorAxisLength','Centroid','Orientation');
alpha = pi/180 * stats(1).Orientation;
Q = [cos(alpha), -sin(alpha); sin(alpha), cos(alpha)];
x0 = stats(1).Centroid.';
a = stats(1).MajorAxisLength;
b = stats(1).MinorAxisLength;
S = diag([1, a/b]);
C = Q*S*Q';
d = (eye(2) - C)*x0;
tform = maketform('affine', [C d; 0 0 1]');
Im2 = imtransform(redChannel, tform);
subplot(2, 3, 5);
imshow(Im2);

Matlab fill shapes by white

As you see, I have shapes and their white boundaries. I want to fill the shapes in white color.
The input is:
I would like to get this output:
Can anybody help me please with this code? it doesn't change the black ellipses to white.
Thanks alot :]]
I = imread('untitled4.bmp');
Ibw = im2bw(I);
CC = bwconncomp(Ibw); %Ibw is my binary image
stats = regionprops(CC,'pixellist');
% pass all over the stats
for i=1:length(stats),
size = length(stats(i).PixelList);
% check only the relevant stats (the black ellipses)
if size >150 && size < 600
% fill the black pixel by white
x = round(mean(stats(i).PixelList(:,2)));
y = round(mean(stats(i).PixelList(:,1)));
Ibw = imfill(Ibw, [x, y]);
end;
end;
imshow(Ibw);
Your code can be improved and simplified as follows. First, negating Ibw and using BWCONNCOMP to find 4-connected components will give you indices for each black region. Second, sorting the connected regions by the number of pixels in them and choosing all but the largest two will give you indices for all the smaller circular regions. Finally, the linear indices of these smaller regions can be collected and used to fill in the regions with white. Here's the code (quite a bit shorter and not requiring any loops):
I = imread('untitled4.bmp');
Ibw = im2bw(I);
CC = bwconncomp(~Ibw, 4);
[~, sortIndex] = sort(cellfun('prodofsize', CC.PixelIdxList));
Ifilled = Ibw;
Ifilled(vertcat(CC.PixelIdxList{sortIndex(1:end-2)})) = true;
imshow(Ifilled);
And here's the resulting image:
If your images are all black&white, and you have the image processing toolkit, then this looks like what you need:
http://www.mathworks.co.uk/help/toolbox/images/ref/imfill.html
Something like:
imfill(image, [startX, startY])
where startX, startY is a pixel in the area that you want to fill.