【opencv】示例-detect_mser.cpp 使用 MSER 算法来检测图像中的极值区域

cpp 复制代码
#include <opencv2/core.hpp> // 包含OpenCV核心操作的头文件
#include <opencv2/imgproc.hpp> // 包含图像处理功能的头文件
#include <opencv2/highgui.hpp> // 包含图形用户界面的头文件
#include <opencv2/features2d.hpp> // 包含特征检测相关功能的头文件
#include "opencv2/core/opengl.hpp" // 包含OpenCV的OpenGL支持的头文件


#include <vector> // 包含标准模板库的vector(向量)容器类的头文件
#include <map> // 包含标准模板库的map(映射)容器类的头文件
#include <iostream> // 包含标准输入输出流的头文件
#include <iomanip> // 包含输入输出流的格式化操作的头文件
#include <limits> // 包含标准限制值的头文件
#include <stdint.h> // 包含标准整型定义的头文件
// 判断是否有OpenGL的支持
#ifdef HAVE_OPENGL
#ifdef _WIN32 // 如果是Windows 32位操作系统
#define WIN32_LEAN_AND_MEAN 1
#define NOMINMAX 1
#include <windows.h> // 包含Windows API的头文件
#endif
#if defined(_WIN64) // 如果是Windows 64位操作系统
#include <windows.h> // 包含Windows API的头文件
#endif


#if defined(__APPLE__) // 如果是苹果系统(MacOS)
#include <OpenGL/gl.h> // 包含OpenGL库的头文件
#include <OpenGL/glu.h> // 包含OpenGL工具库的头文件
#else // 其他操作系统
#include <GL/gl.h> // 包含OpenGL库的头文件
#include <GL/glu.h> // 包含OpenGL工具库的头文件
#endif
#endif


using namespace std; // 指定使用标准命名空间
using namespace cv; // 指定使用OpenCV命名空间


// 帮助信息函数,打印程序如何使用的说明
static void help(char** argv)
{
    cout << "\nThis program demonstrates how to use MSER to detect extremal regions\n"
            "Usage:\n"
         << argv[0] << " <image1(without parameter a synthetic image is used as default)>\n"
            "Press esc key when image window is active to change descriptor parameter\n"
            "Press 2, 8, 4, 6, +, -, or 5 keys in openGL windows to change view or use mouse\n";
}


// MSER算法参数结构体定义
struct MSERParams
{
    // MSERParams构造函数,带有默认参数
    MSERParams(int _delta = 5, int _min_area = 60, int _max_area = 14400,
               double _max_variation = 0.25, double _min_diversity = .2,
               int _max_evolution = 200, double _area_threshold = 1.01,
               double _min_margin = 0.003, int _edge_blur_size = 5)
    {
        // 初始化成员变量
        delta = _delta;
        minArea = _min_area;
        maxArea = _max_area;
        maxVariation = _max_variation;
        minDiversity = _min_diversity;
        maxEvolution = _max_evolution;
        areaThreshold = _area_threshold;
        minMargin = _min_margin;
        edgeBlurSize = _edge_blur_size;
        pass2Only = false;
    }


    // 定义MSER参数
    int delta; // Δ值,用于检测灰度变化
    int minArea; // 最小区域面积
    int maxArea; // 最大区域面积
    double maxVariation; // 最大变异系数
    double minDiversity; // 最小多样性
    bool pass2Only; // 仅进行第二阶段


    // 其他MSER参数
    int maxEvolution; // 最大演化次数
    double areaThreshold; // 区域阈值
    double minMargin; // 最小边缘
    int edgeBlurSize; // 边缘模糊尺寸
};


// 输出MSERParams参数的辅助函数,用于生成调用MSER算法的配置信息
static String Legende(const MSERParams &pAct)
{
    ostringstream ss;
    // 拼接字符串以显示提取的参数值
    ss << "Area[" << pAct.minArea << "," << pAct.maxArea << "] ";
    ss << "del. [" << pAct.delta << "] ";
    ss << "var. [" << pAct.maxVariation << "] ";
    ss << "div. [" << (int)pAct.minDiversity << "] ";
    ss << "pas. [" << (int)pAct.pass2Only << "] ";
    ss << "RGb->evo. [" << pAct.maxEvolution << "] ";
    ss << "are. [" << (int)pAct.areaThreshold << "] ";
    ss << "mar. [" << (int)pAct.minMargin << "] ";
    ss << "siz. [" << pAct.edgeBlurSize << "]";
    // 返回拼接好的字符串
    return ss.str();
}


#ifdef HAVE_OPENGL
// 特定于OpenGL的变量定义
const int win_width = 800; // OpenGL窗口宽度
const int win_height = 640; // OpenGL窗口高度
#endif


// 用于交互的全局状态变量
bool rotateEnable = true; // 是否允许旋转
bool keyPressed = false; // 是否有键盘按键按下


// 旋转操作的参数
Vec4f rotAxis(1, 0, 1, 0); // 定义旋转的轴
Vec3f zoom(1, 0, 0); // 定义缩放参数


// 观察者(摄像头)的位置参数
float obsX = 0.f; // X坐标
float obsY = 0.f; // Y坐标
float obsZ = -10.f; // Z坐标(负值表示相对于屏幕向内)
float tx = 0.f; // 平移参数X
float ty = 0.f; // 平移参数Y


// 观察者的观察角度参数
float thetaObs = -1.570f; // 仰角
float phiObs = 1.570f; // 方位角
float rObs = 10.f; // 观察半径(距离)


// 鼠标操作的前一状态参数(用于实现旋转等操作)
int prevX = -1; // 鼠标前一X坐标
int prevY = -1; // 鼠标前一Y坐标
int prevTheta = -1000; // 前一观察仰角
int prevPhi = -1000; // 前一观察方位角


// 图像上画多个嵌套矩形的函数
// img: 目标图像
// p0: 起始点坐标
// width: 包含宽度值的数组
// color: 包含颜色值的数组
// n: 要画的矩形数量
static void addNestedRectangles(Mat &img, Point p0, int* width, int *color, int n) {
    // 循环画出每个矩形
    for (int i = 0; i < n; i++)
    {
        // 画矩形
        rectangle(img, Rect(p0, Size(width[i], width[i])), Scalar(color[i]), 1);
        // 调整下一个矩形的起始点坐标
        p0 += Point((width[i] - width[i + 1]) / 2, (width[i] - width[i + 1]) / 2);
        // 填充矩形内的颜色
        floodFill(img, p0, Scalar(color[i]));
    }
}


// 图像上画多个嵌套圆的函数
// img: 目标图像
// p0: 圆心坐标
// width: 包含直径值的数组
// color: 包含颜色值的数组
// n: 要画的圆的数量
static void addNestedCircles(Mat &img, Point p0, int *width, int *color, int n) {
    // 循环画出每个圆
    for (int i = 0; i < n; i++)
    {
        // 画圆
        circle(img, p0, width[i] / 2, Scalar(color[i]), 1);
        // 填充每个圆的内部
        floodFill(img, p0, Scalar(color[i]));
    }
}


// 创建一个合成图像的函数
static Mat MakeSyntheticImage()
{
    // 定义背景色为0 
    const int fond = 0;


    // 创建一个800x800的灰度图像,初始颜色为背景色
    Mat img(800, 800, CV_8UC1);
    img = Scalar(fond);


    // 定义嵌套图形的大小数组
    int width[] = { 390, 380, 300, 290, 280, 270, 260, 250, 210, 190, 150, 100, 80, 70 };


    // 定义四组颜色数组,每个数组的颜色用于一组图形
    int color1[] = { 80, 180, 160, 140, 120, 100, 90, 110, 170, 150, 140, 100, 220 };
    int color2[] = { 81, 181, 161, 141, 121, 101, 91, 111, 171, 151, 141, 101, 221 };
    int color3[] = { 175, 75, 95, 115, 135, 155, 165, 145, 85, 105, 115, 155, 35 };
    int color4[] = { 173, 73, 93, 113, 133, 153, 163, 143, 83, 103, 113, 153, 33 };


    // 使用addNestedRectangles和addNestedCircles函数在图像上添加图形
    addNestedRectangles(img, Point(10, 10), width, color1, 13);
    addNestedCircles(img, Point(200, 600), width, color2, 13);


    addNestedRectangles(img, Point(410, 10), width, color3, 13);
    addNestedCircles(img, Point(600, 600), width, color4, 13);


    // 设置直方图的参数
    int histSize = 256; // 直方图的大小(256个灰度级)
    float range[] = { 0, 256 }; // 直方图的范围
    const float* histRange[] = { range }; // 指向直方图范围的指针数组
    Mat hist; // 创建Mat对象,用于存储直方图


    // 计算图像的直方图
    calcHist(&img, 1, 0, Mat(), hist, 1, &histSize, histRange, true, false);


    // 输出非零直方图区域的直方图值
    cout << "****************Maximal region************************\n";
    for (int i = 0; i < hist.rows; i++)
    {
        if (hist.at<float>(i, 0) != 0)
        {
            cout << "h" << setw(3) << left << i << "\t=\t" << hist.at<float>(i, 0) << "\n";
        }
    }


    // 返回生成的合成图像
    return img;
}
// 函数结束




// 主函数 - 程序的入口点
int main(int argc, char *argv[])
{
    Mat imgOrig, img; // 创建Mat对象,用于储存原始图像和处理后的图像
    Size blurSize(5, 5); // 创建Size对象,设置图像模糊的大小
    // 创建命令行解析器,用于处理程序输入
    cv::CommandLineParser parser(argc, argv, "{ help h | | }{ @input | | }");
    // 如果请求帮助则输出帮助信息并退出
    if (parser.has("help"))
    {
        help(argv); // 调用帮助信息函数
        return 0; // 程序成功退出
    }


    // 获取输入参数,如果提供了参数则尝试加载图像
    string input = parser.get<string>("@input");
    if (!input.empty())
    {
        imgOrig = imread(samples::findFile(input), IMREAD_GRAYSCALE); // 加载传入的图像文件名称参数,以灰度模式读取图像
        blur(imgOrig, img, blurSize); // 对原始图像应用模糊操作
    }
    else
    {
        imgOrig = MakeSyntheticImage(); // 如果没有输入图像,则创建一张合成的图像
        img = imgOrig; // 将合成图像赋值给处理图像
    }
    
    // 下面的代码略去了注释,它们执行MSER特征检测,并且显示结果图像
    // MSER特征描述子列表
    vector<String> typeDesc;
    // MSER参数列表
    vector<MSERParams> pMSER;
    
    // 调色板,用于随机生成颜色
    vector<Vec3b> palette;
    // 填充调色板,生成65536(2^16)种随机颜色(每种颜色由随机的RGB值组成)
    for (int i = 0; i <= numeric_limits<uint16_t>::max(); i++)
        palette.push_back(Vec3b((uchar)rand(), (uchar)rand(), (uchar)rand()));
    
    // 显示帮助信息
    help(argv);
    
    // 初始化MSER参数结构体
    MSERParams params;
    
    // 设置MSER参数
    params.delta = 10;
    params.minArea = 100;
    params.maxArea = 5000;
    params.maxVariation = 2;
    params.minDiversity = 0;
    params.pass2Only = true;
    // 将设置的MSER描述子和参数添加到相应的列表中
    typeDesc.push_back("MSER");
    pMSER.push_back(params);
    
    // 修改参数pass2Only,再次添加
    params.pass2Only = false;
    typeDesc.push_back("MSER");
    pMSER.push_back(params);
    
    // 修改参数delta,再次添加
    params.delta = 100;
    typeDesc.push_back("MSER");
    pMSER.push_back(params);
    
    // 定义MSER参数的迭代器
    vector<MSERParams>::iterator itMSER = pMSER.begin();
    // 引入Feature2D类指针,用于后续创建MSER对象
    Ptr<Feature2D> b;
    // 用于临时存储特征描述标签的字符串
    String label;
    // 特征描述符迭代器
    vector<String>::iterator itDesc;
    // 创建一个三通道的彩色图像result,用于存放处理结果
    Mat result(img.rows, img.cols, CV_8UC3);
    // 遍历描述符列表进行特征提取
    for (itDesc = typeDesc.begin(); itDesc != typeDesc.end(); ++itDesc)
    {
        // 用于存放关键点的列表
        vector<KeyPoint> keyImg1;
        // 如果当前描述子是"MSER"
        if (*itDesc == "MSER")
        {
            // 如果图像是彩色的
            if (img.type() == CV_8UC3)
            {
                // 创建MSER实例,参数从迭代器指向的MSERParams获取
                b = MSER::create(itMSER->delta, itMSER->minArea, itMSER->maxArea, itMSER->maxVariation, itMSER->minDiversity, itMSER->maxEvolution,
                                 itMSER->areaThreshold, itMSER->minMargin, itMSER->edgeBlurSize);
                // 转换参数为字符串标签
                label = Legende(*itMSER);
                // 往后移动迭代器,指向下一个参数
                ++itMSER;
            }
            else
            {
                // 如果图像不是彩色的,则不需要一些参数
                b = MSER::create(itMSER->delta, itMSER->minArea, itMSER->maxArea, itMSER->maxVariation, itMSER->minDiversity);
                // 使用dynamicCast对MSER对象进行转换,以调用非通用接口传递参数
                b.dynamicCast<MSER>()->setPass2Only(itMSER->pass2Only);
                // 转换参数为字符串标签
                label = Legende(*itMSER);
                // 往后移动迭代器,指向下一个参数
                ++itMSER;
            }
        }
    
        // 如果图像是彩色的,直接复制到结果图像
        if (img.type() == CV_8UC3)
        {
            img.copyTo(result);
        }
        else
        {
            // 如果不是彩色,将灰度图转换为彩色图再复制到结果图像
            vector<Mat> plan;
            plan.push_back(img);
            plan.push_back(img);
            plan.push_back(img);
            merge(plan, result);
        }
        try
        {
            // 待检测区域的列表
            vector<KeyPoint> keyImg;
            // 被检测到的区域位置
            vector<Rect> zone;
            // 被检测到的区域点列表
            vector<vector<Point>> region;
            // 特征描述
            Mat desc;
    
            // 如果成功转换为MSER对象
            if (b.dynamicCast<MSER>().get())
            {
                // 转换为MSER类型指针
                Ptr<MSER> sbd = b.dynamicCast<MSER>();
                // 调用detectRegions方法来检测区域
                sbd->detectRegions(img, region, zone);
                // 初始化MSER像素计数
                int nbPixelInMSER = 0;
                // 遍历每个区域
                for (auto itr = region.begin(); itr != region.end(); ++itr)
                {
                    // 遍历区域中的每个点
                    for (auto itp = itr->begin(); itp != itr->end(); ++itp)
                    {
                        // 将属于该区域的像素点在result中标记为蓝色
                        result.at<Vec3b>(itp->y, itp->x) = Vec3b(128, 0, 0);
                        // MSER像素数增加
                        nbPixelInMSER++;
                    }
                }
                // 输出MSER区域的数量和像素总数
                cout << "Number of MSER region: " << region.size() << "; Number of pixels in all MSER region: " << nbPixelInMSER << "\n";
            }
    
            // 生成窗口名,结合特征类型和参数标签
            const string winName = *itDesc + label;
            // 创建自适应大小的窗口
            namedWindow(winName, WINDOW_AUTOSIZE);
            // 在窗口中显示处理结果和原始图像
            imshow(winName, result);
            imshow("Original", img);
        }
        catch (const Exception& e) // 捕捉异常
        {
            // 输出特征和异常信息
            cout << "Feature: " << *itDesc << "\n";
            cout << e.msg << endl;
        }
    #ifdef HAVE_OPENGL // 如果支持OpenGL
        DrawOpenGLMSER(img, result); // 使用OpenGL绘制MSER结果
    #endif
        waitKey(); // 等待键盘输入
    }
    return 0;
}

该程序演示了如何使用MSER来检测极值区域

当图像窗口处于活动状态时按 esc 键可更改描述符参数

在 openGL 窗口中按 2、8、4、6、+、- 或 5 键来更改视图或使用鼠标

终端输出:

MSER区域数量:3; 所有 MSER 区域的像素数:10420

MSER区域数量:6; 所有 MSER 区域的像素数:20840

MSER区域数量:0; 所有 MSER 区域中的像素数:0

javascript 复制代码
Press esc key when image window is active to change descriptor parameter
Press 2, 8, 4, 6, +, -, or 5 keys in openGL windows to change view or use mouse
Number of MSER region: 3; Number of pixels in all MSER region: 10420
Number of MSER region: 6; Number of pixels in all MSER region: 20840
Number of MSER region: 0; Number of pixels in all MSER region: 0

MSER 算法在视频分析中有什么应用?

MSER 算法如何应用于实时视频监控系统中的特定物体或活动模式的检测和追踪?

The End

相关推荐
余炜yw35 分钟前
【LSTM实战】跨越千年,赋诗成文:用LSTM重现唐诗的韵律与情感
人工智能·rnn·深度学习
莫叫石榴姐1 小时前
数据科学与SQL:组距分组分析 | 区间分布问题
大数据·人工智能·sql·深度学习·算法·机器学习·数据挖掘
弗锐土豆1 小时前
工业生产安全-安全帽第二篇-用java语言看看opencv实现的目标检测使用过程
java·opencv·安全·检测·面部
如若1231 小时前
利用 `OpenCV` 和 `Matplotlib` 库进行图像读取、颜色空间转换、掩膜创建、颜色替换
人工智能·opencv·matplotlib
YRr YRr2 小时前
深度学习:神经网络中的损失函数的使用
人工智能·深度学习·神经网络
ChaseDreamRunner2 小时前
迁移学习理论与应用
人工智能·机器学习·迁移学习
Guofu_Liao2 小时前
大语言模型---梯度的简单介绍;梯度的定义;梯度计算的方法
人工智能·语言模型·矩阵·llama
我爱学Python!2 小时前
大语言模型与图结构的融合: 推荐系统中的新兴范式
人工智能·语言模型·自然语言处理·langchain·llm·大语言模型·推荐系统
果冻人工智能2 小时前
OpenAI 是怎么“压力测试”大型语言模型的?
人工智能·语言模型·压力测试
日出等日落2 小时前
Windows电脑本地部署llamafile并接入Qwen大语言模型远程AI对话实战
人工智能·语言模型·自然语言处理