【opencv】示例-watershed.cpp 通过用户的鼠标输入分水岭算法可以使得图像的不同部分分割开来...

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);
相关推荐
Mr.Q5 分钟前
OpenCV和Qt坐标系不一致问题
qt·opencv
დ旧言~17 分钟前
【高阶数据结构】图论
算法·深度优先·广度优先·宽度优先·推荐算法
张彦峰ZYF21 分钟前
投资策略规划最优决策分析
分布式·算法·金融
陈鋆23 分钟前
智慧城市初探与解决方案
人工智能·智慧城市
qdprobot23 分钟前
ESP32桌面天气摆件加文心一言AI大模型对话Mixly图形化编程STEAM创客教育
网络·人工智能·百度·文心一言·arduino
QQ395753323724 分钟前
金融量化交易模型的突破与前景分析
人工智能·金融
QQ395753323725 分钟前
金融量化交易:技术突破与模型优化
人工智能·金融
The_Ticker37 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
Elastic 中国社区官方博客43 分钟前
Elasticsearch 开放推理 API 增加了对 IBM watsonx.ai Slate 嵌入模型的支持
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
jwolf243 分钟前
摸一下elasticsearch8的AI能力:语义搜索/vector向量搜索案例
人工智能·搜索引擎