Opencv分水岭算法——watershed自动图像分割用法

时间:2022-11-11 09:19:12

转载自:http://m.blog.csdn.net/article/details?id=52498440

分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将

在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要特征

     其他图像分割方法,如阈值,边缘检测等都不会考虑像素在空间关系上的相似性和封闭性这一概念,彼此像素间互

相独立,没有统一性。分水岭算法较其他分割方法更具有思想性,更符合人眼对图像的印象。

Opencv 中 watershed函数原型:

void watershed( InputArray image, InputOutputArray markers );
第一个参数 image,必须是一个8bit 3通道彩色图像矩阵序列,第一个参数没什么要说的。关键是第二个参数 markers

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

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

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

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

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

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

3. watershed分水岭运算

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

完整代码如下:

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <iostream>

using namespace cv;
using namespace std;

Vec3b RandomColor(int value); //生成随机颜色函数

int main( int argc, char* argv[] )
{
Mat image=imread(argv[1]); //载入RGB彩色图像
imshow("Source Image",image);

//灰度化,滤波,Canny边缘检测
Mat imageGray;
cvtColor(image,imageGray,CV_RGB2GRAY);//灰度转换
GaussianBlur(imageGray,imageGray,Size(5,5),2); //高斯滤波
imshow("Gray Image",imageGray);
Canny(imageGray,imageGray,80,150);
imshow("Canny Image",imageGray);

//查找轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(imageGray,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());
Mat imageContours=Mat::zeros(image.size(),CV_8UC1); //轮廓
Mat marks(image.size(),CV_32S); //Opencv分水岭第二个矩阵参数
marks=Scalar::all(0);
int index = 0;
int compCount = 0;
for( ; index >= 0; index = hierarchy[index][0], compCount++ )
{
//对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点
drawContours(marks, contours, index, Scalar::all(compCount+1), 1, 8, hierarchy);
drawContours(imageContours,contours,index,Scalar(255),1,8,hierarchy);
}

//我们来看一下传入的矩阵marks里是什么东西
Mat marksShows;
convertScaleAbs(marks,marksShows);
imshow("marksShow",marksShows);
imshow("轮廓",imageContours);
watershed(image,marks);

//我们再来看一下分水岭算法之后的矩阵marks里是什么东西
Mat afterWatershed;
convertScaleAbs(marks,afterWatershed);
imshow("After Watershed",afterWatershed);

//对每一个区域进行颜色填充
Mat PerspectiveImage=Mat::zeros(image.size(),CV_8UC3);
for(int i=0;i<marks.rows;i++)
{
for(int j=0;j<marks.cols;j++)
{
int index=marks.at<int>(i,j);
if(marks.at<int>(i,j)==-1)
{
PerspectiveImage.at<Vec3b>(i,j)=Vec3b(255,255,255);
}
else
{
PerspectiveImage.at<Vec3b>(i,j) =RandomColor(index);
}
}
}
imshow("After ColorFill",PerspectiveImage);

//分割并填充颜色的结果跟原始图像融合
Mat wshed;
addWeighted(image,0.4,PerspectiveImage,0.6,0,wshed);
imshow("AddWeighted Image",wshed);

waitKey();
}

Vec3b RandomColor(int value) <span style="line-height: 20.8px; font-family: sans-serif;">//生成随机颜色函数</span>
{
value=value%255; //生成0~255的随机数
RNG rng;
int aa=rng.uniform(0,value);
int bb=rng.uniform(0,value);
int cc=rng.uniform(0,value);
return Vec3b(aa,bb,cc);
}