【图像处理】OpenCV系列二十二 --- 分水岭算法(wate
2019-05-05 本文已影响22人
307656af5a04
上一节我们学习了用如何用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 <opencv2/core/utility.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <cstdio>
#include <iostream>
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<vector<Point> > contours;
vector<Vec4i> 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<Vec3b> 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<int>(i, j);
// 未找到的用白色填充
if (index == -1)
wshed.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
// 不再处理范围的用黑色填充
else if (index <= 0 || index > compCount)
wshed.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
else
// 每一块用不同的颜色绘制
wshed.at<Vec3b>(i, j) = colorTab[index - 1];
}
// 显示分水岭算法处理后的图像,分水岭占%50,灰度图像占%50
wshed = wshed*0.5 + imgGray*0.5;
imshow("watershed transform", wshed);
}
}
return 0;
}
5、实验结果
原图(左)与效果图(右)我是奕双,现在已经毕业将近两年了,从大学开始学编程,期间学习了C语言编程,C++语言编程,Win32编程,MFC编程,毕业之后进入一家图像处理相关领域的公司,掌握了用OpenCV对图像进行处理,如果大家对相关领域感兴趣的话,可以关注我,我这边会为大家进行解答哦!如果大家需要相关学习资料的话,可以私聊我哦!