top of page

QA0003

MATLAB script with notes in HTML format:

Small tag OK.jpg
Q: How to detect tinted cells in input image, and then calculate each perimeter and centroid of detected cell.

 

A:

Part I - Prepare input image  ............................................................................................... 1

Part II - Attempt with kmeans  ............................................................................................ 9

Part III - bwboundaries  ........................................................................................................ 11

       Image 'clean-up' tools that may be useful  ............................................................ 13

Part IV Centroids  ................................................................................................................. 21

        Problem with call to regionprops to get area of each cell  .............................. 21

Part V suggested further work  ........................................................................................ 24

Part VI Area  .......................................................................................................................... 26

% catch_tilted_cells.m
%
% short description: % image segmentation: catch high pigmentation cells with
% histogram and bwboundaries, without kmeans.

% Script Author: John Bofarull Guix, jgb2012@sky.com
%
%
% Given the input image supplied by the question originator, this script obtains
% coordinates of only the cells of interest tilted in saturated magenta, draws
% perimeter, and calculates centroids, while applying a numeral to each cell
%
% The area of each cell may also be of interest, yet it has not been asked.
% The question originator repeatedly mentioned kmeans, but the standard kmeans
% requires of knowledge, if only approximate, of the amount of clusters, otherwise
% in a case like the input image, the amount of sought cells my range from non, just
% a few, or perhaps several hundreds, or even higher orders of magnitude.
%
% applying Mathworks examples:
%   1.- Detecting a Cell Using Image Segmentation
%   2.- https://uk.mathworks.com/help/images/identifying-round-objects.html


close all;clear all;clc

 

Part I - prepare input image

display and measure input image

A=imread('001.jpg');
hf1=figure(1);imshow(A);ax1=gca

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Ar=A(:,:,1);Ag=A(:,:,2);Ab=A(:,:,3);
[sz1,sz2]=size(Ar);

Warning: Image is too big to fit on screen; displaying at
50%
ax1 =
  Axes with properties:

            XLim: [0.500000000000000 1.712500000000000e+03]
            YLim: [0.500000000000000 1.368500000000000e+03]
          XScale: 'linear'
          YScale: 'linear'
   GridLineStyle: '-'
        Position: [1×4 double]
           Units: 'normalized'

  Use GET to show all properties

 

command imsegkmeans is probably what you are looking for, but you need to have the upgraded image processing toolbox, that comes with MATLAB version R2019. Try installing MATLAB R2019 student version and then use imsegkmeans. imsegkmeans is not a public function.

B=rgb2lab(A); figure;imshow(B); B2=B(:,:,2:3); B2 = im2single(B2); nColors = 3;

% repeat the clustering 3 times to avoid local minima

pixel_labels = imsegkmeans(B2,nColors,'NumAttempts',3);

the cells of interest are all distinctively marked with saturated magenta colour. So up for magenta that in this picture all magenta pixels have approximately proportion value around [.5 0 1]

Amag=Ar+Ab;

measuring range of magenta pixels

maxAmag=double(max(max(Amag)))
minAmag=double(min(min(Amag)))


% Amag2=255/maxAmag*(Amag-minAmag); % manual adjustment, not needed
% Amag1=double(Amag(:,:,1));

maxAmag =
  255
minAmag =
   89

the cells of interest are holes, let's turn them into peaks: inverting pixel values

Amag2=double(maxAmag-Amag);
maxAmag2=max(max(Amag2))
minAmag2=min(min(Amag2))

maxAmag2 =
  166
minAmag2 =
    0

stretching image to use all available range [0 255]

Amag3=uint8(255/maxAmag2*Amag2);
hf3=figure(2);imshow(Amag3);ax2=gca;

Warning: Image is too big to fit on screen; displaying at
50%

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

One can manually adjust contrast: the output has to be saved through the File menu of the figure window,

figure(3);imcontrast(hf3)

% or input grey scale thresholds directly into imadjust.
A4=imadjust(Amag3,[.1921 ; .2274],[]);
figure(4);imshow(A4);ax4=gca

Warning: Image is too big to fit on screen; displaying at
50%
ax4 =
  Axes with properties:

            XLim: [0.500000000000000 1.712500000000000e+03]
            YLim: [0.500000000000000 1.368500000000000e+03]
          XScale: 'linear'
          YScale: 'linear'
   GridLineStyle: '-'
        Position: [1×4 double]
           Units: 'normalized'

  Use GET to show all properties

 

 

 

 

 

 

 

 

2D Laplacian

LA1=del2(double(A4));
% LA1=del2(double(A4(:,:,1)));LA2=del2(double(A4(:,:,2)));LA3=del2(double(A4(:,:,3)));
figure(5);imshow(LA1)

[szla1 szla2]=size(LA1)

min(min(LA1))
max(max(LA1))

LA1(LA1<0)=0;
LA1(LA1>=1)=1;

figure(6);imshow(LA1);ax6=gca  
% check it looks the same

min(min(LA1))   % now LA1 pixels only  range between [0 1]
max(max(LA1))

n1=find(LA1==1);
[nx ny]=ind2sub([szla1 szla2],n1);


% therefore I am not using del2 again, but [nx ny] are use in the first histogram
% just as alternative to kmeans without a reasonable guess or wherabouts of the
% amount of cells to catch, which is precisely part of the sought answer.

Warning: Image is too big to fit on screen; displaying at
50%
szla1 =
       1368
szla2 =
       1712
ans =
  -255
ans =
  255
Warning: Image is too big to fit on screen; displaying at
50%
ax6 =
  Axes with properties:

            XLim: [0.500000000000000 1.712500000000000e+03]
            YLim: [0.500000000000000 1.368500000000000e+03]
          XScale: 'linear'
          YScale: 'linear'
   GridLineStyle: '-'
        Position: [1×4 double]
           Units: 'normalized'

  Use GET to show all properties
ans =
    0
ans =
    1

 

 

Part II - attempt with kmeans

Checking the obtained pixel locations [nx ny] correspond to cell skins once you know the coordinates of the cell skins you can calculate each cell centroid.

hold(ax6,'on')
plot(ax6,ny,nx,'ro')


% note that to correctly plot ny goes ahead of nx

% lining up point coordinates
X=[nx ny];

% Let's assume that there may be around, approximately, 30 cells
[idx,sumd,CD]=kmeans(X,3);

% idx vector contains predicted clustersr

x1 = min(X(:,1)):0.01:max(X(:,1));
x2 = min(X(:,2)):0.01:max(X(:,2));

% [x1G,x2G] = meshgrid(x1,x2);
% XGrid = [x1G(:),x2G(:)]; % Defines a fine grid on the plot

% problem:
%  idx2Region = kmeans(XGrid,3,'MaxIter',1,'Start',CD);
   % While attempting to assign each node in the grid to the closest centroid
   % kmeans call the output of meshgrid, that for exercise is already of
   % size above 1x150000. This itself is not a problem, but since meshgrid
   % calls repmat, such large values for a student version saturates memory
   % forcin the following error:
%     Error using repmat
% Requested 170401x136701 (173.6GB) array
% exceeds maximum array size preference.
% Creation of arrays greater than this limit
% may take a long time and cause MATLAB to
% become unresponsive. See array size limit
% or preference panel for more information.
% Error in meshgrid (line 58)
%         xx = repmat(xrow,size(ycol));
%
% and I am mentioning again, the second field input to kmeans is a guess
% of the amount of cells to look for. One can interate kmeans to sweep that guess
% and then have the user inspecting for coherence, but in my opinion,
% perhaps there's a combination of input parameters that simplify such obstacle,
% but on 1st approach, kmeans is not as robust as simply processing the boundaries
% obtained with bwboundaries.

% for instance, let's run 2 histograms, one for each x y boundary coordinates
% nx ny have been obtained with del2, but are quite the same points that are going
% to be obtained in next point with bwboundaries.



figure(7);histogram(nx,floor(numel(nx)/12))
figure(8);histogram(ny,floor(numel(ny)/12))


% histogram too wide to catch the cells of interest, yet one can readily observe that
% there's correlation between histogram peaks and amount of cells, as well as cell locations.

 

 

 

Part III - bwboundaries

back to A4 using command bwboundaries obtains the coordinates of cell skins in variable B.

[B,L] = bwboundaries(A4,'noholes');
hold(ax4,'on')
for k = 1:length(B)
  boundary = B{k};
  plot(ax4,boundary(:,2),boundary(:,1),'r','LineWidth',2)
end

L1=[]
for k=1:1:size(B,1)
   L1=[L1 size(B{k},1)];
end

length(L1) 
% 577 way too many too small cells taken as valid cells.

figure(9);histogram(L1,length(L1));grid on
title('histogram red tag cells boundary lengths')


% histogram seems apparently void above bin 50, but in fact the cells of interest
% created amplitude 1 peaks, one needs to zoom in, at the exact.

L1 =
    []
ans =
  577

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Image 'clean-up' tools that may be useful

one may or may not use the following, but I mention it because the input image is usually unknown a these corrections, along many others mentioned in the Image Processing MATLAB online and local help pages

Inflating white pixels with strel line.

npx=5
se90 = strel('line', npx, 90);
se0 = strel('line', npx, 0);
r0=4
ser=strel('disk',r0)
A5 = imdilate(A4, [se90 se0]);
figure(10);imshow(A5), title('filling holes wth dilated gradient mask');

npx =
    5
r0 =
    4
ser =
strel is a disk shaped structuring element with properties:

     Neighborhood: [7×7 logical]
   Dimensionality: 2
Warning: Image is too big to fit on screen; displaying at
50%

 

Attempting a bit of fill-up before applying bwboundaries, now strel diamond.

for k=1:1:10
seD = strel('diamond',1);
A6 = imerode(A5,seD);
A6 = imerode(A6,seD);
end
figure(11);imshow(A6); title('filing edgees');

A62 = imclearborder(A6, 4);
figure(12); imshow(A62); ax12=gca; title('cleared border image');

Warning: Image is too big to fit on screen; displaying at
50%
Warning: Image is too big to fit on screen; displaying at
50%

 

manually removing 'small' false cells and counting cells of interest with histogram.

again detecting boundaries

[B2,L2] = bwboundaries(A62,'noholes');
hold(ax12,'on')
for k = 1:length(B2)
  boundary2 = B2{k};
  plot(ax12,boundary2(:,2),boundary2(:,1),'r','LineWidth',2)
end

L2=[];
for k=1:1:size(B2,1)
   L2=[L2 size(B2{k},1)];
end

length(L2)  


% apparently there are 119 cells, but the majority of these hits are too small to be
% considered cells, let's eliminate all too-small false cells.
%

% no cell has as many as 1050 pixels along respective perimeter.
% All cell perimeters are shorter than 1050, so setting histogram bin width to 1
% and the amount of histogram bins to 1050.

upper_limit_cell_perimeter=1050

figure(14);hst1=histogram(L2,upper_limit_cell_perimeter)


% central value of each bin
nL0=ceil(cumsum(diff(hst1.BinEdges)));

% histogram count for each bin


L0=hst1.Values;

% Now with figure marker manually measure diameter of any red contour cell.
% Its about 110 pixels so any cell perimeter is about 220 pixels long, roughly.


lower_limit_cell_perimeter=150

% To find out how many counts above lower_limit_cell_perimeter
% one could use
% [vh,nh]=find(hst1.BinEdges>150)
%
% but because histogram bins are arranged as consecutive increasing numerals, then
% instead one can use


nL_1=nL0(nL0>lower_limit_cell_perimeter);

% how many cells for each possible perimeter length
L_1=L0(nL_1);
L_1(L_1>0);


% it turns out that there's only one cell for each perimeter value, but it could be
% the case that many cells have exactly same perimeter length

% perimeter lengths are

P=nonzeros(nL_1.*L_1)

% As mentioned, if one or more bins above lower_limit_cell_perimeter show values >1
% then use the following for loop to obtain in one variable all perimeter lengths from cells of interest

P1=[]
for k=1:1:length(L_1)
      P1=[P1;repmat(nL_1(k),L_1(k),1)];
end
P1==P

LB=[]
for k=1:1:length(B)
   L21=length(B{k});
   LB=[LB length(B{k})];
end


% indices pointing at those boundaries in B longer than lower_limit_cell_perimeter
nLB=find(LB>lower_limit_cell_perimeter)

% B2 contains the coordinates of long boundaries
B3={}
for k=1:1:numel(nLB)
   B3=[B3 B{nLB(k)}];
end


% copying boundaries B3 on high contrast image
hold(ax2,'on')
for k = 1:length(B3)
  boundary3 = B3{k};
  plot(ax2,boundary3(:,2),boundary3(:,1),'r','LineWidth',5)
end


% copying boundaries B3 of intereset on input signal
hf10=figure(10);imshow(A);ax10=gca
hold(ax10,'on')
for k = 1:length(B3)
  boundary3 = B3{k};
  plot(ax10,boundary3(:,2),boundary3(:,1),'r','LineWidth',5)
end

ans =
  119
upper_limit_cell_perimeter =
       1050
hst1 =
  Histogram with properties:

            Data: [1×119 double]
          Values: [1×1050 double]
         NumBins: 1050
        BinEdges: [1×1051 double]
        BinWidth: 0.975500000000000
       BinLimits: [1.800000000000000 1.026075000000000e+03]
   Normalization: 'count'
       FaceColor: 'auto'
       EdgeColor: [0 0 0]

  Use GET to show all properties
lower_limit_cell_perimeter =
  150
P =
  162
  309
  322
  368
  495
  630
  641
  702
P1 =
    []
ans =
  8×1 logical array
  1
  1
  1
  1
  1
  1
  1
  1
LB =
    []
nLB =
   21   113   116   126   151   217   270   303   379   468
B3 =
  0×0 empty cell array
Warning: Image is too big to fit on screen; displaying at
50%
ax10 =
  Axes with properties:

            XLim: [0.500000000000000 1.712500000000000e+03]
            YLim: [0.500000000000000 1.368500000000000e+03]
          XScale: 'linear'
          YScale: 'linear'
   GridLineStyle: '-'
        Position: [1×4 double]
           Units: 'normalized'

  Use GET to show all properties

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Part IV - Centroids

 

There's a problem when calling regionprops to get cell areas

either

stats = regionprops(L2,'Area','Centroid')
% or
% stats = regionprops(L2,'Centroid')

length(stats)

% regionprops is applied to the image before using histogram to exactly obtain the
% cells of interest. Either clean enough image has been produced to then apply
% regionprops, or in this exerrcise, once the clean is obtained, using regionprops
% would force to rebuild an image with solid cells and then apply regionprops
% to avoid excessive regrowth when applying regionprops, that in turn applies
% bwboundary, on the already obtained boundaries,


hf11=figure(11);imshow(A);ax11=gca
hold(ax11,'on')

for k = 1:length(B3)

  boundary = B3{k};
  delta_sq = diff(boundary).^2;                              
% cell k simplified perimeter length in pixels
 p4 = sum(sqrt(sum(delta_sq,2)));

  area = stats(k).Area;                                          
% cell k area of cell k

  metric = 4*pi*area/p4^2;                        % cell k roundness

  metric_string = sprintf('%2.2f',metric);                 % display results in command window.

%   if metric > threshold                                            % mark objects above the threshold with red circle
    centroid = stats(k).Centroid;
   plot(ax11,centroid(1),centroid(2),'ro');

%   end

  text(ax11,boundary(1,2)-35,boundary(1,1)+13,metric_string,'Color','y','FontSize',14,'FontWeight','bold')

end


% therefore the figures produced belong to one of the many too small cells, the
% numbers of interest being cluttered.
% In any case there's no need to sweep through all cells in the half way image
% with many 'too small' cells when one aready has B3.

stats =
  1026×1 struct array with fields:
   Area
   Centroid
ans =
       1026
Warning: Image is too big to fit on screen; displaying at
50%
ax11 =
  Axes with properties:

            XLim: [0.500000000000000 1.712500000000000e+03]
            YLim: [0.500000000000000 1.368500000000000e+03]
          XScale: 'linear'
          YScale: 'linear'
   GridLineStyle: '-'
        Position: [1×4 double]
           Units: 'normalized'

use GET to show all properties

 

fixing meaningless result when attempting to apply last for loop of example

https://uk.mathworks.com/help/images/identifying-round-objects.html

% and bwarea cannot be applied without again spending time building solid cells,
% that for the case, such point has not been asked, therefore the area calculation is omitted.


% close(hf1)

hf20=figure(20);imshow(A);ax20=gca
hold(ax20,'on')

for k = 1:length(B3)
  boundary3 = B3{k};
  plot(ax20,boundary3(:,2),boundary3(:,1),'r','LineWidth',5)
end

for k = 1:length(B3)

  boundary = B3{k};
  centroid_1=1/size(boundary,1)*sum(boundary(:,1));
  centroid_2=1/size(boundary,1)*sum(boundary(:,2));
  centroid=floor([centroid_1 centroid_2])

  plot(ax20,centroid(2),centroid(1),'+y','MarkerSize',31);

  text(ax20,centroid(2)-35,boundary(1)-35,num2str(k),'Color','g','FontSize',14,'FontWeight','bold')

end

Warning: Image is too big to fit on screen; displaying at
50%
ax20 =
  Axes with properties:

            XLim: [0.500000000000000 1.712500000000000e+03]
            YLim: [0.500000000000000 1.368500000000000e+03]
          XScale: 'linear'
          YScale: 'linear'
   GridLineStyle: '-'
        Position: [1×4 double]
           Units: 'normalized'

  Use GET to show all properties
centroid =
  746   154
centroid =
  215   719
centroid =
  544   838
centroid =
   89   904
centroid =
  674   967
centroid =
        629        1064
centroid =
        832        1224
centroid =
        322        1410
centroid =
       1080        1581
centroid =
        550        1649

 

Part V - suggested further work

6.1.- cells 5 and 10 are obviously 2 cells each. It's just that both have isthmi broad enough to confuse bwboundaries.

% bare
% [centers, radii, metric] = imfindcircles(A4,[90 140])
% doesn't catch anything no matter what radii values input.

% but setting object polarity to dark, the detection of all round
% heads is immediate


Rmin=40;Rmax=150
[centersBright, radiiBright] = imfindcircles(A,[Rmin Rmax],'ObjectPolarity','bright');
[centersDark, radiiDark] = imfindcircles(A,[Rmin Rmax],'ObjectPolarity','dark');

hf40=figure(40);imshow(A);ax40=gca
hold(ax40,'on')
viscircles(centersDark, radiiDark,'EdgeColor','y');


% no attempt will be made to catch the cells that do not look like circles with
% imfindcircles.

Rmax =
  150
Warning: You just called IMFINDCIRCLES with a large
radius range. Large radius ranges reduce algorithm
accuracy and increase computational time. For high
accuracy, relatively small radius range should be used. A
good rule of thumb is to choose the radius range such
that Rmax < 3*Rmin and (Rmax - Rmin) < 100. If you have a
large radius range, say [20 100], consider breaking it up
into multiple sets and call IMFINDCIRCLES for each set
separately, like this:

      [CENTERS1, RADII1, METRIC1] = IMFINDCIRCLES(A, [20    60]);
      [CENTERS2, RADII2, METRIC2] = IMFINDCIRCLES(A, [61    100]);
 
Warning: You just called IMFINDCIRCLES with a large
radius range. Large radius ranges reduce algorithm
accuracy and increase computational time. For high
accuracy, relatively small radius range should be used. A
good rule of thumb is to choose the radius range such
that Rmax < 3*Rmin and (Rmax - Rmin) < 100. If you have a
large radius range, say [20 100], consider breaking it up
into multiple sets and call IMFINDCIRCLES for each set
separately, like this:

      [CENTERS1, RADII1, METRIC1] = IMFINDCIRCLES(A, [20    60]);
      [CENTERS2, RADII2, METRIC2] = IMFINDCIRCLES(A, [61    100]);
 
Warning: Image is too big to fit on screen; displaying at
50%


ax40 =
  Axes with properties:

            XLim: [0.500000000000000 1.712500000000000e+03]
            YLim: [0.500000000000000 1.368500000000000e+03]
          XScale: 'linear'
          YScale: 'linear'
   GridLineStyle: '-'
        Position: [1×4 double]
           Units: 'normalized'

  Use GET to show all properties

 

Catching the roundheads:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Part VI - Area

if the question originator adds this point to the question I will supply area calculations for each cell.

Published with MATLAB® R2017a

catch_tilted_cells_01.png
catch_tilted_cells_06.png
catch_tilted_cells_02.png
catch_tilted_cells_17.png
catch_tilted_cells_22.png
catch_tilted_cells_23.png
Small tag OK.jpg

                       notes:

bottom of page