I'm using the watershed algorithm to try and segment touching nuclei. A typical image may look like:
or this:
I'm trying to apply the watershed algorithm with this code:
show(RGB_img)
%Convert to grayscale image
I = rgb2gray(RGB_img);
%Take structuring element of a disk of size 10, for the morphological transformations
%Attempt to subtract the background from the image: top hat is the
%subtraction of the open image from the original
%Morphological transformation to subtract background noise from the image
%Tophat is the subtraction of an opened image from the original. Remove all
%images smaller than the structuring element of 10
I1 = imtophat(I, strel('disk', 10));
%Increases contrast
I2 = imadjust(I1);
%show(I2,'contrast')
%Assume we have background and foreground and assess thresh as such
level = graythresh(I2);
%Convert to binary image based on graythreshold
BW = im2bw(I2,level);
show(BW,'C');
BW = bwareaopen(BW,8);
show(BW,'C2');
BW = bwdist(BW) <= 1;
show(BW,'joined');
%Complement because we want image to be black and background white
C = ~BW;
%Use distance tranform to find nearest nonzero values from every pixel
D = -bwdist(C);
%Assign Minus infinity values to the values of C inside of the D image
% Modify the image so that the background pixels and the extended maxima
% pixels are forced to be the only local minima in the image (So you could
% hypothetically fill in water on the image
D(C) = -Inf;
%Gets 0 for all watershed lines and integers for each object (basins)
L = watershed(D);
show(L,'L');
%Takes the labels and converts to an RGB (Using hot colormap)
fin = label2rgb(L,'hot','w');
% show(fin,'fin');
im = I;
%Superimpose ridgelines,L has all of them as 0 -> so mark these as 0(black)
im(L==0)=0;
clean_img = L;
show(clean_img)
For whatever reason after C = ~BW; the whole image goes dark. This very same code block has worked on a handful of other images, all of which were more "solid" or not as grainy as these. However, I thought I compensated for this with BW = bwdist(BW) <= 1;. I've experimented a ton and I don't really know what's happening. Any help would be great!
Ps. this is the image after BW = bwareaopen(BW,8);
Before the top-hat, you should perform a closing and an opening in order to reduce the noise.
If you perform an area opening on a noisy image, you may end up with the result on your black and white image.
So it would be:
Closing and opening
Top-Hat
Area opening if still necessary
Thresholding
Erosion and dilation to find the inner and outer markers respectively
Watershed (never use a watershed without markers).
Related
In the code below I rotated an image. How can I get a single background color (white or black)?
code:
close all;
clear;
clc;
url='http://www.clker.com/cliparts/T/i/o/c/X/Q/airplane-md.png';
RI = imread(url);
I = rgb2gray(RI);
BI = imbinarize(I);
LI = bwlabel(BI);
mea = regionprops(LI, 'All');
RI = imrotate(RI, -mea(1).Orientation,'loose');
imshow(RI);
Given that the image is a simple logo (as opposed to a photo for instance) you can likely use logical indexing to change all of the black pixels added by imrotate to white pixels.
I don't have the image processing toolbox so I couldn't run your code, but the sample below should illustrate:
%Load RBG image to test on
RI = imread('peppers.png');
%Create black region to remove
RI(100:150,100:150,:) = 0;
figure()
imshow(RI)
title('Original Image')
%Replace all black pixels with white
inds = sum(RI,3)==0;
RI_new = RI;
RI_new(repmat(inds,1,1,3))=255;
figure()
imshow(RI_new)
title('New Image')
In comparison to the answer from #SardarUsama, this has the weakness of assuming there are no black pixels in your original image but the advantage of using built-in Matlab functions only.
Edit: Updated to show example on RGB image rather than grayscale
Your original image has white background. When you rotate it, you get black pixels in the background to fill up the image matrix. This may be due to that the preallocation of the rotated image matrix is done with zeros which then translates to black (implemented probably in imrotatemex and in the lines 116 and 118 of imrotate). You can use these alternate implementations of imrotate but preallocate the matrix with ones (for double data) or 255 (for uint8 data).
For example, in line 31 of Rody's implementation, i.e.:
imagerot = zeros([max(dest) p],class(image));
Change this line to:
imagerot = 255*ones([max(dest) p],'uint8'); %Your image is uint8 in this case
Result:
i'm working in matlab and i wanted to apply the Contrast Stretching for grey scale image and also RGB image ,
so for the grey scale i've tried this one and it worked
clear all;
clc;
itemp = imread('cameraman.tif'); %read the image
i = itemp(:,:,1);
rtemp = min(i); % find the min. value of pixels in all the
columns (row vector)
rmin = min(rtemp); % find the min. value of pixel in the image
rtemp = max(i); % find the max. value of pixels in all the
columns (row vector)
rmax = max(rtemp); % find the max. value of pixel in the image
m = 255/(rmax - rmin); % find the slope of line joining point
(0,255) to (rmin,rmax)
c = 255 - m*rmax; % find the intercept of the straight line
with the axis
i_new = m*i + c; % transform the image according to new slope
figure,imshow(i); % display original image
figure,imshow(i_new); % display transformed image
this is for greyscale image ,
the problem is that that i don't know how to do for the RGB image
any idea? how to implement that?
thank you :)
Could the function stretchlim (reference) be useful for your purpose?
Find limits to contrast stretch image.
Low_High = stretchlim(RGB,Tol) returns Low_High, a two-element vector
of pixel values that specify lower and upper limits that can be used
for contrast stretching truecolor image RGB.
img = imread('myimg.png');
lohi = stretchlim(img,[0.2 0.8]);
If you write
rmin = min(i(:));
Then it computes the minimum over all values in i. This will work for RGB images also, which simply are 3D matrices with 3 values along the 3rd dimension.
The rest of your code also applies directly to such images.
I tried to use image processing toolbox to count my colony. I used imfindcircles to find the colony and count. But I got some problems:
(1)Due to that my colony could be white and black, I tried to find the colony by using Bright or Dark in ObjectPolarity, and use a if loop to select which one I finally choose. But the first step of my if loop doesn't really work.
(2) For using imfindcircles to find the circle, I found it works for the colony in white, while the method is a disaster for the black colony. I'm kind of desperate now because I can't find other ways to segment the colony.
So finally i need to: label each colony in the plate, count the colony number, calculate each colony size, extract the mean gray value for each colony (the colour).
Thank you very much!!!
So here is my code:
im = imread(test.jpeg)
imshow(im)
% to find the colony
[centersBright, radiiBright] = imfindcircles(im,[30 60],'ObjectPolarity','bright','Sensitivity',0.925,'Method','twostage','EdgeThreshold',0.1)
[centersDark, radiiDark] = imfindcircles(im,[30 60],'ObjectPolarity','dark','Sensitivity',0.925,'Method','twostage','EdgeThreshold',0.15)
% to select which one is the correct count. if one of the length(centres) value is lower than 10, I consider it as an error. But if both length(centres) is low than 10, I consider it as a treatment effect and accept the value.
if length(centersDark)<10<length(centersBright)
centers=centersBright
radii=radiiBright
elseif length(centersBright)<10<length(centersDark)
centers=centersDark
radii=radiiDark
else
centers=[centersBright,centersDark]
radii=[radiiBright,radiiDark]
end
% view and label the colony
h = viscircles(centers,radii)
for k = 1:length(radii)
string = sprintf('%d',k)
text(centers(k,1),centers(k,2),string,'color','y','HorizontalAlignment', 'center','VerticalAlignment', 'middle')
area(k)=pi*radii(k)^2
end
Suggested Solution
While it's hard to distinguish between the colonies and their surrounding in the black and white version of your input, it is not hard to do so in the hue space by using thresholding.
The reason is that the colonies have a unique hue which is different from their background.
This will be more noticable after converting to HSV space:
Therefore, I suggest the folowing solution:
convert input image to HSV space
use thresholding on the hue component.
extract connected components
Use the connected components which are large enough for the mask
perform imclose opertation for cleaning the artifacts
Code
%reads the image
I = imread('colony2.png');
%convert to hsv
hsvIm = rgb2hsv(I);
%thresholding on the hue space
bwIm = hsvIm(:,:,1) < 0.15;
%find connected components
CC = bwconncomp(bwIm);
%choose only cc with sufficient amount of pixels
numPixels = cellfun(#numel,CC.PixelIdxList);
relevantCC = CC.PixelIdxList(numPixels > 10);
%generate a binary mask with these conencted componants
colonyMask = false(size(bwIm));
for ii=1:length(relevantCC)
colonyMask(relevantCC{ii}) = true;
end
%perform morpholopical operations for cleaning
colonyMask = imclose(colonyMask,strel('disk',1));
%display result
h = imshow(I); % Save the handle; we'll need it later
set(h, 'AlphaData', ~colonyMask);
Results
Selection of threshold
The threshold was chosen by choosing the first pick in the histogram of the hue component
histogram(hsvIm(:,:,1))
I'm using the watershed algorithm to try and segment touching nuclei. A typical image may look like:
or this:
I'm trying to apply the watershed algorithm with this code:
show(RGB_img)
%Convert to grayscale image
I = rgb2gray(RGB_img);
%Take structuring element of a disk of size 10, for the morphological transformations
%Attempt to subtract the background from the image: top hat is the
%subtraction of the open image from the original
%Morphological transformation to subtract background noise from the image
%Tophat is the subtraction of an opened image from the original. Remove all
%images smaller than the structuring element of 10
I1 = imtophat(I, strel('disk', 10));
%Increases contrast
I2 = imadjust(I1);
%show(I2,'contrast')
%Assume we have background and foreground and assess thresh as such
level = graythresh(I2);
%Convert to binary image based on graythreshold
BW = im2bw(I2,level);
show(BW,'C');
BW = bwareaopen(BW,8);
show(BW,'C2');
BW = bwdist(BW) <= 1;
show(BW,'joined');
%Complement because we want image to be black and background white
C = ~BW;
%Use distance tranform to find nearest nonzero values from every pixel
D = -bwdist(C);
%Assign Minus infinity values to the values of C inside of the D image
% Modify the image so that the background pixels and the extended maxima
% pixels are forced to be the only local minima in the image (So you could
% hypothetically fill in water on the image
D(C) = -Inf;
%Gets 0 for all watershed lines and integers for each object (basins)
L = watershed(D);
show(L,'L');
%Takes the labels and converts to an RGB (Using hot colormap)
fin = label2rgb(L,'hot','w');
% show(fin,'fin');
im = I;
%Superimpose ridgelines,L has all of them as 0 -> so mark these as 0(black)
im(L==0)=0;
clean_img = L;
show(clean_img)
After C = ~BW; the whole image goes dark. I believe this is because the image pixels are all -inf or some smaller negative number. This is there a way around this and if so what could I change in my code to get this algorithm working? I've experimented a ton and I don't really know what's happening. Any help would be great!
The problem is with your show command. As you said in the comments this uses imshow under the hood. If you try imshow directly you'll see you also get a black image. However, if you call it with appropriate limits:
imshow(clean_img,[min(clean_img(:)), max(clean_img(:))])
you'll see everything you expect to see.
In general I usually prefer imagesc for that reason. imshow makes arbitrary judgements as to what range to represent, and I usually can't be bothered to keep up with it. I think in your case, your end image is uint16 so imshow chooses to represent the range [1, 65025]. Since all your pixel values are below 400, they look black to the naked eye for that range.
I'm trying to measure the areas of each particle shown in this image:
I managed to get the general shape of each particle using MSER shown here:
but I'm having trouble removing the background. I tried using MATLAB's imfill, but it doesn't fill all the particles because some are cut off at the edges. Any tips on how to get rid of the background or find the areas of the particles some other way?
Cheers.
Edit: This is what imfill looks like:
Edit 2: Here is the code used to get the outline. I used this for the MSER.
%Compute region seeds and elliptial frames.
%MinDiversity = how similar to its parent MSER the region is
%MaxVariation = stability of the region
%BrightOnDark is used as the void is primarily dark. It also prevents dark
%patches in the void being detected.
[r,f] = vl_mser(I,'MinDiversity',0.7,...
'MaxVariation',0.2,...
'Delta',10,...
'BrightOnDark',1,'DarkOnBright',0) ;
%Plot region frames, but not used right now
%f = vl_ertr(f) ;
%vl_plotframe(f) ;
%Plot MSERs
M = zeros(size(I)) ; %M = no of overlapping extremal regions
for x=r'
s = vl_erfill(I,x) ;
M(s) = M(s) + 1;
end
%Display region boundaries
figure(1) ;
clf ; imagesc(I) ; hold on ; axis equal off; colormap gray ;
%Create contour plot using the values
%0:max(M(:))+.5 is the no of contour levels. Only level 0 is needed so
%[0 0] is used.
[c,h]=contour(M,[0 0]) ;;
set(h,'color','r','linewidth',1) ;
%Retrieve the image data from the contour image
f = getframe;
I2 = f.cdata;
%Convert the image into binary; the red outlines are while while the rest
%is black.
I2 = all(bsxfun(#eq,I2,reshape([255 0 0],[1 1 3])),3);
I2 = imcrop(I2,[20 1 395 343]);
imshow(~I2);
Proposed solution / trick and code
It seems you can work with M here. One trick that you can employ here would be to pad zeros all across the boundaries of the image M and then fill its holes. This would take care of filling the blobs that were touching the boundaries before, as now there won't be any blob touching the boundaries because of the zeros padding.
Thus, after you have M, you can add this code -
%// Get a binary version of M
M_bw = im2bw(M);
%// Pad zeros all across the grayscale image
padlen = 2; %// length of zeros padding
M_pad = padarray(M_bw,[padlen padlen],0);
%// Fill the holes
M_pad_filled = imfill(M_pad,'holes');
%// Get the background mask after the holes are gone
background_mask = ~M_pad_filled(padlen+1:end-padlen,padlen+1:end-padlen);
%// Overlay the background mask on the original image to show that you have
%// a working background mask for use
I(background_mask) = 0;
figure,imshow(I)
Results
Input image -
Foreground mask (this would be ~background_mask) -
Output image -