--------------------------------------------------------------------------------------------------------------------
附录A 教程【3】给出的matlab源码,附详细注释
function [ ] = MarkerControlled_Watershed_tutorial( )
%标记符控制的分水岭算法教程
%本程序按照官方文档的步骤,展示了如何使用分水岭算法,并测试对于含手势图片的分割结果是否理想
%程序修改自matlab官方文档 Marker-Controlled Watershed Segmentation
%网址: http://cn.mathworks.com/help/images/examples/marker-controlled-watershed-segmentation.html?s_tid=gn_loc_drop
%%
%官方文档简介
%这个例子展示了如何使用分水岭分割算法来分割一副图像中的物体
%分水岭变换经常应用到这类问题中。
%分水岭变换通过将灰度图像看成拓扑表面(亮的像素看做较高的点,暗像素看做低点),以此在一幅图中寻找“汇水盆地”和“分水岭脊线”
%如果你可以识别或者说标记mark前景物体与背景的位置(提供一些先验信息),使用分水岭变换的分割将会表现的很好。
%当然,人为提供这些标记“mark”,算法就算有效,很大程度上也失去了意义,于是就有了下面可以自行标记前景背景的分水岭算法。
%Marker-Controlled Watershed Segmentation,即标记符控制的分水岭分割算法,主要遵循以下几个基本步骤:
%1.计算一个分割函数(最基本的分水岭分割)。你尝试去分割的图像需要满足:黑暗区域对应着待分割的目标物体
%2.计算前景标记。得到的前景标记是每个物体内部的联通像素斑块。
%3.计算背景标记。背景标记由那些不属于任何物体的的像素点组成。
%4.修改分割函数,使得它只能在前景标记和背景标记的位置处达到最小值。
%5.计算修改后分割函数的分水岭变换
close all;
%%
%步骤一:读取彩色图像并转化为灰度图
HandImage = imread('testImgae/pear.jpg');
I = rgb2gray(HandImage);
imshow(I)
%%
%******************步骤二:使用梯度幅值作为分割函数**************************
%使用Sobel边缘检测算子,imfilter函数,和一些简单的四则运算来计算梯度幅值。
%梯度值总是在物体的边缘处高,而总是在物体内部低。
hy = fspecial('sobel');%获取sobel算子模板,计算纵向梯度
hx = hy';%横向模板
Iy = imfilter(double(I), hy, 'replicate');%计算纵向梯度
Ix = imfilter(double(I), hx, 'replicate');
gradmag = sqrt(Ix.^2 + Iy.^2);%计算梯度幅值
figure
imshow(gradmag,[]), title('Gradient magnitude (gradmag)')
%你能直接在梯度幅值矩阵上应用分水岭变换来分割图像吗?
L = watershed(gradmag);
Lrgb = label2rgb(L);
figure, imshow(Lrgb), title('Watershed transform of gradient magnitude (Lrgb)')
%从结果可以看到,显然不能
%如果没有额外的预处理,比如添加标记符(如下所述),直接使用分水岭变换通常导致严重的过分割
%取消下面3行代码的注释可以观察label2rgb函数的作用
% testA=[1 1 2 2 2 2 ;1 2 3 3 3 4;0 1 1 3 4 4];
% testArgb = label2rgb(testA);
% figure, imshow(testArgb), title('测试label2rgb函数的作用,使用简单的标记函数testA')
%%
%******************步骤三:标记前景物体************************************
%我们可以应用多种方法来获得前景标记,这些标记必须是前景对象内部的连通像素斑块。
%这个例子中,将使用形态学技术“基于重建的开操作”和“基于重建的闭操作”来清理图像。
%这些操作将会在每个对象内部创建平坦的极大值小斑块,这些斑块可以使用imregionalmax函数来定位。
%我们的目标是
%开运算和闭运算:先腐蚀后膨胀称为开;先膨胀后腐蚀称为闭(所谓腐蚀还是膨胀是针对白色而言来理解的)
%开和闭这两种运算可以除去比结构元素小的特定图像细节,同时保证不产生全局几何失真。
%开运算可以把比结构元素小的突刺滤掉,切断细长搭接而起到分离作用;闭运算可以把比结构元素小的缺口或孔填充上,搭接短的间隔而起到连接作用。
%但是
%开操作的第一步腐蚀能去除白色小物体,随后的膨胀趋向于恢复保留下来的物体的形状,这种恢复是不精确的,其精确度取决于形状和结构体之间的相似性。
%基于重建进行的开操作能够准确的恢复腐蚀之后的物体形状。
%所以基于重建的开操作与单纯的开操作功能类似,但更好地保留了原物体的形状。
%基于重建的开操作:I先腐蚀成Ie,后进行形态学重建。
%开操作是腐蚀后膨胀,基于重建的开操作是腐蚀后进行形态学重建。
%下面通过示例比较这两种方式,直观展示“基于重建的开操作”如何优于“开操作”
%首先,用imopen做开操作。
se = strel('disk',20);%disk指定构建一个圆形的结构体,第二个参数指定结构体的半径
Io = imopen(I, se);%开操作
% figure
%imshow(Io), title('Opening (Io)')
%接下来,进行基于重建的开操作。使用imerode和imreconstruct函数实现
Ie = imerode(I, se);%先腐蚀‘erosion’
Iobr = imreconstruct(Ie, I);%再重建
% figure
%imshow(Iobr), title('Opening-by-reconstruction (Iobr)')
%对开操作后的结果进行闭操作,可以移除较暗的斑点和枝干标记。
%同样的,我们来对比常规的形态学闭操作和基于重建的闭操作的区别。
%首先,使用imclose对开操作的结果进行常规闭操作:
Ioc = imclose(Io, se);
% figure
%imshow(Ioc), title('Opening-closing 开操作+闭操作 (Ioc)')
%然后我们来对开操作的结果进行基于重建的闭操作
%即首先使用imdilate函数进行膨胀,然后使用imreconstruct进行重建。
%这里要解释一下,重建操作是通过膨胀操作和交运算定义的,即 Hk+1=(Hk 膨胀 结构体B)∩G。
%重建定义的具体解释可以参考《数字图像处理的MATLAB实现(第2版)》P358页
%重建操作是对标记图像,在模板图像的限制下,进行膨胀。本质上是一种*膨胀*算法
%而这里需要的是模拟闭操作对膨胀后的图像进行*腐蚀*,怎么办呢?
%可以通过对模板图像和标记图像同时求补,然后进行重建操作,对补的膨胀相当于对原图的腐蚀。
%当然,重建的结果也要求补,才能得到实际的结果。
%IM2 = imcomplement(IM)计算图像IM的补集。IM可以是二值图像,或者RGB图像。IM2与IM有着相同的数据类型和大小。
Iobrd = imdilate(Iobr, se);%在基于重建的开操作的结果基础上,进行腐蚀
Iobrcbr = imreconstruct(imcomplement(Iobrd), imcomplement(Iobr));%重建,标记图像为腐蚀后图像取补,模板为腐蚀前原图取补。
Iobrcbr = imcomplement(Iobrcbr);%重建结果再取补,得到实际基于重建的闭操作的结果。
figure
imshow(Iobrcbr), title('Opening-closing by reconstruction 基于重建的开+闭操作 (Iobrcbr)')
%如你所见,通过比较Iobrcbr和loc可以看到,在移除小污点同时不影响对象全局形状的应用下,基于开闭操作的重建要比标准的开闭操作更加有效。
%计算Iobrcbr的区域极大值来得到好的前景标记。
%得到的前景标记图fgm是二值图,白色对应前景区域
fgm = imregionalmax(Iobrcbr);
figure
imshow(fgm), title('Regional maxima of opening-closing by reconstruction (fgm)')
%为了方便解释结果,将前景标记图叠加到原始图像上
I2 = I;
I2(fgm) = 255;%将fgm中的前景区域(像素值为1)标记到原图上(置白色)
figure
imshow(I2), title('Regional maxima superimposed on original image (I2)')
%注意到一些大部分重合或被阴影遮挡的物体没有被标记出来。这意味着这些物体最终可能不会被正确的分割出来。
%并且,有些物体中前景标记正确的到达了物体的边缘。这意味着你应该清除掉标记斑块的边缘,向内收缩一点。
%你可以通过先闭操作,再腐蚀做到这点。
se2 = strel(ones(5,5));
fgm2 = imclose(fgm, se2);
fgm3 = imerode(fgm2, se2);
%这个操作会导致遗留下一些离群的孤立点,这些是需要被移除的。
%你可以通过bwareaopen做到这点,函数将移除那些包含像素点个数少于指定值的区域。
fgm4 = bwareaopen(fgm3, 20);
I3 = I;
I3(fgm4) = 255;
figure
imshow(I3)
title('Modified regional maxima superimposed on original image (fgm4)')
%%
%*********************第四步:计算背景标记**********************************
%本例中设计出的标记背景算法的前提假设是:图像中相对亮的是物体,相对暗的区域是背景。
%如果不满足这条假设,标记结果可能不甚理想
%现在你需要标记背景。在去除噪点后的图像Iobrcbr中,暗像素属于背景,所以你可以先进行一下阈值操作。
%bw = imbinarize(Iobrcbr);
bw=im2bw(Iobrcbr,graythresh(Iobrcbr));%我使用上一行代码报错了,故换了一种二值化方法
figure
imshow(bw), title('Thresholded opening-closing by reconstruction (bw)')
%背景像素现在是黑的了,但是理想情况下我们不希望背景标记太接近我们想要分割的物体的边界。
%我们要使背景变瘦,通过计算“骨架影响范围”来“细化”背景,或者SKIZ,bw的前景。这句没翻通,原文如下
%We'll "thin" the background by computing the "skeleton by influence zones", or SKIZ, of the foreground of bw.
%这个可以通过对bw的距离变换进行分水岭变换来实现,然后寻找结果的分水岭脊线(DL==0)。
D = bwdist(bw);
%D = bwdist(BW)计算二值图像BW的欧几里得矩阵。对BW的每一个像素,距离变换指定像素和最近的BW非零像素的距离。
%bwdist默认使用欧几里得距离公式。BW可以由任意维数,D与BW有同样的大小。
%由于bw中目标物体是白色的1.所以D中对应的目标物体处均是0,随着进入背景越深,对应像素值越大。
%这时正好符合我们使用分水岭算法的假设(想分出的目标物体数值较低)
%于是得到的背景标记是 物体与背景间的一个圈,能够包住目标物体
DL = watershed(D);
bgm = DL == 0;%分水岭变换结果L中,同一区域用同一数字表示,区域间分界线同一由0标识
figure
imshow(bgm), title('Watershed ridge lines (bgm)')
%%
%******************第五步:计算分割函数(修改后)的分水岭变换*****************
% 函数imimposemin可以被用来修改一副图片,使得其只在指定的位置处取得局部最小值
% 这里你可以使用imimposemin来修改梯度幅值图像,使得局部最小值只出现在前景标记和背景标记处。
% 从结果来看imimposemin会将指定区域置为-Inf,从而成为极小值
% 且图像变得相当“平整”,一块块的相同数值的区域
gradmag2 = imimposemin(gradmag, bgm | fgm4);
%这段测试imimposemin都做了什么
% gradmag2;
% temp=gradmag2-gradmag;
%
% maxValue=max(max(temp));
% minValue=min(min(temp));
% temp=temp/maxValue;
% figure
% imshow(temp)
% maxValue=max(max(gradmag));
% temp_gradmag=gradmag/maxValue;
% figure
% imshow(temp_gradmag)
%
% maxValue=max(max(gradmag2));
% temp_gradmag2=gradmag2/maxValue;
% figure
% imshow(temp_gradmag2)
%Finally we are ready to compute the watershed-based segmentation.
L = watershed(gradmag2);
%%
%******************第六步:可视化结果************************************
I4 = I;
I4(imdilate(L == 0, ones(3, 3)) | bgm | fgm4) = 255;
figure
imshow(I4)
title('Markers and object boundaries superimposed on original image (I4)')
Lrgb = label2rgb(L, 'jet', 'w', 'shuffle');
figure
imshow(Lrgb)
title('Colored watershed label matrix (Lrgb)')
figure
imshow(I)
hold on
himage = imshow(Lrgb);
himage.AlphaData = 0.3;
title('Lrgb superimposed transparently on original image')
end
--------------------------------------------------------------------------------------------------------------------
附录B 提炼后的源码,去掉多余演示步骤,只可视化关键步骤
function [ ] = MarkerControlled_Watershed( )
%标记符控制的分水岭算法
%与教程函数不同,本函数没有多余的步骤和注释,直接实现
%展示的结果图更少更精炼
close all;
%%
%步骤一:读取彩色图像并转化为灰度图
HandImage = imread('testImgae/hand1.jpg');
I = rgb2gray(HandImage);
%如果原图中,目标物体是较暗的,即与假设相反,这里可以取反
% Fan=ones(size(I,1),size(I,2))*255;
% Fan=uint8(Fan);
% Fan=Fan-I;
% I=Fan;
imshow(I), title('原图I')
%%
%******************步骤二:基于重建的开闭操作************************************
se = strel('disk',20);%disk指定构建一个圆形的结构体,第二个参数指定结构体的半径
%接下来,进行基于重建的开操作。使用imerode和imreconstruct函数实现
Ie = imerode(I, se);%先腐蚀‘erosion’
Iobr = imreconstruct(Ie, I);%再重建
Iobrd = imdilate(Iobr, se);%在基于重建的开操作的结果基础上,进行腐蚀
Iobrcbr = imreconstruct(imcomplement(Iobrd), imcomplement(Iobr));%重建,标记图像为腐蚀后图像取补,模板为腐蚀前原图取补。
Iobrcbr = imcomplement(Iobrcbr);%重建结果再取补,得到实际基于重建的闭操作的结果。
figure
imshow(Iobrcbr), title('基于重建的开+闭操作 (Iobrcbr)')
%%
%******************步骤三:标记前景物体************************************
%计算Iobrcbr的区域极大值来得到好的前景标记。
%得到的前景标记图fgm是二值图,白色对应前景区域
fgm = imregionalmax(Iobrcbr);
%为了方便解释结果,将前景标记图叠加到原始图像上
I2 = I;
I2(fgm) = 255;%将fgm中的前景区域(像素值为1)标记到原图上(置白色)
%注意到一些大部分重合或被阴影遮挡的物体没有被标记出来。这意味着这些物体最终可能不会被正确的分割出来。
%并且,有些物体中前景标记正确的到达了物体的边缘。这意味着你应该清除掉标记斑块的边缘,向内收缩一点。
%你可以通过先闭操作,再腐蚀做到这点。
se2 = strel(ones(5,5));
fgm2 = imclose(fgm, se2);
fgm3 = imerode(fgm2, se2);
%这个操作会导致遗留下一些离群的孤立点,这些是需要被移除的。
%你可以通过bwareaopen做到这点,函数将移除那些包含像素点个数少于指定值的区域。
fgm4 = bwareaopen(fgm3, 20);
I3 = I;
I3(fgm4) = 255;
%%
%*********************第四步:计算背景标记**********************************
%本例中设计出的标记背景算法的前提假设是:图像中相对亮的是物体,相对暗的区域是背景。
%如果不满足这条假设,标记结果可能不甚理想
%现在你需要标记背景。在去除噪点后的图像Iobrcbr中,暗像素属于背景,所以你可以先进行一下阈值操作。
%bw = imbinarize(Iobrcbr);
bw=im2bw(Iobrcbr,graythresh(Iobrcbr));%我使用上一行代码报错了,故换了一种二值化方法
%背景像素现在是黑的了,但是理想情况下我们不希望背景标记太接近我们想要分割的物体的边界。
D = bwdist(bw);
%D = bwdist(BW)计算二值图像BW的欧几里得矩阵。对BW的每一个像素,距离变换指定像素和最近的BW非零像素的距离。
%bwdist默认使用欧几里得距离公式。BW可以由任意维数,D与BW有同样的大小。
%由于bw中目标物体是白色的1.所以D中对应的目标物体处均是0,随着进入背景越深,对应像素值越大。
%这时正好符合我们使用分水岭算法的假设(想分出的目标物体数值较低)
%于是得到的背景标记是 物体与背景间的一个圈,能够包住目标物体
DL = watershed(D);
bgm = DL == 0;%分水岭变换结果L中,同一区域用同一数字表示,区域间分界线同一由0标识
%%
%******************第五步:计算分割函数(修改后)的分水岭变换*****************
% 函数imimposemin可以被用来修改一副图片,使得其只在指定的位置处取得局部最小值
% 这里你可以使用imimposemin来修改梯度幅值图像,使得局部最小值只出现在前景标记和背景标记处。
% 从结果来看imimposemin会将指定区域置为-Inf,从而成为极小值
% 且图像变得相当“平整”,一块块的相同数值的区域
%使用Sobel边缘检测算子,imfilter函数,和一些简单的四则运算来计算梯度幅值。
%梯度值总是在物体的边缘处高,而总是在物体内部低。
hy = fspecial('sobel');%获取sobel算子模板,计算纵向梯度
hx = hy';%横向模板
Iy = imfilter(double(I), hy, 'replicate');%计算纵向梯度
Ix = imfilter(double(I), hx, 'replicate');
gradmag = sqrt(Ix.^2 + Iy.^2);%计算梯度幅值
gradmag2 = imimposemin(gradmag, bgm | fgm4);
%Finally we are ready to compute the watershed-based segmentation.
L = watershed(gradmag2);
%%
%******************第六步:可视化结果************************************
I4 = I;
I4(imdilate(L == 0, ones(3, 3)) | bgm | fgm4) = 255;
figure
imshow(I4)
title('在原图上绘制前景、背景标记,以及分割边界')
Lrgb = label2rgb(L, 'jet', 'w', 'shuffle');
figure
imshow(Lrgb)
title('Colored watershed label matrix (Lrgb)')
figure
imshow(I)
hold on
himage = imshow(Lrgb);
himage.AlphaData = 0.3;
title('Lrgb superimposed transparently on original image')
end