OpenCV2 使用分水岭算法对图像分割的个人理解 cv::watershed()

来源:互联网 发布:centos 查看硬盘分区 编辑:程序博客网 时间:2024/05/20 11:34

本文是基于《opecv2 计算机视觉编程手册》中的案例对分水岭算法进行解读。

先介绍一下分水岭分割方法。它是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。

一般的分水岭算法会对微弱边缘,图像中的噪声,物体表面细微的灰度变化造成过度的分割。opencv中的分水岭算法对此进行了改进,它使用预定义的一组标记来引导对图像的分割。理解这一节,关键是理解这个标记。书上这一节的内容对这个标记讲得很模糊,网上也搜了很多cv::watershed(),大部分内容是opencv1.0案例使用鼠标响应事件的,有个别是opencv2.0的案例,但都只是复制代码,并未做一些解读。这里我谈谈自己的一些看法,希望对大家有所帮助,如有理解不正确的地方还请高手多多指教。
watershedSegmenter.h

#if !defined WATERSHS#define WATERSHS#include <opencv2/core/core.hpp>#include <opencv2/imgproc/imgproc.hpp>class WatershedSegmenter {  private:      //用来表示标记(图)      cv::Mat markers;  public:       //设置标记图      void setMarkers(const cv::Mat& markerImage) {        //watershed()的输入参数必须为一个32位有符号的标记,所以要先进行转换         markerImage.convertTo(markers,CV_32S);      }      //执行watershed()      cv::Mat process(const cv::Mat &image) {        // Apply watershed        cv::watershed(image,markers);        return markers;      }      // 以图像形式返回结果      cv::Mat getSegmentation() {        cv::Mat tmp;    // 从32S到8U(0-255)会进行饱和运算,所以像素高于255的一律复制为255        markers.convertTo(tmp,CV_8U);//        return tmp;      }      // 以图像形式返回分水岭(我理解的是分割线)      cv::Mat getWatersheds() {        cv::Mat tmp;        //在设置标记图像,即执行setMarkers()后,边缘的像素会被赋值为-1,其他的用正整数表示        //下面的这个转换可以让边缘像素变为-1*255+255=0,即黑色,其余的溢出,赋值为255,即白色。        markers.convertTo(tmp,CV_8U,255,255);        return tmp;      }};#endif

segment.cpp

#include <iostream>#include <opencv2/core/core.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/highgui/highgui.hpp>#include "watershedSegmentation.h"int main(){    // Read input image 原图    cv::Mat image= cv::imread("../group.jpg");    if (!image.data)        return 0;     // Display the image    cv::namedWindow("Original Image");    cv::imshow("Original Image",image);

group.jpg

// 二值图 这里进行了像素反转,因为一般我们用255白色表示前景(物体),用0黑色表示背景    cv::Mat binary;    binary= cv::imread("../binary.bmp",0);    // Display the binary image    cv::namedWindow("Binary Image");    cv::imshow("Binary Image",binary);

binary.bmp
这里写图片描述

// 由二值图像获得前景。腐蚀。移除噪点与微小物体    cv::Mat fg;    cv::erode(binary,fg,cv::Mat(),cv::Point(-1,-1),6);    // Display the foreground image    cv::namedWindow("Foreground Image");    cv::imshow("Foreground Image",fg);

fg前景
这里写图片描述

    //膨胀二值图来获取背景(只有草地,没有树林)    cv::Mat bg;    cv::dilate(binary,bg,cv::Mat(),cv::Point(-1,-1),6);    cv::threshold(bg,bg,1,128,cv::THRESH_BINARY_INV);    //最后一个参数的表示 ifsrc>1,dst=0,else dst=128。这样就使背景全为灰色(128)    // Display the background image    cv::namedWindow("Background Image");    cv::imshow("Background Image",bg);

bg背景
这里写图片描述

    // Show markers image    cv::Mat markers(binary.size(),CV_8U,cv::Scalar(0));    markers= fg+bg;//使用重载操作符+    cv::namedWindow("Markers");    cv::imshow("Markers",markers);

markers标记图像 (最初的markers) 我觉得难点就是对这个标记图像的理解。下面谈谈我的个人看法。这个图像中,随便找一头牛。前景(物体:牛)是白色255,中间是黑色0,再然后才是背景灰128。可以把这3数看成海拔图,两个山岭相连,小的山岭128m,低处山谷0m,高的山岭255m。然后分水岭算法开始扩大白色255区域,往(黑色)注水,一直注到灰色的齐平,注水到黑色与白色部分全是白色。
这里写图片描述
设置markers,执行,显示结果

    // Create watershed segmentation object    WatershedSegmenter segmenter;    // Set markers and process    segmenter.setMarkers(markers);    segmenter.process(image);    // Display segmentation result    cv::namedWindow("Segmentation");    cv::imshow("Segmentation",segmenter.getSegmentation());

注水后就成下图,上面的只是我的理解,可能不太正确,但我觉得这样便于理解怎么从上图白色那么一点变成下图。
结果图
这里写图片描述

// Display watersheds    cv::namedWindow("Watersheds");    cv::imshow("Watersheds",segmenter.getWatersheds());

分水岭(分割线)
这里写图片描述

(完)
还请大家批评指教~

2 0
原创粉丝点击