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

7cae2c56dcfebfb0311809f4df040d7a.png

f6e56d9a7ed4a0c755bd860fed3011e5.png

180e4add7fc8fe1a796ff866c58315c0.png

4b506fa8be086aa76831f0ab1373fa7c.png

0cf9ec361c492ae8fa305057a633811b.png

#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

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 算法在视频分析中有什么应用?

4ecbe0f09295f7e801d825c9d95dec3a.png

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

b33fffed1701ccf8bf5cf99a778dbe26.png

The End

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值