opencv分水岭算法的两个新用法

opencv中的分水岭算法非常高效,在一些场景中存在价值很高,可以由创意的调用这个程序,减少重写代码的量。但最近遇到的问题让我发现想用好它不是很简单。

今天提到的两个方法,当一个由创造性的调包侠吧。

一、用分水岭算法得到斑块的中轴线

opencv分水岭算法的两个新用法_第1张图片
opencv分水岭算法的两个新用法_第2张图片
上图是斑块,下图是各斑块的分界线

上代码:

void onWatershed(Mat im)//分水岭,im已被处理成单通道参考(通过split())
{
    Mat im1;
    Mat out=Mat::zeros(im.size(), CV_32S);//分水岭算法对输出的要求为32位整形
    Mat bg, fg,x;
    im.copyTo(fg);
    //threshold(v[0], fg, 150, 255, cv::THRESH_BINARY);//_INV
    fg.copyTo(x);
    vector<vector>contours;

    //寻找轮廓,findContours配合以drawContours可以得到斑块的轮廓
    findContours(x, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
    for (auto i = 0; i < contours.size(); i++){
        drawContours(out, contours, i, Scalar(i +1));
    }
    /**/

    Mat disTransf = Transfer(fg);//这是重点,使用距离变换作为分水岭算法的基础图
    disTransf.convertTo(bg, CV_8UC1);//说明[1]
    normalize(bg, bg, 0, 255, CV_MINMAX);

    Mat im2[] = { bg, bg, bg };//这是分水岭输入数据的要求,3波段
    merge(im2, 3, im1);


    watershed(im1, out);//分水岭算法
    out.convertTo(out, CV_8UC1);//将结果中的边界-1输出
    threshold(out, out, 0, 255, cv::THRESH_BINARY_INV);
}

分水岭方法是根据所提供的标记数据(代码中为out),标识出种子区。在上面代码中,各个斑块的边界作为了种子区。对于种子区,有一点需要注意,不同种子之间需要用不同的数值来标记,所以drawContours的时候,设置了i区分各斑块的边界。

被分割的图片(代码中的im1)是能表达某种特征的基础图,在上面代码中,这个特征图就是黑色无值区域距离最近的斑块的距离。所以黑色也就因此被分割成斑块中轴的线条。

上距离转换图:
opencv分水岭算法的两个新用法_第3张图片

下面,再来一发不一样的应用

二、使用分水岭标识不同斑块

这有点多此一举的感觉,但是需要对斑块有区分的操作的情况下,还是希望不同斑块有不同的标识符,方便进一步的处理。

效果是这样的:
opencv分水岭算法的两个新用法_第4张图片

上代码:

//修改这两行代码
/*
Mat im2[] = { bg, bg, bg };//这是分水岭输入数据的要求,3波段
merge(im2, 3, im1);
*/
//变成下面的结果
Mat im2[] = { fg, fg, fg };//即原图
merge(im2, 3, im1);

通过这种方式,得到分水岭算法的两种使用方式。当然喽,你肯定能写出类似的代码,不过像我这种懒人大概会调包了。;)

说明【1】:

im.convertTo(bg, CV_8UC1);//这两个先后顺序关系重大,推测是需要一个255的值。
normalize(bg, bg, 0, 255, CV_MINMAX);//距离变成一背景

之前对WaterShed方法不太理解,帮助文档说明有限,感觉像黑箱一样,它的效果全靠试出来的。之前模模糊的过去,就推测了一种可能性,但是这种直接float类型转int型,造成精度的丢失,有时候会不能得到准确的位置。比如根号2和1就经常出现。
究竟在调用WaterShed的时候第一个mat参数有什么属性呢:
官方的说法是image – Input 8-bit 3-channel image. 是8比特的3通道矩阵图片
还有就是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. marks标记是第二个参数的意义。官方文档也告诉了一般的方法,使用contours来得到。
第一个参数image 的其他属性呢。我之前直接线性拉伸,结果不对,然后随便写了一个大致的推测,没有深入研究。
然后,又实验了对矩阵平方的方法,使用掩膜确保其他区域值为0;但是都不行。
最后,尝试了一种可能,终于可以了:
最小的有效值需要是1.
方法如下:

im = Mat::zeros(img.size(), CV_32F) + 1.4;//选1.4是为了区分1和根号2,同时又不至于四舍五入丢失这一属性。所以选1.1也可以,这样也能是让原来的1.4能四舍五入到2,而且有更少的假边缘。
im = disTransf.mul(im);
im.convertTo(bg, CV_8UC1);//

然后就ok了。

你可能感兴趣的:(opencv)