标记符控制的分水岭算法原理及matlab实现

2024-06-12 14:48

本文主要是介绍标记符控制的分水岭算法原理及matlab实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

--------------------------------------------------------------------------------------------------------------------

附录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


这篇关于标记符控制的分水岭算法原理及matlab实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1054524

相关文章

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

java如何分布式锁实现和选型

《java如何分布式锁实现和选型》文章介绍了分布式锁的重要性以及在分布式系统中常见的问题和需求,它详细阐述了如何使用分布式锁来确保数据的一致性和系统的高可用性,文章还提供了基于数据库、Redis和Zo... 目录引言:分布式锁的重要性与分布式系统中的常见问题和需求分布式锁的重要性分布式系统中常见的问题和需求

SpringBoot基于MyBatis-Plus实现Lambda Query查询的示例代码

《SpringBoot基于MyBatis-Plus实现LambdaQuery查询的示例代码》MyBatis-Plus是MyBatis的增强工具,简化了数据库操作,并提高了开发效率,它提供了多种查询方... 目录引言基础环境配置依赖配置(Maven)application.yml 配置表结构设计demo_st

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧

Python pyinstaller实现图形化打包工具

《Pythonpyinstaller实现图形化打包工具》:本文主要介绍一个使用PythonPYQT5制作的关于pyinstaller打包工具,代替传统的cmd黑窗口模式打包页面,实现更快捷方便的... 目录1.简介2.运行效果3.相关源码1.简介一个使用python PYQT5制作的关于pyinstall

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小

python实现自动登录12306自动抢票功能

《python实现自动登录12306自动抢票功能》随着互联网技术的发展,越来越多的人选择通过网络平台购票,特别是在中国,12306作为官方火车票预订平台,承担了巨大的访问量,对于热门线路或者节假日出行... 目录一、遇到的问题?二、改进三、进阶–展望总结一、遇到的问题?1.url-正确的表头:就是首先ur

C#实现文件读写到SQLite数据库

《C#实现文件读写到SQLite数据库》这篇文章主要为大家详细介绍了使用C#将文件读写到SQLite数据库的几种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录1. 使用 BLOB 存储文件2. 存储文件路径3. 分块存储文件《文件读写到SQLite数据库China编程的方法》博客中,介绍了文