您当前的位置:首页 >> 视频
【图像处理】OpenCV系列二十二 --- 分水岭算法(watershed)详解
发布时间:2019-10-11
 

上一节我们学习了用如何用cvtColor函数对一幅图像进行颜色空间的转换,相信大家学习之后,已理解如何使用颜色空间转换的用法,本节呢,我们在学习watershed函数的用法与原理,即分水岭算法如何使用。

1、函数原型

void watershed(InputArray image, 

InputOutputArray markers);

2、函数功能

使用分水岭算法执行基于标记的图像分割;该函数实现了分水岭非参数标记分割算法的一个变;

在将图像传递给函数之前,您必须大致勾勒出图像标记中包含正索引的所需区域;

因此,每个区域被表示为具有像素值1、2、3等的一个或多个连通组件;

这样的标记可以从二进制掩码中检索到,可以使用findContours和drawContours对轮廓进行查找以及绘制出轮廓;

标记的区域是未来图像的“种子”;标记中的所有其他像素,其与所勾画的区域之间的关系是不知道的,需要通过算法来计算,一般开始设置为0;

在函数输出中,标记中的每个像素设置为“种子”组件的值或区域之间边界处的-1;

Note:

任何两个相邻的连接部件都不一定由分水岭边界(-1的像素)分隔;

例如,它们可以在传递给函数的初始标记图像中互相接触。

3、参数详解

  • 第一个参数,InputArray image,输入图像,一个8位三通道的图像;
  • 第二个参数,InputOutputArray markers,输入/输出32位单通道标记图像,与原图像具有同样的尺寸;

4、实验案例

#include 
#include
#include
#include
#include
#include

using namespace cv;
using namespace std;

Mat markerMask, img;
Point prevPt(-1, -1);

// 鼠标移动事件
static void onMouse(int event, int x, int y, int flags, void*)
{
// 越界判断
if (x < 0 || x >= img.cols || y < 0 || y >= img.rows)
return;

// 左键松开消息
if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON))
// 清理鼠标按下的坐标
prevPt = Point(-1, -1);

// 左键按下消息
else if (event == EVENT_LBUTTONDOWN)
// 记录鼠标按下的坐标
prevPt = Point(x, y);

//鼠标移动消息
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
// 鼠标当前的位置
Point pt(x, y);

// 判断是鼠标是否越界
if (prevPt.x < 0)
prevPt = pt;

// 在标记图像上绘制线条
line(markerMask, prevPt, pt, Scalar::all(255), 5, 8, 0);
line(img, prevPt, pt, Scalar::all(255), 5, 8, 0);
prevPt = pt;

// 实时刷新图像
imshow("image", img);
}
}

int main(int argc, char** argv)
{
// 打开图像
Mat img0 = imread("lena.png", 1), imgGray;

// 判断图像是否为空
if (img0.empty())
{
cout << "image error !\n";
return 0;
}

// 创建显示图像的窗口
namedWindow("image", 1);

// 备份图像
img0.copyTo(img);

// 彩色图像转换为灰度图像
cvtColor(img, markerMask, COLOR_BGR2GRAY);

// 将单通道的灰度图像,转换为三通道
cvtColor(markerMask, imgGray, COLOR_GRAY2BGR);

// 将标记图像全部像素置为0
markerMask = Scalar::all(0);

// 显示原图
imshow("image", img);
//imshow("imgGray", imgGray);

// 调用鼠标事件
setMouseCallback("image", onMouse, 0);
for (;;)
{
// 接收键盘的输入
char c = (char)waitKey(0);

// esc键退出
if (c == 27)
break;

// 重置图像为初始状态
if (c == 'r')
{
markerMask = Scalar::all(0);
img0.copyTo(img);
imshow("image", img);
}

// 按w或者空格对图像进行分水岭算法处理
if (c == 'w' || c == ' ')
{
int i, j, compCount = 0;
vector > contours;
vector hierarchy;

// 查找图像的轮廓
findContours(markerMask, contours,
hierarchy, RETR_CCOMP,
CHAIN_APPROX_SIMPLE);

// 如果图像的轮廓为空,不处理
if (contours.empty())
continue;

// 标记
Mat markers(markerMask.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);

if (compCount == 0)
continue;
vector colorTab;

// 随机生成颜色
for (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));
}

double t = (double)getTickCount();

// 分水岭算法处理
watershed(img0, markers);
t = (double)getTickCount() - t;

// 一次分水岭算法所耗费时间
printf("execution time = %gms\n",
t*1000. / getTickFrequency());

Mat wshed(markers.size(), CV_8UC3);

// 绘制经过分水岭处理之后的图像
for (i = 0; i < markers.rows; i++)
for (j = 0; j < markers.cols; j++)
{
int index = markers.at(i, j);

// 未找到的用白色填充
if (index == -1)
wshed.at(i, j) = Vec3b(255, 255, 255);

// 不再处理范围的用黑色填充
else if (index <= 0 || index > compCount)
wshed.at(i, j) = Vec3b(0, 0, 0);
else
// 每一块用不同的颜色绘制
wshed.at(i, j) = colorTab[index - 1];
}

// 显示分水岭算法处理后的图像,分水岭占%50,灰度图像占%50
wshed = wshed*0.5 + imgGray*0.5;
imshow("watershed transform", wshed);
}
}
return 0;
}

5、实验结果

【图像处理】OpenCV系列二十二 --- 分水岭算法(watershed)详解

原图(左)与效果图(右)


我是奕双,现在已经毕业将近两年了,从大学开始学编程,期间学习了C语言编程,C++语言编程,Win32编程,MFC编程,毕业之后进入一家图像处理相关领域的公司,掌握了用OpenCV对图像进行处理,如果大家对相关领域感兴趣的话,可以关注我,我这边会为大家进行解答哦!如果大家需要相关学习资料的话,可以私聊我哦!