分水岭算法看了两天了,基本原理看着挺简单,但是opencv中具体的实现方式看着还挺困难的。今天就说说我理解的地方,有很多不太理解的还得以后深入学习时候再补充。
基本原理:分水岭实则为两个盆地的交界处,通过在每个盆地中浸水的方式产生分割边界,两个盆地快要混合到一起的那个边界即为分割边界;
具体实现方式:
通过mark图像(即人工选取初始浸水点)指导浸水过程,通过findContours函数产生contours,然后通过drawContours函数得到mark图像。如下图为一个mark图像。
有3个区域是初始浸水区。opencv分水岭算法过程如下:
初始化mark矩阵,生成最初的注水区域。
1、设置mark图像的边框为-1;
2、标记每个mark区域的边界为-2;
3、对于mark图像的每个像素,如果它本身为0,但上下左右四邻域有一个不为0,则把该点按照RGB值放入相应的队列。队列高度是256,对应(0-255),初始阶段完成后,把-2对应的边界点按照RGB放入相应队列。
之后进入浸水过程,递归描述如下:
for(;;)
{
扫描0-255高度值队列,如果找到一个像素,则弹出该标记,并退出扫描;
如果该像素的四邻域中存在两个不同的非0值,表示该点为注水盆地的边缘,即分水岭,在mark图像中标记该点为-1;
扫描该点的四邻域,是否存在为0的mark域,如果存在的话就把该邻域点标记为RGB值,放入队列;
}
经过递归过程后得到下图:
具体代码如下:
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
//分水岭图像分割
void watershedSrgment(Mat& src, int& noOfSegment)//noOfSegment表示分割的类别数
{
Mat grayMat;
Mat otsuMat;
cvtColor(src,grayMat,CV_BGR2GRAY);
threshold(grayMat,otsuMat,0,255,CV_THRESH_BINARY_INV+CV_THRESH_OTSU);
imshow("src",src);
imshow("otsuMat",otsuMat);
//形态学
morphologyEx(otsuMat,otsuMat,MORPH_OPEN,Mat::ones(9,9,CV_8SC1),Point(4,4),2);//2表示迭代的次数,Point(4,4)表示结构元素的原点
imshow("Mor-open",otsuMat);
//距离变换
Mat disTranMat(otsuMat.rows,otsuMat.cols,CV_32FC1);
distanceTransform(otsuMat,disTranMat,CV_DIST_L2,3);
//归一化
normalize(disTranMat,disTranMat,0.0,1,NORM_MINMAX);
imshow("DisTranMat",disTranMat);
//阈值分割
threshold(disTranMat,disTranMat,0.1,1,CV_THRESH_BINARY);
normalize(disTranMat,disTranMat,0,255,NORM_MINMAX);
disTranMat.convertTo(disTranMat,CV_8UC1);
imshow("TDisTranMat",disTranMat);
//计算标记的分割块
int i, j, compCount = 0;
vector> contours;
vector hierarchy;
findContours(disTranMat,contours,hierarchy,CV_RETR_CCOMP,CV_CHAIN_APPROX_SIMPLE);
Mat markers(disTranMat.size(),CV_32S);
markers = Scalar::all(0);
int idx = 0;
//绘制区域块
for (; idx >= 0; idx = hierarchy[idx][0], compCount++)
{
drawContours(markers,contours,idx,Scalar::all(compCount+1),-1,8,hierarchy,INT_MAX);
}
imshow("markers",markers*255);
double t = (double)getTickCount();
watershed(src,markers);
t = (double)getTickCount() - t;
cout << "time: " << t * 1000 / getTickFrequency() << endl;
vector colorTab;
for (int i = 0; i < compCount; i++)
{
int b = theRNG().uniform(0,255);
int g = theRNG().uniform(0,255);
int r = theRNG().uniform(0,255);
colorTab.push_back(Vec3b((uchar)b,(uchar)g,(uchar)r));
}
Mat watershedImage(markers.size(),CV_8UC3);
for (int i = 0; i < markers.rows; i++)
{
for (int j = 0; j < markers.cols; j++)
{
int index = markers.at(i,j);
if (index == -1)
watershedImage.at(i, j) = Vec3b((uchar)255, (uchar)255, (uchar)255);
else if (index <= 0 || index > compCount)
watershedImage.at(i, j) = Vec3b(0, 0, 0);
else
watershedImage.at(i, j) = colorTab[index-1];
}
}
imshow("watershed",watershedImage);
waitKey(0);
}
int main()
{
//imread中0表示灰度返回,1表示原图返回
Mat srcImage = imread("E:\\研究生\\学习材料\\学习书籍\\OpenCV图像处理编程实例-源码-20160801\\《OpenCV图像处理编程实例-源码-20160801\\images\\flower.jpg");
if (!srcImage.data)
return -1;
int noOfSegment = 0;
watershedSrgment(srcImage, noOfSegment);
return 0;
}