您的位置:首页 > 运维架构

Opencv之分水岭原理和实现

2016-09-17 19:17 429 查看
在很多实际应用中,我们需要分割图像,分割方法有多种经典的分割方法:

一、常见图像分割方法:

1、基于边缘检测的方法:

       此方法主要是通过检测区域的边缘进行分割,利用区域之间的特征的不一致性,首先检测图像中的边缘点,然后按照一定的方法把这些边缘点进行全部连接起来,从而构成分割区域。图像中的边缘通常是灰度,颜色或者纹理,其中基于灰度的方法很普遍,许多边缘检测算子利用灰度来检测图像的梯度,Roberts 算子、Laplace 算子、Prewitt 算子、Sobel 算子、Rosonfeld算子、Kirsch 算子以及Canny 。边缘检测算法比较适合边缘灰度值过渡比较显著且噪声较小的简单图像的分割。对于边缘比较复杂以及存在较强噪声的图像,则面临抗噪性和检测精度的矛盾。若提高检测精度,则噪声产生的伪边缘会导致不合理的轮廓:若提高抗噪性,则会产生轮廓漏检和位置偏差。

2.阈值分割方法

       阈值分割是最古老的分割技术,也是最简单实用的。许多情况下,图像中目标区域与背景区域或者说不同区域之间其灰度值存在差异,此时可以将灰度的均一性作为依据进行分割。阈值分割即通过一个或几个阈值将图像分割成不同的区域。阈值分割方法的核心在于如何寻找适当的阈值。最常用的阈值方法是基于灰度直方图的方法,如最大类间方差法(OTSU)、最小误差法、最大熵法等。此类方法通常对整幅图像使用固定的全局阈值,如果图像中有阴影或亮度分布不均等现象,分割效果会受到影响。基于局部阈值的分割方法对图像中的不同区域采用不同的阈值,相对于全局阈值方法具有更好的分割效果,该方法又称为自适应阈值方法。

3.区域生长

      区域生长方法[46]也是一种常用的区域分割技术,其基本思路是首先定义一个生长准则,然后在每个分割区域内寻找一个种子像素,通过对图像进行扫描,依次在种子点周围邻域内寻找满足生长准则的像素并将其合并到种子所在的区域,然后再检查该区域的全部相邻点,并把满足生长准则的点合并到该区域,不断重复该过程直到找不到满足条件的像素为止。该方法的关键在于种子点的位置、生长准则和生长顺序。

4.分水岭算法

      是以数学形态学作为基础的一种区域分割方法。其基本思想是将梯度图像看成是假想的地形表面,每个像素的梯度值表示该点的海拔高度。原图中的平坦区域梯度较小,构成盆地,边界处梯度较大构成分割盆地的山脊。分水岭算法模拟水的渗入过程,假设水从最低洼的地方渗入,随着水位上升,较小的山脊被淹没,而在较高的山脊上筑起水坝,防止两区域合并。当水位达到最高山脊时,算法结束,每一个孤立的积水盆地构成一个分割区域。由于受到图像噪声和目标区域内部的细节信息等因素影响,使用分水岭算法通常会产生过分割现象,分水岭算法一般是作为一种预分割方法,与其它分割方法结合使用,以提高算法的效率或精度。

二、分水岭算法



在上面的水岭算法示意图中局部极小值、积水盆地,分水岭线以及水坝的概念可以描述为:

(1)区域极小值:导数为0的点,局部范围内的最小值点;

(2)集水盆(汇水盆地):当“水”落到汇水盆地时,“水”会自然而然地流到汇水盆地中的区域极小值点处。每一个汇水盆地中有且仅有一个区域极小值点;

(3)分水岭:当“水”处于分水岭的位置时,会等概率地流向多个与它相邻的汇水盆地中;

(4)水坝:人为修建的分水岭,防止相邻汇水盆地之间的“水”互相交汇影响。

OpenCV提供了函数watershed()来实现分水岭算法,它采用的是Meyer在1994年提出的基于距离函数的算法,具体的论文大家可以搜索“Meyer, F. Color Image Segmentation, ICIP92, 1992”。

函数原型:

void watershed(InputArray image, InputOutputArray markers)
第一个参数:输入图src,需要8位三通道的彩图;
第二个参数较复杂:“markers中存储了图像的大致轮廓,<span style="color:#ff0000;">32位单通道图像</span>,并且以值1,2,3..分别表示各个components.markers通常由函数结合使用来获得。markers相
当于watershed()运行时的种子参数。markers中,不属于轮廓(outlined regions)的点的值应置为0.函数运行后,
图像中的像素如果是在由某个轮廓种子生成的区域中,那么其值就置为这个种子的编号,如果像素不在轮廓种子生成的区域中(边界),则置为-1。

网上的经典程序整理,但部分函数没看懂。。。。

头文件:

#include <opencv2/opencv.hpp>
using namespace cv;

class WatershedSegmenter {

private:

Mat markers;

public:

void setMarkers(const Mat& markerImage) {

// Convert to image of ints
markerImage.convertTo(markers, CV_32S);
}

Mat process(const Mat &image) {

// Apply watershed
watershed(image, markers);

return markers;
}

// Return result in the form of an image
Mat getSegmentation() {

Mat tmp;
// all segment with label higher than 255
// will be assigned value 255
<span style="color:#ff0000;">markers.convertTo(tmp, CV_8U);</span>//这里没看懂,CV_32S转化成CV_8U.

return tmp;
}

// Return watershed in the form of an image
Mat getWatersheds() {

Mat tmps;
//在设置标记图像,即执行process()后,maskers边缘的像素会被赋值为-1,其他的用正整数表示
//下面的这个转换可以让边缘像素变为-1*255+255=0,即黑色,其余的溢出,赋值为255,即白色。
<span style="color:#ff0000;">markers.convertTo(tmps, CV_8U, 255, 255);
</span>
return tmps;
}
};

getSegmentation()和GetWatersgeds()还没理解,基础太差!

#include <iostream>
#include <opencv2/opencv.hpp>
#include "watershedSegmentation.h"
using namespace std;
using namespace cv;

int main()
{
Mat image = imread("D://vvoo//group.jpg");
if (!image.data)
return 0;
imshow("Original Image", image);
// Get the binary map
Mat binary;
cvtColor(image, binary,CV_BGR2GRAY);
threshold(binary, binary, 80, 255, CV_THRESH_BINARY);  //阈值化操作
imshow("Binary Image", binary);
// Eliminate noise and smaller objects
Mat fg, bg;
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
erode(binary, fg, element);
imshow("Foreground Image", fg);
dilate(binary, bg, element);
threshold(bg, bg, 1, 128, THRESH_BINARY_INV);
imshow("Background Image", bg);
// Show markers image
Mat marker(binary.size(), CV_8U, Scalar(0));
marker = fg + bg;
imshow("Markers", marker);

// Create watershed segmentation object
WatershedSegmenter segmenter;
// Set marker and process
segmenter.setMarkers(marker);
segmenter.process(image);
// Display segmentation result
imshow("Segmentation", segmenter.getSegmentation());
// Display watersheds
imshow("Watersheds", segmenter.getWatersheds());
waitKey(0);
return 0;
}
个人理解:

其中markers是前景和背景进行相加得到,感觉因为前景和背景的像素值不同,相加后就得到了“地形图”,然后想这些地形图灌水,如图:



我的意思是:其中牛的中心灰度是128(小山岭),白色为255(大山岭),黑色为0(盆地),makers灰度最低的是0(盆地),然后我们向黑色区域(盆地)灌水,慢慢地灌溉到灰色区域(与灰色区域水位持平)时,灌溉结束后就是上面右图。

imshow("Watersheds", segmenter.getWatersheds()),显示的结果是最终的图(左边),右边的图是原图,别大家看了结果图后不知分割出来的东东是什么:



三、grabCut()分割介绍

原文地址:http://www.cnblogs.com/tornadomeet/archive/2012/11/09/2763271.html

grabcut是在graph cut基础上改进的一种图像分割算法,它同样是基于图割理论。稍微看了下grabcut方面的论文,论文中一般都是在graph cut上作改进,比如说引入了GMM模型等。同graph cut一样,在使用grabcut是也是需要人机交互的,即人工先给定一定区域的目标或者背景,然后送给grabcut算法来分割。通过实验发现,其分割效果一般般,且分割速度比较慢,一张普通大小的图片差不多需要1s左右的时间,

void cv::grabCut( const Mat& img, Mat& mask, Rect rect, Mat& bgdModel, Mat& fgdModel, int iterCount, int mode )
img——待分割的源图像,必须是8位3通道(CV_8UC3)图像,在处理的过程中不会被修改;
  mask——掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果。mask只能取以下四种值:
  GCD_BGD(=0),背景;
  GCD_FGD(=1),前景;
  GCD_PR_BGD(=2),可能的背景;
  GCD_PR_FGD(=3),可能的前景。
  如果没有手工标记GCD_BGD或者GCD_FGD,那么结果只会有GCD_PR_BGD或GCD_PR_FGD;
  rect——用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;
  bgdModel——背景模型,如果为null,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;
  fgdModel——前景模型,如果为null,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;
  iterCount——迭代次数,必须大于0;
  mode——用于指示grabCut函数进行什么操作,可选的值有:
  GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;
  GC_INIT_WITH_MASK(=1),用掩码图像初始化GrabCut;
  GC_EVAL(=2),执行分割。


小程序:用时时间太长,7s左右,分割效果不好

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main()
{

Mat srcImage = imread("D://vvoo//group.jpg");

Mat mask;
Mat foreground;

Rect rect(0, 100, 500, 200);
Mat bkgModel, fgrModel; // the models (internally used)

// GrabCut segmentation
grabCut(srcImage,  // input image
mask, // segmentation result
rect, bkgModel, fgrModel, 5, GC_INIT_WITH_RECT);

// Get the pixels marked as likely foreground
mask = mask & 1;//try to find forgeground
foreground.create(srcImage.size(), CV_8UC3);
foreground.setTo(Scalar(255, 255, 255));
srcImage.copyTo(foreground, mask); // pixels of background are not copied;pixels of srcImage is copied if mask isn't zero

// draw rectangle on original image
rectangle(srcImage, rect, Scalar(255, 255, 255), 1);
imshow("Original Image", srcImage);

// display result
imshow("Foreground objects", foreground);

waitKey();
return 0;
}

结果:还有好多草



四、参考资料

1.http://blog.csdn.net/u010741471/article/details/45193521#reply

2.http://blog.csdn.net/fdl19881/article/details/6749976

3.http://blog.csdn.net/lsg32/article/details/8219066
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息