0


OpenCV实战(15)——轮廓检测详解

OpenCV实战(15)——轮廓检测详解

0. 前言

在计算机视觉领域,轮廓通常指图像中对象边界的一系列点。因此,轮廓通常描述了对象边界的关键信息,包含了有关对象形状的主要信息,该信息可用于形状分析与对象检测和识别。本节中,我们首先介绍如何提取图像中轮廓,然后讲解如何计算轮廓的形状描述符。

1. 提取区域轮廓

1.1 轮廓提取

图像通常包含目标对象的表示,图像分析的目标之一是识别和提取这些对象。在目标检测/识别应用中,通常需要生成一个二值图像,显示目标物体的位置,提取包含在二值图像中的对象。例如,使用如下二值图像:

二值图像

我们可以通过简单的阈值操作获得此图像,然后应用开/闭形态滤波器。本节将介绍如何提取图像中的目标对象,更具体地说,我们将提取图像中的连接部分,即由二值图像中的一组连接像素组成的形状。

  1. OpenCV

提供了一个简单的函数来提取图像的连接部分的轮廓,即

  1. cv::findContours

函数。

(1) 要使用

  1. cv::findContours

函数,我们需要一个点向量存储所有输出轮廓:

  1. std::vector<std::vector<cv::Point>> contours;

(2) 使用

  1. cv::findContours

函数检测图像的所有轮廓并将它们保存在轮廓向量中:

  1. cv::findContours(image,
  2. contours,// 轮廓向量
  3. cv::RETR_EXTERNAL,// 检索外部轮廓
  4. cv::CHAIN_APPROX_NONE);// 检索每个轮廓的所有像素
  1. cv::findContours

函数的输入是二值图像,输出是一个轮廓向量,每个轮廓由一个

  1. cv::Point

对象向量表示,因此输出参数定义为

  1. std::vector

对象。此外,还指定了两个标志,第一个表示只需要外部轮廓,即忽略对象中的孔;第二个标志用于指定轮廓的格式,使用

  1. CV_CHAIN_APPROX_NONE

选项,向量将列出轮廓中的所有点,使用

  1. CV_CHAIN_APPROX_SIMPLE

标志,将仅包含水平、垂直或对角线轮廓的端点,也可以使用其他标志获取更复杂的轮廓链近似表示。使用上示图像,可以得到

  1. 10

个连通分量。

(3) 使用

  1. OpenCV

可以非常方便地在一张图片上绘制出连接部分的轮廓:

  1. cv::Mat result(image.size(), CV_8U, cv::Scalar(255));
  2. cv::drawContours(result, contours,-1,// 绘制所有轮廓
  3. cv::Scalar(0),// 颜色2);// 线宽为2

如果此函数的第

  1. 3

个参数为负值,则绘制所有轮廓,也可以使用正值指定要绘制的轮廓的索引,如下图所示:

轮廓图像

轮廓是通过系统地扫描图像直到检测出所有的目标部分,从连接部分上的起点开始,沿着它的轮廓,在其边框上标记像素;完成标记后,在最后一个位置继续扫描,直到找到新的连接部分。

(4) 然后可以单独分析识别的连接部分。例如,我们可以通过预估目标对象的预期大小消除一些无效部分,可以使用连接部分周长的最小值和最大值消除无效连接:

  1. // 消除所有过短或过长的轮廓int cmin =50;int cmax =500;
  2. std::vector<std::vector<cv::Point>>::iterator itc = contours.begin();while(itc!=contours.end()){if(itc!=contours.end()){if(itc->size()<cmin || itc->size()>cmax){
  3. itc = contours.erase(itc);}else{++itc;}}}

由于

  1. std::vector

中的每个消除操作的时间复杂度都是

  1. O
  2. (
  3. N
  4. )
  5. O(N)
  6. O(N),因此该循环可以进一步进行优化。在原图上绘制轮廓,结果如下图所示:

轮廓绘制

1.2 复杂轮廓分析

使用简单的标准就能够帮助我们识别图像中所有感兴趣的对象,在更复杂情况下,我们需要对连接部分的属性进行更精细的分析。
使用

  1. cv::findContours

函数,还可以通过在函数调用中指定

  1. CV_RETR_LIST

标志检测二值图中所有闭合轮廓(包括对象中的孔形轮廓):

  1. cv::findContours(image, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);

使用以上函数调用,可以得到以下轮廓:

轮廓

可以看到上图中增加了额外轮廓。也可以将这些轮廓组织成层次结构,主要部分是父组件,其中的孔是其子组件,如果这些孔内还有组件,它们将成为之前子组件的子组件,依此类推,该层次结构可以通过使用

  1. CV_RETR_TREE

标志获得:

  1. std::vector<cv::Vec4i> hierarchy;
  2. cv::findContours(image,
  3. contours,// 轮廓向量
  4. hierarchy,// 分层表示
  5. CV_RETR_TREE,// 使用树结构检索所有轮廓
  6. CV_CHAIN_APPROX_NONE);// 每一轮廓的所有像素

在这种情况下,每个轮廓在相同的索引处都有一个对应的层次元素,由四个整数组成。前两个整数提供了同一级别的下一个和前一个轮廓的索引,后两个整数提供该轮廓的第一个子项和父项的索引,负索引表示轮廓列表的结尾。

  1. CV_RETR_CCOMP

标志类似,但层次结构仅包括两个级别。

2. 计算区域形状描述符

连接部分通常对应于图片场景的中某个目标对象,为了识别此对象,或将其与其他图像元素进行比较,我们可能需要进行测量以提取所需特征。在本节中,我们介绍

  1. OpenCV

中可用的形状描述符,用于描述轮廓形状。
有多个

  1. OpenCV

函数可用作形状描述符,应用这些函数可以提取连接部分。我们使用目标对象相对应的轮廓向量,计算轮廓上(

  1. contours[0]

  1. contours[3]

)的形状描述符并在轮廓图像(线宽为

  1. 1

)上绘制结果(线宽为

  1. 2

)。

(1)

  1. boundingRect

函数用于计算矩形边框:

  1. // 矩形
  2. cv::Rect r0 = cv::boundingRect(contours[0]);
  3. cv::rectangle(result, r0,0,2);

(2)

  1. minEnclosingCircle

函数用于近似最小包围圆:

  1. // 圆形float radius;
  2. cv::Point2f center;
  3. cv::minEnclosingCircle(contours[1], center, radius);
  4. cv::circle(result, center,static_cast<int>(radius),0,2);

(3) 区域轮廓的多边形近似使用

  1. approxPolyDP

函数:

  1. // 近似多边形
  2. std::vector<cv::Point> poly;
  3. cv::approxPolyDP(contours[2], poly,5,true);
  4. cv::polylines(result, poly,true,0,2);
  5. std::cout <<"Polygon size: "<< poly.size()<< std::endl;

多边形绘制函数

  1. cv::polylines

与其他绘图函数类似,第

  1. 3

个参数为布尔类型用于指示轮廓是否闭合,如果为true,则将最后一个点连接到第一个点。

(4) 凸包函数

  1. convexHull

是多边形近似的另一种形式:

  1. // 凸包
  2. std::vector<cv::Point> hull;
  3. cv::convexHull(contours[3], hull);
  4. cv::polylines(result, hull,true,0,2);

(5) 矩是另一个强大的描述符,可以计算区域内的质心:

  1. // 矩
  2. itc = contours.begin();while(itc!=contours.end()){
  3. cv::Moments mom = cv::moments(*itc++);
  4. cv::circle(result,
  5. cv::Point(mom.m10/mom.m00, mom.m01/mom.m00),2, cv::Scalar(0),2);}

结果图像如下:

形状描述符

边界框大多数情况下是表示和定位图像中目标对象的最紧凑的方式,其定义为完全包含对象形状的最小尺寸的矩形。边界框的高度和宽度可以指示对象的垂直或水平尺寸,例如,可以使用高宽比来区分汽车和行人;当只需要目标的近似尺寸和位置时,通常使用最小包围圆。
当想要与目标对象形状相似的紧凑的表示时,可以采用多边形近似,通过指定精度参数(

  1. cv::approxPolyDP

函数中的第

  1. 4

个参数)指定目标对象形状与近似多边形之间的最大可接受距离,函数返回的

  1. cv::Point

的向量对应于多边形的顶点。为了绘制这个多边形,我们需要遍历向量并在它们之间线段将相邻点连接起来。
形状的凸包或凸包络是包含形状的最小凸多边形,可以将其想象为弹性皮筋围在目标对象周围时的形状,凸包轮廓将在对象形状轮廓的凹面位置偏离原始轮廓,这些位置通常称为凸面缺陷,并且可以使用

  1. OpenCV

函数

  1. cv::convexityDefects

识别这些缺陷,调用方式如下所示:

  1. std::vector<cv::Vec4i> defects;
  2. cv::convexityDefects(contours[3], hull, defects);
  1. contour

  1. hull

参数分别是原始轮廓和凸包轮廓(均为

  1. std::vector<cv::Point>

实例)。输出是由四个整数元素组成的向量,前两个整数是轮廓上的点索引,用于界定缺陷;第三个整数对应凹面内最远的点,最后一个整数对应这个最远点到凸包的距离。
矩是形状结构分析中常用的数学工具,

  1. OpenCV

定义了一个封装了形状所有计算矩的数据结构,

  1. cv::moments

函数的返回值就使用这种数据结构,这些矩构成了对物体形状的简洁描述。我们可以使用这个结构中前3个空间矩来获得形状的质心。
也可以使用

  1. OpenCV

函数计算结构属性,

  1. cv::minAreaRect

函数计算最小的封闭旋转矩形;

  1. cv::contourArea

函数估计轮廓(内部像素数)的面积;

  1. cv::pointPolygonTest

函数用于确定一个点是在轮廓内部还是外部,而

  1. cv::matchShapes

可以测量两个轮廓之间的相似性。我们可以通过组合所有这些属性进行更高级的图像结构分析。

2.1 四边形检测

我们可以利用形态学操作转换后获得的图像提取图像形状,假设,我们使用形态学操作转换图像获得的

  1. MSER

结果,然后构建算法检测图像中的四边形分量。假设我们检测以下使用

  1. MSER

算法得到的二值图像,检测四边形分量能够帮助我们识别建筑物上的窗户等,为了减少图像中的噪音,我们使用了一些形态滤波器对图像进行预处理:

  1. // 创建二值图像
  2. components = components==255;// 图像开操作
  3. cv::morphologyEx(components, components, cv::MORPH_OPEN, cv::Mat(), cv::Point(-1,-1),3);

接下来,获取轮廓:

  1. // 反转图像
  2. cv::Mat componentsInv =255- components;// 获取轮廓和连接部分
  3. cv::findContours(componentsInv, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);

最后,遍历所有轮廓并用多边形近似:

  1. cv::Mat quadri(components.size(), CV_8U,255);
  2. std::vector<std::vector<cv::Point>>::iterator it = contours.begin();while(it!= contours.end()){
  3. poly.clear();// 使用多边形近似轮廓
  4. cv::approxPolyDP(*it,poly,5,true);// 检测轮廓是否为四边形if(poly.size()==4){
  5. cv::polylines(quadri, poly,true,0,2);}++it;}

检测结果如下所示:

四边形描述符

如果想要检测矩形,我们可以测量相邻边之间的角度并消除掉偏差过大(与

  1. 90

度相比)的四边形。

3. 完整代码

完整代码文件

  1. blobs.cpp

如下所示:

  1. #include<iostream>#include<vector>#include<opencv2/core/core.hpp>#include<opencv2/imgproc/imgproc.hpp>#include<opencv2/highgui/highgui.hpp>intmain(){// 读取二进制图像
  2. cv::Mat image = cv::imread("binary.png",0);if(!image.data)return0;
  3. cv::namedWindow("Binary Image");
  4. cv::imshow("Binary Image", image);// 获取轮廓和连接部分
  5. std::vector<std::vector<cv::Point>> contours;
  6. cv::findContours(image,
  7. contours,// 轮廓向量
  8. cv::RETR_EXTERNAL,// 检索外部轮廓
  9. cv::CHAIN_APPROX_NONE);// 检索每个轮廓的所有像素
  10. std::cout <<"Contours: "<< contours.size()<< std::endl;
  11. std::vector<std::vector<cv::Point>>::const_iterator itContours = contours.begin();for(; itContours!=contours.end();++itContours){
  12. std::cout <<"Size: "<< itContours->size()<< std::endl;}// 绘制轮廓
  13. cv::Mat result(image.size(), CV_8U, cv::Scalar(255));
  14. cv::drawContours(result, contours,-1,// 绘制所有轮廓
  15. cv::Scalar(0),// 颜色2);// 线宽为2
  16. cv::namedWindow("Contours");
  17. cv::imshow("Contours", result);// 消除所有过短或过长的轮廓int cmin =50;int cmax =500;
  18. std::vector<std::vector<cv::Point>>::iterator itc = contours.begin();while(itc!=contours.end()){if(itc!=contours.end()){if(itc->size()<cmin || itc->size()>cmax){
  19. itc = contours.erase(itc);}else{++itc;}}}// 绘制轮廓
  20. cv::Mat original = cv::imread("2.png");
  21. cv::drawContours(original, contours,-1, cv::Scalar(0,0,255),2);
  22. cv::namedWindow("Contours on Animals");
  23. cv::imshow("Contours on Animals",original);
  24. result.setTo(cv::Scalar(255));
  25. cv::drawContours(result, contours,-1,0,1);
  26. image = cv::imread("binary.png",0);// 矩形
  27. cv::Rect r0 = cv::boundingRect(contours[0]);
  28. cv::rectangle(result, r0,0,2);// 圆形float radius;
  29. cv::Point2f center;
  30. cv::minEnclosingCircle(contours[1], center, radius);
  31. cv::circle(result, center,static_cast<int>(radius),0,2);// 近似多边形
  32. std::vector<cv::Point> poly;
  33. cv::approxPolyDP(contours[2], poly,5,true);
  34. cv::polylines(result, poly,true,0,2);
  35. std::cout <<"Polygon size: "<< poly.size()<< std::endl;// 凸包
  36. std::vector<cv::Point> hull;
  37. cv::convexHull(contours[3], hull);
  38. cv::polylines(result, hull,true,0,2);// std::vector<cv::Vec4i> defects;// cv::convexityDefects(contours[3], hull, defects);// 矩
  39. itc = contours.begin();while(itc!=contours.end()){
  40. cv::Moments mom = cv::moments(*itc++);
  41. cv::circle(result,
  42. cv::Point(mom.m10/mom.m00, mom.m01/mom.m00),2, cv::Scalar(0),2);}
  43. cv::namedWindow("Some Shape descriptors");
  44. cv::imshow("Some Shape descriptors", result);
  45. image = cv::imread("binary.png",0);
  46. cv::findContours(image, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
  47. result.setTo(255);
  48. cv::drawContours(result, contours,-1,0,2);
  49. cv::namedWindow("All Contours");
  50. cv::imshow("All Contours", result);// MSER 图像
  51. cv::Mat components;
  52. components = cv::imread("mser.png",0);// 创建二值图像
  53. components = components==255;// 图像开操作
  54. cv::morphologyEx(components, components, cv::MORPH_OPEN, cv::Mat(), cv::Point(-1,-1),3);
  55. cv::namedWindow("MSER image");
  56. cv::imshow("MSER image", components);
  57. contours.clear();// 反转图像
  58. cv::Mat componentsInv =255- components;// 获取轮廓和连接部分
  59. cv::findContours(componentsInv, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
  60. cv::Mat quadri(components.size(), CV_8U,255);
  61. std::vector<std::vector<cv::Point>>::iterator it = contours.begin();while(it!= contours.end()){
  62. poly.clear();// 使用多边形近似轮廓
  63. cv::approxPolyDP(*it,poly,5,true);// 检测轮廓是否为四边形if(poly.size()==4){
  64. cv::polylines(quadri, poly,true,0,2);}++it;}
  65. cv::namedWindow("MSER quadrilateral");
  66. cv::imshow("MSER quadrilateral", quadri);
  67. cv::waitKey();return0;}

小结

在本文中,首先介绍了轮廓的相关概念,然后了解利用

  1. cv::findContours()

检测轮廓、

  1. cv::drawContours()

绘制轮廓,在获取轮廓后,我们可以计算轮廓的形状描述符。

系列链接

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实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取


本文转载自: https://blog.csdn.net/LOVEmy134611/article/details/128833287
版权归原作者 盼小辉丶 所有, 如有侵权,请联系我们删除。

“OpenCV实战(15)——轮廓检测详解”的评论:

还没有评论