用形态学运算变换图像(四)用分水岭算法实现图像分割

论坛 期权论坛 脚本     
匿名网站用户   2020-12-20 02:46   11   0

(四)用分水岭算法实现图像分割
分水岭变换,是一种流行的图像处理算法,用于快速将图像分割成多个同质区域。若把图像看成是一个拓扑地貌,同类区域相当于陡峭边缘内相对平坦的盆地;算法通过逐步增高水位,将其分割成多个部分。因原始版本算法会过度分割图像,产生很多小的区域,因此OpenCV中提出改进版本,使用一系列预定义标记来引导图像分割的方式。
4.1 实现
分水岭分割法,需要调用cv::watershed函数。输入对象是一个标记图像,图像的像素值为32位有符号整数,每个非零像素代表一个标签。其原理是对图像中部分图像做标记,表明它们所属区域是已知的。
分水岭算法,可根据这个初始化标签确定其他像素所属的区域。本节先建立一个标记图像作为灰度图像,后将其转换成整型图像。把该步骤封装进WatershedSegmenter类,包含指定标记图像和计算分水岭的方法:

       class WatershedSegmenter
       {
         Private:
            cv::Mat markers;     
         Public:       
             Void setMarkers(const cv::Mat& markerImage)
             {  
                 //转换成整数型图像  
                 markerImage.converTo(markers,CV_32S);
             }
             cv::Mat process(const cv::Mat &image)
             { 
                 //应用分水岭 
                 cv::watershed(image,markers);
                 return  markers;
              }
         }

    不同应用程序获得标记的方式各不相同。如,可在预处理过程中识别出一些属于某个感兴趣物体的像素,然后根据初始检测结果,使用分水岭算法划分出整个物体的边缘。如:从二值图像中识别出属于前景和属于背景的像素,前景像素标记为255,背景像素标记为128(数字随意选择,任何不等于255的数字都可以),其他像素的标签是未知的,标记为 0。
    因二值图像包含属于图像不同部分的白色像素,因此对图像做深度腐蚀运算,只保留明显属于前景物体的像素:
    
          //消除噪声和细小物体
          cv::Mat  fg;
          cv::erode(binary,fg,cv::Mat(),cv::Point(-1,-1),4);
    对原二值图像做一次大幅的膨胀运算,选中一些背景像素:
          //标识不含物体的图像像素
           cv::Mat  bg;
           cv::dilate(binary,bg,cv::Mat(),cv::Point(-1,-1),4);
           cv::threshold(bg,bg,1,128,cv::THRESH_BINARY_INV);
           
    得到的黑色像素对应背景像素。因此膨胀后,要立即通过阈值化运算把它们赋值为128得到图像。
    合并前景和背景两幅图像,得到标记图像,代码:
    
        //创建标记图像
        cv::Mat markers(binary,size(),CV_8U,cv::Scalar(0));
        markers=fg+bg;
        
    合并后的图像,被作为分水岭算法的输入。白色区域属于前景,灰色区域属于背景,黑色区域带有未知标签。分水岭算法的作用,即明确划分前景和背景,并对黑色区域的像素做出标记(属于前景还是背景)。
    分割图像的方法:
    
         //创建分水岭分割类的对象
         WatershedSegmenter  segmenter;
        //设置标记图像,然后执行分割过程
        segmenter.setMarkers(markers);
        segmenter.process(image);
        
 上面的代码会修改标记图像,每个值为0的像素都会被赋予一个输入标签,而边缘处的像素被赋值为-1,得到标签图像和边缘图像。

4.2 原理
用分水岭算法分割图像的原理,是从高度0开始逐步用洪水淹没图像。需要从一组预先定义好的标记像素开始,每个用标记创建的盆地,都按照初始标记的值加上标签,如果两个标签相同的盆地汇合,就不创建分水岭,以免过度分割。调用cv::watershed函数即为执行该过程。
函数输入的标记图像会被修改,用以生成最终的分水岭分割图。输入的标记图像可以含有任意数值的标签,未知标签的像素值为0。标记图像的类型选用32位有符号整数,以便定义超过255个的标签。另,可把分水岭的对应像素设为特殊值-1。
为方便显示结果,采用两种特殊方法。
第一种:返回由标签组成的图像(包含值为0的分水岭)。该方法通过阈值化易实现,代码如示:

        //以图像的形式返回结果
        cv::Mat getSegmentation()
        {
            cv::Mat tmp;
            //所有标签值大于255的区段都赋值为255
            markers.convertTo(tmp,CV_8U);
            return tmp;
         }

第二种:返回一幅图像,图像中分水岭线条赋值为0,其他部分赋值为255。用cv::convertTo方法来获得结果,代码如下:

       //以图像的形式返回分水岭
       cv::Mat getWatersheds()
       {
           cv::Mat tmp;
           //在变化前,把每个像素p转换为255p+255
           markers.convertTo(tmp,CV_8U,255,255);
           return tmp;
      }
      
     在变换前性转换,使值为-1的像素变为0(因为-1*255+255=0)。     
     值大于255的像素赋值为255。这是因为将有符号整数转化为无符号字符型时,应用了饱和度运算。
     图像饱和度的调整方法有很多,最简单的就是判断每个像素的R、G、B值是否大于或小于128,大于加上调整值,小于则减去调整值;也可以将像素RGB转换为HSV或者HSL,然后调整其S部分,从而达到线性调整图像饱和度的目的。
4.3 拓展         
     标记图像方法有:第一种,用户可以交互地在场景中的物体和背景上绘制区域,以标注物体;第二种,当需要标识的物体位于图像中间时,可以简单地在输入图像中的中心位置标记特定标签,在图像的边缘位置(假设背景在边缘位置)标记上另一个标签。在创建标记图像时,可以在标记图像上绘制加粗的矩形:    
     
        //标识背景像素
        cv::Mat imageMask(image.size(),CV_8U,cv::Scalar(0));
        cv::rectangle(imageMask,
                               cv::Point(5,5),
                               cv::Point(image.cols-5,image.rows-5) ,
                               cv::Scalar(255),3);
        //标识前景像素
        //在图像的中心
        cv::rectangle(imageMask,
                              cv::Point (image.cols/2-10,image.rows/2-10),
                              cv::Point(image.cols/2+10,image.rows/2+10),
                              cv::Scalar(1),10);
如果把这个标记图像叠加到实验图像上,得到其结果和分水岭的图像。
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1136255
帖子:227251
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP