由于噪声和光照的影响,物体的轮廓会出现不规则的形状,根据不规则的轮廓形状不利于对图像内容进行分析,此时需要将物体的轮廓拟合成规则的几何形状,根据需求可以将图像轮廓拟合成矩形、多边形等。本小节将介绍OpenCV 4中提供的轮廓外接多边形函数,实现图像中轮廓的形状拟合。

矩形是常见的几何形状,矩形的处理和分析方法也较为简单,OpenCV 4提供了两个函数求取轮廓外接矩形,分别是求取轮廓最大外接矩形的boundingRect()函数和求取轮廓最小外接矩形的minAreaRect()函数。

寻找轮廓外接最大矩形就是寻找轮廓X方向和Y方向两端的像素,该矩形长和宽分别与图像的两个轴平行。boundingRect()函数可以实现这个功能,该函数的函数原型在代码清单7-19中给出。

  •  
代码清单7-19 boundingRect()函数原型Rect cv::boundingRect(InputArray  array)	
  • array:输入的灰度图像或者2D点集,数据类型为vector或者Mat。

该函数可以求取包含输入图像中物体轮廓或者2D点集的最大外接矩形,函数只有一个参数,可以是灰度图像或者2D点集,灰度图像的参数类型为Mat,2D点集的参数类型为vector或者Mat。该函数的返回值是一个Rect类型的变量,该变量可以直接用rectangle()函数绘制矩形。返回值共有四个参数,前两个参数是最大外接矩形左上角第一个像素的坐标,后两个参数分别表示最大外接矩形的宽和高。

最小外接矩形的四个边都与轮廓相交,该矩形的旋转角度与轮廓的形状有关,多数情况下矩形的四个边不与图像的两个轴平行。minAreaRect()函数可以求取轮廓的最小外接矩形,该函数的函数原型在代码清单7-20中给出。

  •  
代码清单7-20 minAreaRect()函数原型RotatedRect cv::minAreaRect(InputArray  points)
  • points:输入的2D点集合

该函数可以根据输入的2D点集合计算最小的外接矩形,函数的返回值是RotatedRect类型的变量,含有矩形的中心位置、矩形的宽和高和矩形旋转的角度。RotatedRect类具有两个重要的方法和属性,可以输出矩形的四个顶点和中心坐标。输出四个顶点坐标的方法是points(),假设RotatedRect类的变量为rrect,可以通过rrect.points(points)命令进行读取,其中坐标存放的变量是Point2f类型的数组。输出矩形中心坐标的属性是center,假设RotatedRect类的变量为rrect,可以通过opt=rrect.center命令进行读取,其中坐标存放的变量是Point2f类型的变量。

为了了解两个外接矩形函数的使用方法,代码清单7-21中给出了提取轮廓外接矩形的示例程序。程序中首先利用Canny算法提取图像边缘,之后通过膨胀算法将邻近的边缘连接成一个连通域,然后提取图像的轮廓,并提取每一个轮廓的最大外接矩形和最小外接矩形,最后在图像中绘制出矩形轮廓,程序的运行结果在图7-20给出。

  •  
代码清单7-21 myRect.cpp计算轮廓外接矩形#include <opencv2/opencv.hpp>#include <iostream>#include <vector>
using namespace cv;using namespace std;
int main(){ Mat img = imread("stuff.jpg"); if (img.empty()) { cout << "请确认图像文件名称是否正确" << endl; return -1; } Mat img1, img2; img.copyTo(img1); //深拷贝用来绘制最大外接矩形 img.copyTo(img2); //深拷贝迎来绘制最小外接矩形 imshow("img", img);
// 去噪声与二值化 Mat canny; Canny(img, canny, 80, 160, 3, false); imshow("", canny);
//膨胀运算,将细小缝隙填补上 Mat kernel = getStructuringElement(0, Size(3, 3)); dilate(canny, canny, kernel);
// 轮廓发现与绘制 vector<vector<Point>> contours; vector<Vec4i> hierarchy; findContours(canny, contours, hierarchy, 0, 2, Point());
//寻找轮廓的外接矩形 for (int n = 0; n < contours.size(); n++) { // 最大外接矩形 Rect rect = boundingRect(contours[n]); rectangle(img1, rect, Scalar(0, 0, 255), 2, 8, 0);
// 最小外接矩形 RotatedRect rrect = minAreaRect(contours[n]); Point2f points[4]; rrect.points(points); //读取最小外接矩形的四个顶点 Point2f cpt = rrect.center; //最小外接矩形的中心
// 绘制旋转矩形与中心位置 for (int i = 0; i < 4; i++) { if (i == 3) { line(img2, points[i], points[0], Scalar(0, 255, 0), 2, 8, 0); break; } line(img2, points[i], points[i + 1], Scalar(0, 255, 0), 2, 8, 0); } //绘制矩形的中心 circle(img, cpt, 2, Scalar(255, 0, 0), 2, 8, 0); }
//输出绘制外接矩形的结果 imshow("max", img1); imshow("min", img2); waitKey(0); return 0;}
【从零学习OpenCV 4】轮廓外接多边形_轮廓外接多边形
图7-20 myRect.cpp程序运行结果

有时候用矩形逼近轮廓会造成较大的误差,例如图7-20中对于圆形轮廓的逼近矩形围成的面积比真实轮廓面积大,如果寻找逼近轮廓的多边形,那么多边形围成的面积会更加接近真实的圆形轮廓面积。OpenCV 4提供了approxPolyDP()函数用于寻找逼近轮廓的多边形,该函数的函数原型在代码清单7-22中给出。

  •  
代码清单7-22 approxPolyDP()函数原型void cv::approxPolyDP(InputArray  curve,                      OutputArray  approxCurve,                      double  epsilon,                      bool  closed                       )
  • curve:输入轮廓像素点。
  • approxCurve:多边形逼近结果,以多边形顶点坐标的形式给出。
  • epsilon:逼近的精度,即原始曲线和逼近曲线之间的最大距离。
  • closed:逼近曲线是否为封闭曲线的标志, true表示曲线封闭,即最后一个顶点与第一个顶点相连。

该函数根据输入的轮廓得到最佳的逼近多边形。函数的第一个参数是输入的轮廓2D像素点,数据类型是vector或者Mat。第二个参数是多边形的逼近结果,以多边形顶点坐标的形式输出,是CV_32SC2类型的N×1的Mat类矩阵,可以通过输出结果的顶点数目初步判断轮廓的几何形状。第三个参数是多边形逼近时的精度,即原始曲线和逼近曲线之间的最大距离。第四个参数是逼近曲线是否为封闭曲线的标志, true表示曲线封闭,即最后一个顶点与第一个顶点相连。

为了了解该函数用法,在代码清单7-23中给出了对多个轮廓进行多边形逼近的示例程序。程序中首先提取了图像的边缘,然后对边缘进行腐蚀运算将靠近的边缘变成一个连通域,之后对边缘结果进行轮廓检测,并对每个轮廓进行多边形逼近,将逼近结果绘制在原图像中,并通过判断逼近多边形的顶点数目识别轮廓的形状,程序运行结果在图7-21给出。

  •  
代码清单7-23 myApproxPolyDP.cpp多边形轮廓拟合#include <opencv2/opencv.hpp>#include <iostream>#include <vector>
using namespace cv;using namespace std;
//绘制轮廓函数void drawapp(Mat result, Mat img2){ for (int i = 0; i < result.rows; i++) { //最后一个坐标点与第一个坐标点连接 if (i == result.rows - 1) { Vec2i point1 = result.at<Vec2i>(i); Vec2i point2 = result.at<Vec2i>(0); line(img2, point1, point2, Scalar(0, 0, 255), 2, 8, 0); break; } Vec2i point1 = result.at<Vec2i>(i); Vec2i point2 = result.at<Vec2i>(i + 1); line(img2, point1, point2, Scalar(0, 0, 255), 2, 8, 0); }}
int main(int argc, const char *argv[]){ Mat img = imread("approx.png"); if (img.empty()) { cout << "请确认图像文件名称是否正确" << endl; return -1; } // 边缘检测 Mat canny; Canny(img, canny, 80, 160, 3, false); //膨胀运算 Mat kernel = getStructuringElement(0, Size(3, 3)); dilate(canny, canny, kernel);
// 轮廓发现与绘制 vector<vector<Point>> contours; vector<Vec4i> hierarchy; findContours(canny, contours, hierarchy, 0, 2, Point());
//绘制多边形 for (int t = 0; t < contours.size(); t++) { //用最小外接矩形求取轮廓中心 RotatedRect rrect = minAreaRect(contours[t]); Point2f center = rrect.center; circle(img, center, 2, Scalar(0, 255, 0), 2, 8, 0);
Mat result; approxPolyDP(contours[t], result, 4, true); //多边形拟合 drawapp(result, img); cout << "corners : " << result.rows << endl;
//判断形状和绘制轮廓 if (result.rows == 3) { putText(img, "triangle", center, 0, 1, Scalar(0, 255, 0), 1, 8); } if (result.rows == 4) { putText(img, "rectangle", center, 0, 1, Scalar(0, 255, 0), 1, 8); } if (result.rows == 8) { putText(img, "poly-8", center, 0, 1, Scalar(0, 255, 0), 1, 8); } if (result.rows > 12) { putText(img, "circle", center, 0, 1, Scalar(0, 255, 0), 1, 8); } } imshow("result", img); waitKey(0); return 0;}
【从零学习OpenCV 4】轮廓外接多边形_轮廓外接多边形_02
图7-21 myApproxPolyDP.cpp程序中多边形拟合结果
从零学习OpenCV 4往期推荐

【从零学习OpenCV 4】Windows系统中安装OpenCV 4

【从零学习OpenCV 4】Ubuntu系统中安装OpenCV 4

【从零学习OpenCV 4】opencv_contrib扩展模块的安装

【从零学习OpenCV 4】Mat类介绍

【从零学习OpenCV 4】Mat类构造与赋值

【从零学习OpenCV 4】Mat类支持的运算

【从零学习OpenCV 4】这4种读取Mat类元素的的方法你都知道么?

【从零学习OpenCV 4】namedWindow函数&imshow函数的使用

【从零学习OpenCV 4】颜色模型与转换

【从零学习OpenCV 4】多通道分离与合并

【从零学习OpenCV 4】图像像素统计

【从零学习OpenCV 4】两图像间的像素操作

【从零学习OpenCV 4】图像二值化

【从零学习OpenCV 4】LUT查找表

【从零学习OpenCV 4】图像仿射变换

【从零学习OpenCV 4】图像透视变换

【从零学习OpenCV 4】极坐标变换

【从零学习OpenCV 4】绘制几何图形

【从零学习OpenCV 4】图像金字塔

【从零学习OpenCV 4】创建图像窗口滑动条

【从零学习OpenCV 4】鼠标响应

【从零学习OpenCV 4】图像直方图绘制

【从零学习OpenCV 4】直方图归一化

【从零学习OpenCV 4】直方图比较

【从零学习OpenCV 4】直方图均衡化

【从零学习OpenCV 4】直方图匹配

【从零学习OpenCV 4】图像卷积

【从零学习OpenCV 4】图像中添加椒盐噪声

【从零学习OpenCV 4】图像中添加高斯噪声

【从零学习OpenCV 4】均值滤波

【从零学习OpenCV 4】方框滤波

【从零学习OpenCV 4】高斯滤波

【从零学习OpenCV 4】双边滤波

【从零学习OpenCV 4】边缘检测原理

【从零学习OpenCV 4】Sobel算子 

【从零学习OpenCV 4】Scharr算子

【从零学习OpenCV 4】Laplacian算子

【从零学习OpenCV 4】Canny算法

【从零学习OpenCV 4】图像距离变换

【OpenCV 4开发详解】图像连通域分析

【从零学习OpenCV 4】轮廓外接多边形_轮廓外接多边形_03