图像处理——分水岭算法

图像处理——分水岭算法首先感谢以下两位的博文帮助我的理解:(1)迈克老狼2012  https://www.cnblogs.com/mikewolf2002/p/3304118.html(2)-牧野-       http://blog.csdn.net/dcrmg/article/details/52498440分水岭算法是一种图像区域分割法,在分割的过程中

大家好,又见面了,我是你们的朋友全栈君。

首先感谢以下两位的博文帮助我的理解:

(1)迈克老狼2012   https://www.cnblogs.com/mikewolf2002/p/3304118.html

(2)-牧野-              http://blog.csdn.net/dcrmg/article/details/52498440


分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近(求梯度)的像素点互相连接起来构成一个封闭的轮廓。分水岭算法常用的操作步骤:彩色图像灰度化,然后再求梯度图,最后在梯度图的基础上进行分水岭算法,求得分段图像的边缘线。

        下面左边的灰度图,可以描述为右边的地形图,地形的高度是由灰度图的灰度值决定,灰度为0对应地形图的地面,灰度值最大的像素对应地形图的最高点。

ima1 (2)ima2

我们可以自己编程实现灰度图的地形图显示,工程FirstOpenCV6就实现了简单的这个功能,比如上边的灰度图,显示为:

image

对灰度图的地形学解释,我们我们考虑三类点

1. 局部最小值点,该点对应一个盆地的最低点,当我们在盆地里滴一滴水的时候,由于重力作用,水最终会汇聚到该点。注意:可能存在一个最小值面,该平面内的都是最小值点。

2. 盆地的其它位置点,该位置滴的水滴会汇聚到局部最小点。

3. 盆地的边缘点,是该盆地和其它盆地交接点,在该点滴一滴水,会等概率的流向任何一个盆地。

image

       假设我们在盆地的最小值点,打一个洞,然后往盆地里面注水,并阻止两个盆地的水汇集,我们会在两个盆地的水汇集的时刻,在交接的边缘线上(也即分水岭线),建一个坝,来阻止两个盆地的水汇集成一片水域。这样图像就被分成2个像素集,一个是注水盆地像素集,一个是分水岭线像素集。

      下面的gif图很好的演示了分水岭算法的效果:

lpe1 (1)ima3 (1)

     

     在真实图像中,由于噪声点或者其它干扰因素的存在,使用分水岭算法常常存在过度分割的现象,这是因为很多很小的局部极值点的存在,比如下面的图像,这样的分割效果是毫无用处的。

ima7ima7b

      为了解决过度分割的问题,可以使用基于标记(mark)图像的分水岭算法,就是通过先验知识,来指导分水岭算法,以便获得更好的图像分段效果。通常的mark图像,都是在某个区域定义了一些灰度层级,在这个区域的洪水淹没过程中,水平面都是从定义的高度开始的,这样可以避免一些很小的噪声极值区域的分割。

      下面的gif图很好的演示了基于mark的分水岭算法过程:

ima4lpe2ima5

      上面的过度分段图像,我们通过指定mark区域,可以得到很好的分段效果:

 

ima8ima9

Opencv 中 watershed函数原型:

[cpp] 
view plain  
copy

 
print
?

  1. void watershed( InputArray image, InputOutputArray markers );  


第一个参数 image,必须是一个8bit 3通道彩色图像矩阵序列,第一个参数没什么要说的。关键是第二个参数 markers,Opencv官方文档的说明如下:

Before passing the image to the function, you have to roughly outline the desired regions in the image markers with positive (>0) indices. So, every region is represented as one or more connected components with the pixel values 1, 2, 3, and so on. Such markers can be retrieved from a binary mask using findContours() and drawContours(). The markers are “seeds” of the future image regions. All the other pixels in markers , whose relation to the outlined regions is not known and should be defined by the algorithm, should be set to 0’s. In the function output, each pixel in markers is set to a value of the “seed” components or to -1 at boundaries between the regions.


就不一句一句翻译了,大意说的是在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。

接下来执行分水岭会发生什么呢?算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。

简单概括一下就是说第二个入参markers必须包含了种子点信息。Opencv官方例程中使用鼠标划线标记,其实就是在定义种子,只不过需要手动操作,而使用findContours可以自动标记种子点。而分水岭方法完成之后并不会直接生成分割后的图像,还需要进一步的显示处理,如此看来,只有两个参数的watershed其实并不简单。


下边通过图示来看一下watershed函数的第二个参数markers在算法执行前后发生了什么变化。对于一个原图:


图像处理——分水岭算法


经过灰度化、滤波、Canny边缘检测、findContours轮廓查找、轮廓绘制等步骤后终于得到了符合Opencv要求的merkers,我们把merkers转换成8bit单通道灰度图看看它里边到底是什么内容:


这个是分水岭运算前的merkers:

图像处理——分水岭算法


这个是findContours检测到的轮廓:

图像处理——分水岭算法


看效果,基本上跟图像的轮廓是一样的,也是简单的勾勒出了物体的外形。但如果仔细观察就能发现,图像上不同线条的灰度值是不同的,底部略暗,越往上灰度越高。由于这幅图像边缘比较少,对比不是很明显,再来看一幅轮廓数量较多的图效果:


这个是分水岭运算前的merkers:

图像处理——分水岭算法


这个是findContours检测到的轮廓:

图像处理——分水岭算法


从这两幅图对比可以很明显看到,从图像底部往上,线条的灰度值是越来越高的,并且merkers图像底部部分线条的灰度值由于太低,已经观察不到了相互连接在一起的线条灰度值是一样的,这些线条和不同的灰度值又能说明什么呢?

答案是:每一个线条代表了一个种子,线条的不同灰度值其实代表了对不同注水种子的编号,有多少不同灰度值的线条,就有多少个种子,图像最后分割后就有多少个区域。



再来看一下执行完分水岭方法之后merkers里边的内容发生了什么变化:


图像处理——分水岭算法


可以看到,执行完watershed之后,merkers里边被分割出来的区域已经非常明显了,空间上临近并且灰度值上相近的区域被划分为一个区域,灰度值是一样,不同区域间被划分开,这其实就是分水岭对图像的分割效果了。


总的概括一下watershed图像自动分割的实现步骤:

1. 图像灰度化、滤波、Canny边缘检测

2. 查找轮廓,并且把轮廓信息按照不同的编号绘制到watershed的第二个入参merkers上,相当于标记注水点。

3. watershed分水岭运算

4. 绘制分割出来的区域,视觉控还可以使用随机颜色填充,或者跟原始图像融合以下,以得到更好的显示效果。


以下是Opencv分水岭算法watershed实现的完整过程:


[cpp] 
view plain  
copy

 
print
?

  1. #include “opencv2/imgproc/imgproc.hpp”  
  2. #include “opencv2/highgui/highgui.hpp”  
  3.   
  4. #include <iostream>  
  5.   
  6. using namespace cv;  
  7. using namespace std;  
  8.   
  9. Vec3b RandomColor(int value);  //生成随机颜色函数  
  10.   
  11. int main( int argc, char* argv[] )  
  12. {  
  13.     Mat image=imread(argv[1]);    //载入RGB彩色图像  
  14.     imshow(“Source Image”,image);  
  15.   
  16.     //灰度化,滤波,Canny边缘检测  
  17.     Mat imageGray;  
  18.     cvtColor(image,imageGray,CV_RGB2GRAY);//灰度转换  
  19.     GaussianBlur(imageGray,imageGray,Size(5,5),2);   //高斯滤波  
  20.     imshow(“Gray Image”,imageGray);   
  21.     Canny(imageGray,imageGray,80,150);    
  22.     imshow(“Canny Image”,imageGray);  
  23.   
  24.     //查找轮廓  
  25.     vector<vector<Point>> contours;    
  26.     vector<Vec4i> hierarchy;    
  27.     findContours(imageGray,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());    
  28.     Mat imageContours=Mat::zeros(image.size(),CV_8UC1);  //轮廓     
  29.     Mat marks(image.size(),CV_32S);   //Opencv分水岭第二个矩阵参数  
  30.     marks=Scalar::all(0);  
  31.     int index = 0;  
  32.     int compCount = 0;  
  33.     for( ; index >= 0; index = hierarchy[index][0], compCount++ )   
  34.     {  
  35.         //对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点  
  36.         drawContours(marks, contours, index, Scalar::all(compCount+1), 1, 8, hierarchy);  
  37.         drawContours(imageContours,contours,index,Scalar(255),1,8,hierarchy);    
  38.     }  
  39.   
  40.     //我们来看一下传入的矩阵marks里是什么东西  
  41.     Mat marksShows;  
  42.     convertScaleAbs(marks,marksShows);  
  43.     imshow(“marksShow”,marksShows);  
  44.     imshow(“轮廓”,imageContours);  
  45.     watershed(image,marks);  
  46.   
  47.     //我们再来看一下分水岭算法之后的矩阵marks里是什么东西  
  48.     Mat afterWatershed;  
  49.     convertScaleAbs(marks,afterWatershed);  
  50.     imshow(“After Watershed”,afterWatershed);  
  51.   
  52.     //对每一个区域进行颜色填充  
  53.     Mat PerspectiveImage=Mat::zeros(image.size(),CV_8UC3);  
  54.     for(int i=0;i<marks.rows;i++)  
  55.     {  
  56.         for(int j=0;j<marks.cols;j++)  
  57.         {  
  58.             int index=marks.at<int>(i,j);  
  59.             if(marks.at<int>(i,j)==-1)  
  60.             {  
  61.                 PerspectiveImage.at<Vec3b>(i,j)=Vec3b(255,255,255);  
  62.             }              
  63.             else  
  64.             {  
  65.                 PerspectiveImage.at<Vec3b>(i,j) =RandomColor(index);  
  66.             }  
  67.         }  
  68.     }  
  69.     imshow(“After ColorFill”,PerspectiveImage);  
  70.   
  71.     //分割并填充颜色的结果跟原始图像融合  
  72.     Mat wshed;  
  73.     addWeighted(image,0.4,PerspectiveImage,0.6,0,wshed);  
  74.     imshow(“AddWeighted Image”,wshed);  
  75.   
  76.     waitKey();  
  77. }  
  78.   
  79. Vec3b RandomColor(int value)    <span style=“line-height: 20.8px; font-family: sans-serif;”>//生成随机颜色函数</span>  
  80. {  
  81.     value=value%255;  //生成0~255的随机数  
  82.     RNG rng;  
  83.     int aa=rng.uniform(0,value);  
  84.     int bb=rng.uniform(0,value);  
  85.     int cc=rng.uniform(0,value);  
  86.     return Vec3b(aa,bb,cc);  
  87. }  


第一幅图像分割效果:

图像处理——分水岭算法


按比例跟原始图像融合:

图像处理——分水岭算法


第二幅图像原始图:

图像处理——分水岭算法


分割效果:

图像处理——分水岭算法


按比例跟原始图像融合:

图像处理——分水岭算法

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/145666.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • mysql的longtext

    mysql的longtextvarchar为变长字节,所占空间为字符串实际长度加1,最长为65535个字节而longtext也是变长字符存储,只保存字符数据,最长为4294967295字节,比较适合存储大内容…

    2022年5月14日
    78
  • 纯CSS画三角形

    纯CSS画三角形之前遇到过的问题如果查了然后懂了,然后没有及时复盘思考,就很容易还回去,今天把许久之前的这个知识点再整理一下,一是加深自己的记忆,二也是让初学者更容易懂。首先,创建一个空的div&lt;divclass="triangle"&gt;&lt;/div&gt;然后,CSS处理它的边框,给它不一样的颜色,好观察.triangle{border-left:100pxs…

    2022年6月24日
    22
  • cuda编程基础(建站)

    一:新建CUDA项目流程(VS2013下)1.新建项目(file->New->Project)2.在项目列表中可以看见NVIDIA的CUDA项目(前提是你安装了CUDA)选择项目,添加一些必要的信息,自己定义就行3.项目生生成成功.cu文件就是跑在GPU上面的文件。文件夹里面是自动生成的一些要依赖的库文件你可以不用管二:第一个程序:HelloWorld我们通过最基本最经典的HelloWo

    2022年4月15日
    56
  • hadoop生态系统的详细介绍-详细一点[通俗易懂]

    前提日常喜欢看一些微信分享的好文,总结下来,可以作为过滤器吧(节约更多人的时间!),在这里引用的是别人的文章!对原文的作者表示感谢!确实写的很好!hadoop生态系统的详细介绍简介Hadoop是一个开发和运行处理大规模数据的软件平台,是Appach的一个用java语言实现开源软件框架,实现在大量计算机组成的集群中对海量数据进行分布式计算。今天我们来详细介绍下hadoop的生态系统。Hadoop生态…

    2022年4月3日
    73
  • jlink 与 swd 接口定义

    jlink 与 swd 接口定义1.JLink介绍J-Link是SEGGER公司为支持仿真ARM内核推出的JTAG仿真器。J-Link支持所有基于ARM架构的处理器或微控制器配合IAREWAR,ADS,KEIL等集成开发环境进行开发过程中进行单步控制执行调试。J-Link除了可以配合集成开发环境进行调试程序,进行程序下载之外,J-Link还可以单独使用。比如在产品的生产环节中,就可以单独使用J-Link进行固件的下载。JLink,SWD接口定义缺口向左,左边为JLink接口定义,右边为SWD接口定义JTAG

    2022年4月25日
    40
  • 谷粒商城项目2——环境搭建、renren-generator逆向生成所有微服务基本CRUD代码[通俗易懂]

    谷粒商城项目2——环境搭建、renren-generator逆向生成所有微服务基本CRUD代码[通俗易懂]续接上文谷粒商城项目1——分布式基础概念、环境搭建_Kaisa..的博客-CSDN博客至此,环境搭建完成了,接下来就是分布式组件了目录二、环境搭建8.人人开源框架搭建(1).克隆项目初始环境(2).创建renren-fast后台管理系统数据库(3).配置renren-fast环境(4).前端环境搭建(5).测试登录9.renren-generator代码生成器(1).根据数据库逆向生成Bean、Mapper等(2).启动renren-generator(3).创建公共微服务模块导入逆向生成代码所需要的各种依

    2022年7月28日
    11

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号