OpenCV实战——使用MSER提取特征区域

Posted 盼小辉丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV实战——使用MSER提取特征区域相关的知识,希望对你有一定的参考价值。

OpenCV实战——使用MSER提取特征区域

0. 前言

分水岭算法一节中,我们了解了如何通过创建分水岭将图像分割成多个区域。最大稳定极值区域 (maximally stable external regions, MSER) 算法同样使用注水过程类比提取图像中的特征区域,这些区域同样通过逐级淹没图像来创建,但我们将重点关注在浸入过程中保持相对稳定的盆地,这些区域对应于图像中目标对象的特征部分。
OpenCV 中可以使用 cv::MSER 类计算图像 MSER,使用默认的空构造函数创建 cv::MSER 类的实例。

1. MSER 算法原理

MSER 算法使用与分水岭算法相同的机制,也就是说,它同样通过将图像从级别 0 逐渐淹没到级别 255,随着水位的增加,可以观察到边界清晰、颜色较深的区域形成了一段时间内形状相对稳定的盆地,这些稳定的盆地是 MSER。算法测量每个级别的连接区域以及它们的稳定性来检测 MSER,检测过程通过比较一个区域的当前面积与该区域在水位下降了 delta 值时的面积来实现,当这种相对变化达到局部最小值时,该区域被识别为 MSER 之一。
用于衡量相对稳定性的 delta 值是 cv::MSER 类的构造函数中的第 1 个参数,它的默认值为 5;接下来的两个构造参数用于指定可接受的最小和最大区域大小,用以将区域的大小限制在某个预定义的范围内;第 4 个参数用于保证 MSER 是稳定的,即 MSER 形状的相对变化足够小,稳定区域可以包含在较大的区域(称为父区域,parent regions )中,MSER 的最大允许变化默认值为 0.25;此外,父 MSER 必须与其子MSER有足够大的不同,即满足多样性标准,由 cv::MSER 构造函数的第 5 个参数指定,父 MSER 的最小多样性默认值为 0.2

2. 实现 MSER 算法

(1) 我们可以通过指定检测区域的最小和最大大小来初始化 MSER 类,以限制它们的数量:

// MSER 检测器
cv::Ptr<cv::MSER> ptrMSER = cv::MSER::create(
    5,      // 局部极小值检测的 delta 值
    200,    // 最小可接受面积
    2000    // 最大可接受面积
);

(2) 可以通过指定输入图像和合适的输出数据结构,调用函子获得 MSER

// 点集向量
std::vector<std::vector<cv::Point> > points;
// 矩形向量
std::vector<cv::Rect> rects;
// 检测 MSER 特征
ptrMSER->detectRegions(image, points, rects);

(3) 使用 MSER 可以得到由组成每个区域的像素点表示的区域向量。为了可视化结果,我们创建一个空白图像,并以不同颜色(随机选择)显示检测到的区域:

// 创建空白图像
cv::Mat output(image.size(), CV_8UC3);
output = cv::Scalar(255, 255, 255);
// OpenCV 随机数生成器
cv::RNG rng;
// 对于每个检测到的 MSER 特征区域使用不同颜色标示
for (std::vector<std::vector<cv::Point> >::reverse_iterator it=points.rbegin();
                it!=points.rend(); ++it) 
    // 生成随机颜色
    cv::Vec3b c(rng.uniform(0, 254), rng.uniform(0, 254), rng.uniform(0, 254));
    std::cout << "MSER size = " << it->size() << std::endl;
    // 遍历 MSER 集合中的每个点
    for (std::vector<cv::Point>::iterator itPts=it->begin();
                itPts!=it->end(); ++itPts) 
        if (output.at<cv::Vec3b>(*itPts)[0]==255) 
            output.at<cv::Vec3b>(*itPts) = c;
        
    

MSER 可以形成区域层次结构。因此,为了使所有区域都可见,我们并不覆盖包含在较大区域中的小区域:

生成的图像如下所示:

通过检测到的结果,可以能够从该图像中提取一些有意义的区域,例如,人物眼睛、头发等。
MSER 检测器的输出是一个点集向量。由于我们通常更加关注整个区域而非其单个像素位置,因此通常使用能够描述 MSER 位置和大小的几何形状来表示 MSER,椭圆是一种常用的表示形状。为了获得这些椭圆边界,我们需要使用两个 OpenCV 函数:第一个是 cv::minAreaRect 函数,它返回能够包含集合中所有点且面积最小的矩形,可以使用 cv::RotatedRect 实例描述这个矩形;得到边界矩形后,可以使用 cv::ellipse 函数在矩形中绘制内接椭圆。我们此过程封装在一个类中,该类的构造函数基本上与 cv::MSER 类的构造函数相同:

class MSERFeatures 
    private:
        cv::MSER mser;          // mser 检测器
        double minAreaRatio;    // 用于消除细长矩形
    public:
        MSERFeatures (
            int minArea=60, int maxArea=14400,      // 可接受的面积范围
            double minAreaRatio=50,                 // (MSER 区域面积/边界矩形面积)的最小值
            int delta=5,                            // 用于稳定性测量的 delta 值
            double maxVariation=0.25,               // 面积变化的最大值
            double minDiversity=0.2                 // 子级和父级之间的最小面积差异 
        ) : mser(delta, minArea, maxArea,
                maxVariation, minDiversity),
                minAreaRatio(minAreaRatio) 

额外的参数 minAreaRatio 用于消除边界矩形面积与其所表示的 MSER 有较大不同的 MSER,可以去除无关的细长形状。边界矩形列表通过以下方法计算:

// 计算旋转矩形边界框
void getBoundingRects(const cv::Mat& image, std::vector<cv::RotatedRect>& rects) 
    // 检测 MSER 特征
    std::vector<std::vector<cv::Point> > points;
    mser(image, points);
    // 循环检测到的每个特征
    for (std::vector<std::vector<cv::Point> >::iterator it=points.begin();
            it!points.end(); ++it) 
        // 提取矩形边界
        cv::RotatedRect rr = cv::minAreaRect(*it);
        if (it->size()>minAreaRatio*rr.size.area())
            rects.push_back(rr);
        
    

在图像上绘制相应的椭圆:

// 为每个 MSER 绘制相应的旋转椭圆
cv::Mat getImageOfEllipses(const cv::Mat& image, 
        std::vector<cv::RotatedRect>& rects,
        cv::Scalar color=255) 
    cv::Mat output = image.clone();
    // 获取 MSER 特征
    getBoundingRects(image, rects);
    // 循环检测到的每个特征
    for (std::vector<cv::RotatedRect>::iterator it = rects.begin();
            it!=rects.end(); ++it) 
        cv::ellipse(output, *it, color);
    
    return output;

得到 MSER 的检测结果如下:

// 创建MSER特征检测器实例 
MSERFeatures mserF(200,  // 最小面积 
                    1500, // 最大面积
                    0.5); // 面积比例阈值
                            // 使用默认 delta 值

// 旋转矩形的边界向量 
std::vector<cv::RotatedRect> rects;
// 检测并返回图像
cv::Mat result= mserF.getImageOfEllipses(image,rects);

在图像中应用此函数,可以得到以下结果:

将此结果与之前的结果进行比较,可以看出这种表示方式更容易解释,在图中,子级和父级 MSER 通常用相似的椭圆表示。在某些情况下,我们需要对这些椭圆应用最小变化标准消除重复表示。

3. 完整代码

完整代码 (mserFeatures.cpp) 如下所示:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <vector>

int main() 
    // 读取输入图像
    cv::Mat image = cv::imread("3.png", 0);
    if (!image.data) return 0;
    cv::namedWindow("Image");
    cv::imshow("Image", image);
    // MSER 检测器
    cv::Ptr<cv::MSER> ptrMSER = cv::MSER::create(
        5,      // 局部极小值检测的 delta 值
        200,    // 最小可接受面积
        2000    // 最大可接受面积
    );
    // 点集向量
    std::vector<std::vector<cv::Point> > points;
    // 矩形向量
    std::vector<cv::Rect> rects;
    // 检测 MSER 特征
    ptrMSER->detectRegions(image, points, rects);
    std::cout << points.size() << " MSERs detected" << std::endl;
    // 创建空白图像
    cv::Mat output(image.size(), CV_8UC3);
    output = cv::Scalar(255, 255, 255);
    // OpenCV 随机数生成器
    cv::RNG rng;
    // 对于每个检测到的 MSER 特征区域使用不同颜色标示
    for (std::vector<std::vector<cv::Point> >::reverse_iterator it=points.rbegin();
                    it!=points.rend(); ++it) 
        // 生成随机颜色
        cv::Vec3b c(rng.uniform(0, 254), rng.uniform(0, 254), rng.uniform(0, 254));
        std::cout << "MSER size = " << it->size() << std::endl;
        // 遍历 MSER 集合中的每个点
        for (std::vector<cv::Point>::iterator itPts=it->begin();
                    itPts!=it->end(); ++itPts) 
            if (output.at<cv::Vec3b>(*itPts)[0]==255) 
                output.at<cv::Vec3b>(*itPts) = c;
            
        
    
    cv::namedWindow("MSER point sets");
    cv::imshow("MSER point sets", output);
    cv::imwrite("mser.png", output);
    // 提取并显示矩形 MSER
    std::vector<cv::Rect>::iterator itr = rects.begin();
    std::vector<std::vector<cv::Point> >::iterator itp = points.begin();
    for (; itr!=rects.end(); ++itr, ++itp) 
        if (static_cast<double>(itp->size())/itr->area()>0.6)
            cv::rectangle(image, *itr, cv::Scalar(255), 2);
        
    
    cv::namedWindow("Rectangular MSERs");
    cv::imshow("Rectangular MSERs", image);
    // 重新加载输入图像
    image = cv::imread("3.png", 0);
    if (!image.data) return 0;
    // 提取并显示椭圆 MSER
    for (std::vector<std::vector<cv::Point> >::iterator it=points.begin();
                it!=points.end(); ++it)
        // 遍历 MSER 集合中的每个点
        for (std::vector<cv::Point>::iterator itPts=it->begin();
                itPts!=it->end(); ++itPts) 
            // 提取边界框
            cv::RotatedRect rr = cv::minAreaRect(*it);
            if (rr.size.height/rr.size.width > 0.6 || rr.size.height/rr.size.width < 1.6) 
                cv::ellipse(image, rr, cv::Scalar(255), 2);
            
        
    
    cv::namedWindow("MSER ellipses");
    cv::imshow("MSER ellipses", image);
    /*
    // 使用 mserFeatures 类进行检测

    // 创建MSER特征检测器实例 
    MSERFeatures mserF(200,  // 最小面积 
                        1500, // 最大面积
                        0.5); // 面积比例阈值
                                // 使用默认 delta 值

    // 旋转矩形的边界向量 
    std::vector<cv::RotatedRect> rects;
    // 检测并返回图像
    cv::Mat result= mserF.getImageOfEllipses(image,rects);
    cv::namedWindow("MSER regions");
    cv::imshow("MSER regions",result);
    */
    cv::waitKey();
    return 0;

相关链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战——基于分水岭算法的图像分割

以上是关于OpenCV实战——使用MSER提取特征区域的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV 例程 300篇247. 特征检测之最大稳定极值区域(MSER)

有没有人在 OpenCV 中使用 MSER 来检测区域?

opencv mser算法框出图片文字区域

请问大虾们,opencv如何只对图像选择区域提取特征点

最大稳定极值区域(MSER)检测

OpenCV 例程200篇224. 特征提取之提取骨架