本文主要记录基于形态学分析提取图像中木塞个数及木塞的直径的解决方法。本方法同样适用于其他图像中圆物体的统计计数、标记位置、计算直径等。


1. Problem

  Write program to automatic count the number of wood dowel plugs and calculate their diameters.

图 1.1. 测试图像

2. Solutions

简要概述下处理的过程:

  1. 读取图像并转换为灰度图;
  2. 图像转换为二值图;
  3. 使用开闭运算消除噪声;填充孔洞(初步分割已完成);
  4. 对初步分割的结果进行标记,将最小值强加到指定位置(避免分水岭变换过度分割);
  5. 使用分水岭变换将未分割开来的图像进行分割;
  6. 统计、标记位置及半径。

2.1 图像转换

  以下是图像预处理代码:

I=imread('test.tif')            % 读入图像
f=imbinarize(I);                % 转换为二值图像

结果如下图所示:

图 2.1. 二值图像

2.2 开闭运算、填充孔洞

  代码如下:

se = strel('square',3);         % 结构元素
f = imopen(f,se);               % 开运算
f = imclose(f,se);              % 闭运算
f = imfill(f,'holes');          % 填充孔洞

结果如下图所示:

图 2.2: 开闭运算结果 图 2.3:填充孔洞结果

  由图 2.3可以看出,图中一些木塞并没有分割开来,此时进行统计显然不能得到好的结果,如图 2.4 所示。

图 2.4:分割后统计结果

2.3 分水岭变换

  对初步分割的结果进行标记,将最小值强加到指定位置(避免分水岭变换过度分割),之后再利用分水岭变换分割。代码如下:

F3=-bwdist(~f);          % 积水盆地图
mask=imextendedmin(F3,2);% 使用imextendedmin滤出微小的局部最小值
figure;imshowpair(f,mask,'blend');  % mask与f混合显示
D2=imimposemin(F3,mask); % 修改距离变换,使其仅在所需位置具有最小值
Posi=watershed(D2);      % 分水岭位置
f(Posi==0)=0;            % 将初步结果未分割开处置0使其分割开来

  局部标记与初步分割结果混合显示图及最终分割结果如下:

图 2.5: 混合显示结果 图 2.6:最终分割结果

对比图 2.3与图 2.6可以看出原来没有分割开的已经被很好的分开。

2.4 统计标记

L = bwlabel(f);             % 二进制图像中标记连接的组件
figure;imshow(I);hold on;   % 原始图像
% 统计测量图像区域的属性:质心、椭圆长短轴
stats = regionprops('table',L,'Centroid','MajorAxisLength','MinorAxisLength');
% 圆心位置及相应半径
centers=stats.Centroid; 
diameters=mean([stats.MajorAxisLength stats.MinorAxisLength],2);
radii=diameters/2;
% 绘制木塞轮廓
viscircles(centers,radii,'Color','r');
% 标记直径
for i=1:size(centers,1)
    text(centers(i,1)-min(radii),centers(i,2),sprintf('%.2f',diameters(i)),'Color','b');
end
% 标记个数
text(30,30,['Number=',num2str(length(radii))],'Color','r'); 

原始图片和标记结果对比展示如下:

图 2.7:统计标记结果

3. 参考文献