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提取特征区域的主要内容,如果未能解决你的问题,请参考以下文章