cpp
#include <opencv2/core/utility.hpp> // 引入OpenCV的核心工具包
#include "opencv2/imgproc.hpp" // 引入OpenCV的图片处理模块
#include "opencv2/imgcodecs.hpp" // 引入OpenCV的图像编解码模块
#include "opencv2/highgui.hpp" // 引入OpenCV的高层GUI(图形用户界面)模块
#include <cstdio> // 引入标准输入输出头文件
#include <iostream> // 引入输入输出流头文件
using namespace cv; // 使用cv命名空间,避免每次调用OpenCV库函数时都要加cv::前缀
using namespace std; // 使用std命名空间,避免每次调用标准库函数时都要加std::前缀
static void help(char** argv) // help函数,用于显示程序使用方法
{
cout << "\nThis program demonstrates the famous watershed segmentation algorithm in OpenCV: watershed()\n"
"Usage:\n" << argv[0] <<" [image_name -- default is fruits.jpg]\n" << endl;
cout << "Hot keys: \n"
"\tESC - quit the program\n"
"\tr - restore the original image\n"
"\tw or SPACE - run watershed segmentation algorithm\n"
"\t\t(before running it, *roughly* mark the areas to segment on the image)\n"
"\t (before that, roughly outline several markers on the image)\n";
}
Mat markerMask, img; // 定义标记掩码和图像矩阵
Point prevPt(-1, -1); // 定义前一个点,初始化为(-1, -1),代表未初始化状态
static void onMouse(int event, int x, int y, int flags, void*) // onMouse函数,用于处理鼠标事件
{
// 检查鼠标点击是否超出图像范围,如果超出则返回
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) // 主函数
{
// 解析命令行参数
cv::CommandLineParser parser(argc, argv, "{help h | | }{ @input | fruits.jpg | }");
if(parser.has("help")) // 如果输入了帮助参数
{
help(argv); // 显示帮助信息
return 0; // 退出程序
}
string filename = samples::findFile(parser.get<string>("@input")); // 获取输入的图像文件名
Mat img0 = imread(filename, IMREAD_COLOR), imgGray; // 读取图像文件
if(img0.empty()) // 如果图像为空
{
cout << "Couldn't open image "; // 显示错误信息
help(argv); // 显示帮助信息
return 0; // 退出程序
}
help(argv); // 显示帮助信息
namedWindow("image", 1); // 创建一个窗口
img0.copyTo(img); // 复制图像
cvtColor(img, markerMask, COLOR_BGR2GRAY); // 将图像转换为灰度图,并存储到标记掩码中
cvtColor(markerMask, imgGray, COLOR_GRAY2BGR); // 将标记掩码转换回BGR格式,存储到imgGray中
markerMask = Scalar::all(0); // 将标记掩码初始化为0
imshow("image", img); // 显示图像
setMouseCallback("image", onMouse, 0); // 设置鼠标回调函数
for(;;) // 无限循环,等待用户操作
{
char c = (char)waitKey(0); // 等待按键
if(c == 27) // 如果按下ESC键
break; // 退出循环
if(c == 'r') // 如果按下r键
{
markerMask = Scalar::all(0); // 重置标记掩码
img0.copyTo(img); // 复制原始图像到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); // 初始化标记矩阵为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 ) // 如果标记为-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];
}
// 将分水岭图像与原始灰度图像混合显示
wshed = wshed*0.5 + imgGray*0.5;
imshow( "watershed transform", wshed ); // 显示分水岭转换后的图像
}
}
return 0; // 程序正常退出
}
这段代码演示了如何使用OpenCV实现分水岭分割算法,包括图像的加载、处理和显示等步骤。首先,通过命令行参数获取输入的图像名,然后读取图像并对其进行分水岭算法的预处理,包括将图像转为灰度图并进行标记。通过鼠标事件,用户可以在图像上标记出想要分割的区域。最后,执行分水岭算法并显示分割结果。整个程序还包括了一些基本的用户交互,如按键操作来控制程序的行为。
go
findContours(markerMask, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
apache
drawContours(markers, contours, idx, Scalar::all(compCount + 1), -1, 8, hierarchy, INT_MAX);
go
watershed(img0, markers);