前言:
本文我们来学习矩形形状的拟合以及周长、面积的计算。
一、点集的最小外包
点集是指坐标点的集。已知二维笛卡尔坐标系中的很多坐标点,需要找到包围这些坐标点的最小外包四边形或者圆,在这里最小指的是最小面积。如下图所示:
在OpenCV中,通过一系列的点(即点集)去找到这三类最小外包几何单元都有相应的函数可以实现。
1.1 最小外包旋转矩形
OpenCV提供了两个关于矩形的类:一个是关于直立矩形的Rect;另一个是关于旋转矩形的RotatedRect,它们的内部定义如下所示:
typedef struct Rect
{
int x; // 方形的最左角的 x坐标
int y; // 方形的最上或者最下角的 y坐标
int width; // 宽
int height; // 高
}
Rect;
以及
class CV_EXPORTS RotatedRect
{
public:
//构造函数
RotatedRect();
RotatedRect(const Point2f& center, const Size2f& size, float angle);
RotatedRect(const CvBox2D& box);
//返回矩形的4个顶点
void points(Point2f pts[]) const;
//返回包含旋转矩形的最小矩形
Rect boundingRect() const;
//转换到旧式的cvbox2d结构
operator CvBox2D() const;
Point2f center; //矩形的质心
Size2f size; //矩形的边长
float angle; //旋转角度,当角度为0、90、180、270等时,矩形就成了一个直立的矩形
};
因此从源代码中可以看出来,只需要三个要素就可以确定一个旋转矩形,它们是中心坐标、尺寸(宽、高)和旋转角度。对于RotatedRect,OpenCV并没有提供类似于画直立矩形的函数rectangle,可以通过画四条边的方式画出该旋转矩形。但是要想画出矩形的四条边,就得知道其四个顶点的坐标,OpenCV提供了函数:
RotatedRect minAreaRect(InputArray points)
详细描述如下:
返回输入点集points的最小外包旋转矩形。对于该函数的C++API,参数points接收三种点集形式,其中第一种是N*2的Mat类型,指每一行代表一个点的坐标且数据类型只能是CV_32S或者CV_32F;第二种输入类型是vector<Point>或者vector<Point2f>,即多个点组成的向量;第三种是N*1的双通道Mat类型。
举例:求五个坐标点(4,2)、(1,1)、(5,2)、(8,2)、(10,6)的最小外包旋转矩形。C++API的使用代码如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//我们创建各个点的Point
Point2f point1 = Point2f(10,10);
Point2f point2 = Point2f(50, 10);
Point2f point3 = Point2f(10,100);
Point2f point4 = Point2f(50, 100);
Point2f point5 = Point2f(20, 50);
vector<Point2f> points; // 将5个点存储为vector<Point2f>类型
points.push_back(point1);
points.push_back(point2);
points.push_back(point3);
points.push_back(point4);
points.push_back(point5);
//在黑色背景上画出这些点
Mat dst_img = Mat(Size(120,120),CV_8UC3);
for (auto i = 0; i < 5; ++i)
{
circle(dst_img, points[i],5,Scalar(0,0,255),-1,8,0);
}
// 计算点集的最小外包旋转矩形
RotatedRect rRect = minAreaRect(points);
//打印最小外包旋转矩形的信息
cout << "最小外包旋转矩形的中心坐标:" << rRect.center<< endl;
cout << "最小外包旋转矩形的尺寸:" << rRect.size << endl;
cout << "最小外包旋转矩形的旋转角度:" << rRect.angle << endl;
imshow("绘制出点集",dst_img);
waitKey(0);
return 0;
}
运行程序可以看到:
1.2 旋转矩形的四个顶点
在上一节我们说到,旋转矩形是通过中心点坐标、尺寸和旋转角度三个方面来定义的,当然通过这三个属性值就可以计算出旋转矩形的四个顶点,这样虽然简单,但是写起来比较复杂。OpenCV 3.X提供了函数:
void boxPoints(RotatedRect box, OutputArray Points)
便于计算旋转矩形的4个顶点,这样就可以使用函数line画出四个顶点的连线,从而画出旋转矩形。演示代码如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//我们创建各个点的Point
Point2f point1 = Point2f(200,200);
Point2f point2 = Point2f(280, 150);
Point2f point3 = Point2f(300,100);
Point2f point4 = Point2f(260, 300);
vector<Point2f> points;
points.push_back(point1);
points.push_back(point2);
points.push_back(point3);
points.push_back(point4);
//在黑色背景上画出这些点
Mat dst_img = Mat(Size(400,400),CV_8UC3);
for (auto i = 0; i <points.size(); ++i)
{
circle(dst_img, points[i],5,Scalar(0,0,0),-1,8,0);
}
// 计算点集的最小外包旋转矩形
RotatedRect rRect = minAreaRect(points);
Mat vertices;
// 求得旋转矩形的四个顶点坐标,然后在黑色画板上用line函数画出四条边,得到该矩形
boxPoints(rRect, vertices); //计算出的4个顶点存在一个4行2列的Mat对象中,每一行代表一个顶点坐标
cout << "四个顶点坐标如下:" << endl;
cout << vertices << endl; // 打印四个顶点
for (auto i = 0; i < vertices.rows; ++i)
{
Point p1 = Point(vertices.at<float>(i,0), vertices.at<float>(i, 1));
Point p2 = Point(vertices.at<float>((i+1)%4, 0), vertices.at<float>((i + 1) % 4, 1));
line(dst_img,p1,p2,Scalar(0,0,255),3);
}
imshow("绘制出点集以及对应的最小旋转矩形", dst_img);
waitKey(0);
return 0;
}
运行程序如下:
1.3、最小外包直立矩形
OpenCV提供了函数:
Rect boundingRect(InputArray points)
在VS2015中的提示为:
来实现点集的最小外包直立矩形。在OpenCV 2.X中,该函数的C++API输入点集只有两种形式:vector<Point2f>、
vector<Point>或者N行1列的双通道Mat类型且数据只能是CV_32S或者CV_32F,不再适用于N*2的单通道Mat类型。而在OpenCV种,为了和函数minAreaRect、minEnclosingCircle统一,该函数也适用于三种点集形式,在OpenCV 3.X版本中对该函数的C++API做了改变,输入点集也可以是一个N*2的单通道Mat对象,代码如下:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//我们创建各个点的Point
Point2f point1 = Point2f(200,200);
Point2f point2 = Point2f(280, 150);
Point2f point3 = Point2f(300,100);
Point2f point4 = Point2f(260, 300);
vector<Point2f> points;
points.push_back(point1);
points.push_back(point2);
points.push_back(point3);
points.push_back(point4);
//在黑色背景上画出这些点
Mat dst_img = Mat(Size(400,400),CV_8UC3);
for (auto i = 0; i <points.size(); ++i)
{
circle(dst_img, points[i],5,Scalar(0,0,0),-1,8,0);
}
// 计算点集的最小外包直立矩形
Rect rRect = boundingRect(points);
cout << "最小外包直立矩形的顶点坐标如下:" << endl;
cout << rRect << endl;
rectangle(dst_img,Point(rRect.x, rRect.y), Point(rRect.x+ rRect.width, rRect.y + rRect.height),Scalar(0,0,255),2,8,0);
imshow("绘制出点集以及对应的最小旋转矩形", dst_img);
waitKey(0);
return 0;
}
运行程序,如下所示:
二、轮廓的周长和面积
如何计算点集所围区域的周长和面积呢?OpenCV对这两方面的度量都给出了相应的计算函数,其中函数:
double arcLength(InputArray curve,bool closed)
用来计算点集所围区域的周长,参数curve代表输入点集,对于该函数的C++API,假设点集中有n个坐标点,curve一般有三种形式:vector<Point>、N*2的单通道Mat(一行代表一个坐标点)、N*1的双通道Mat;参数closed是指点集是否首尾相接。
函数:
double contourArea(InputArray contour, bool oriented=false)
用来计算点集所围区域的面积,参数contour和函数arcLength的参数curve类似,代表一个点集。
下面介绍contourArea和arcLength的C++API的使用方式:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//我们创建各个点的Point
Point2f point1 = Point2f(0,0);
Point2f point2 = Point2f(50, 30);
Point2f point3 = Point2f(100,0);
Point2f point4 = Point2f(100, 100);
vector<Point2f> points;
points.push_back(point1);
points.push_back(point2);
points.push_back(point3);
points.push_back(point4);
//在黑色背景上画出这些点
Mat dst_img = Mat(Size(200,200),CV_8UC3);
for (auto i = 0; i <points.size(); ++i)
{
circle(dst_img, points[i],5,Scalar(0,0,0),-1,8,0);
}
// 计算点集所围区域的周长和面积
double length1 = arcLength(points,false); // 首尾不相连
double length2 = arcLength(points, true); // 首尾相连
double area = contourArea(points);
//打印周长和面积
cout << "首尾不相连的周长:" << length1 << endl;
cout << "首尾相连的周长:" << length2 << endl;
cout << "面积:" << area << endl;
imshow("绘制出点集", dst_img);
waitKey(0);
return 0;
}
运行程序如下: