RotatedRect和minAreaRect源代码学习笔记
参考链接
以下图片来自于文章cv2.minAreaRect(),感谢原作者!
1. RotatedRect类的成员函数
这里我们主要学习RotatedRect有三个点的构造函数和Points函数,我们先来看RotatedRect的构造函数。
1 2 3 4 |
◆ RotatedRect() [3/3] cv::RotatedRect::RotatedRect ( const Point2f & point1, const Point2f & point2,const Point2f & point3 ) Any 3 end points of the RotatedRect. They must be given in order (either clockwise or anticlockwise). |
该函数介绍中说明了可以用3个按逆时针或顺时针顺序给出的点来构造一个RotatedRect,以下是该构造函数的源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
RotatedRect::RotatedRect(const Point2f& _point1, const Point2f& _point2, const Point2f& _point3) { Point2f _center = 0.5f * (_point1 + _point3); Vec2f vecs[2]; vecs[0] = Vec2f(_point1 - _point2); vecs[1] = Vec2f(_point2 - _point3); double x = std::max(norm(_point1), std::max(norm(_point2), norm(_point3))); double a = std::min(norm(vecs[0]), norm(vecs[1])); // check that given sides are perpendicular CV_Assert( std::fabs(vecs[0].ddot(vecs[1])) * a <= FLT_EPSILON * 9 * x * (norm(vecs[0]) * norm(vecs[1])) ); // wd_i stores which vector (0,1) or (1,2) will make the width // One of them will definitely have slope within -1 to 1 int wd_i = 0; if( std::fabs(vecs[1][1]) < std::fabs(vecs[1][0]) ) wd_i = 1; int ht_i = (wd_i + 1) % 2; float _angle = std::atan(vecs[wd_i][1] / vecs[wd_i][0]) * 180.0f / (float) CV_PI; float _width = (float) norm(vecs[wd_i]); float _height = (float) norm(vecs[ht_i]); center = _center; size = Size2f(_width, _height); angle = _angle; } |
从该函数的源代码第10行可知,构造RotatedRect时给出的三个3个点除了需要按顺时针或逆时针的方向给出外,这三个点构成的两个向量还需要是垂直的。
1 |
CV_Assert( std::fabs(vecs[0].ddot(vecs[1])) * a <= FLT_EPSILON * 9 * x * (norm(vecs[0]) * norm(vecs[1])) ); |
由源代码第15行可知,width和height由第2,3两个点组成的向量来决定。源代码中计算width,height和angle的方法与OpenCV之RotatedRect基本用法和角度探究讲到的规则相符:
1. angle范围是[-45°,45°],
2. 确定width的方法:找到最下面的顶点,设定水平轴,左边(正)和右边(负)分别得到一个角度,以绝对值小的那个角度为angle,接触的边为width。比如下图,就是左边的角为angle,正值,其边为width。
接下来我们来看points的函数说明cv::RotatedRect Class Reference。
1 2 |
void points (Point2f pts[]) const pts The points array for storing rectangle vertices. The order is bottomLeft, topLeft, topRight, bottomRight. |
points的源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void RotatedRect::points(Point2f pt[]) const { double _angle = angle*CV_PI/180.; float b = (float)cos(_angle)*0.5f; float a = (float)sin(_angle)*0.5f; pt[0].x = center.x - a*size.height - b*size.width; pt[0].y = center.y + b*size.height - a*size.width; pt[1].x = center.x + a*size.height - b*size.width; pt[1].y = center.y - b*size.height - a*size.width; pt[2].x = 2*center.x - pt[0].x; pt[2].y = 2*center.y - pt[0].y; pt[3].x = 2*center.x - pt[1].x; pt[3].y = 2*center.y - pt[1].y; } |
由代码以及说明可知,当我们使用如下代码来获取RotatedRect的四个点时,rect_points[0],rect_points[1],rect_points[2],rect_points[3]分别对应的是RotatedRect左下,左上,右上,右下四个点。
1 2 3 |
RotatedRect rrect = RotatedRect(pCenter,Size2f(width, height), 60); Point2f rect_points[4]; rrect.points(rect_points); |
注意:这个说法目前只能确保在Opencv4.5.5中以及20220323对应的opencv源代码中是成立的,并不适用于所有版本。关于Points输出的四个点的顺序,以及minAreaRect的width/height/angle在不同版本中会有不同的规则,在github/opencv 可以看到多个相关的issues,例如Specify the return value (angle) of minAreaRect()。
2. minAreaRect
minAreaRect的源代码在minAreaRect第360行,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
cv::RotatedRect cv::minAreaRect( InputArray _points ) { CV_INSTRUMENT_REGION(); Mat hull; Point2f out[3]; RotatedRect box; convexHull(_points, hull, false, true); if( hull.depth() != CV_32F ) { Mat temp; hull.convertTo(temp, CV_32F); hull = temp; } int n = hull.checkVector(2); const Point2f* hpoints = hull.ptr<Point2f>(); if( n > 2 ) { rotatingCalipers( hpoints, n, CALIPERS_MINAREARECT, (float*)out ); box.center.x = out[0].x + (out[1].x + out[2].x)*0.5f; box.center.y = out[0].y + (out[1].y + out[2].y)*0.5f; box.size.width = (float)std::sqrt((double)out[1].x*out[1].x + (double)out[1].y*out[1].y); box.size.height = (float)std::sqrt((double)out[2].x*out[2].x + (double)out[2].y*out[2].y); box.angle = (float)atan2( (double)out[1].y, (double)out[1].x ); } else if( n == 2 ) { box.center.x = (hpoints[0].x + hpoints[1].x)*0.5f; box.center.y = (hpoints[0].y + hpoints[1].y)*0.5f; double dx = hpoints[1].x - hpoints[0].x; double dy = hpoints[1].y - hpoints[0].y; box.size.width = (float)std::sqrt(dx*dx + dy*dy); box.size.height = 0; box.angle = (float)atan2( dy, dx ); } else { if( n == 1 ) box.center = hpoints[0]; } box.angle = (float)(box.angle*180/CV_PI); return box; } |
这里我们只关注第19~37的代码。第21行函数rotatingCalipers(该函数的源代码与minAreaRect在同一文件中)的关于输出描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// out - output info. // In case CV_CALIPERS_MAXDIST it points to float value - // maximal height of polygon. // In case CV_CALIPERS_MINAREARECT // ((CvPoint2D32f*)out)[0] - corner // ((CvPoint2D32f*)out)[1] - vector1 // ((CvPoint2D32f*)out)[2] - vector2 // // ^ // | // vector2 | // | // |____________\ // corner / // vector1 |
在minAreaRect的源码中,我们知道width和angle均由out[1]的坐标计算得到。但是从rotatingCalipers的源码中我们没有看到明确的代码来说明向量out[1]坐标值的正负,因此没有明确的代码来证明angle的值的范围和以及其确认方式。
在OpenCV之RotatedRect基本用法和角度探究和cv2.minAreaRect()这两篇文章中均根据实验来总结了一些结论。从实测结果与github/opencv中与RotatedRect和minAreaRect相关的issues来看,这两篇文章的结论可能只适用于部分opencv版本。
我使用博主OpenCV之RotatedRect基本用法和角度探究验证minAreaRect的代码在opencv4.5.5+VS2019的环境下验证的结论与博主得到的结论相反,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#include<opencv2/opencv.hpp> using namespace std; using namespace cv; #define W_CONVEX "RESULT" int main() { Mat image=Mat::zeros(600,600,CV_8UC3); RNG& rng=theRNG(); while(1) { char key; int count=(unsigned)rng%100+3; vector<Point> points; for(int i=0;i<count;i++) { Point point; point.x=rng.uniform(image.cols/4,image.cols*3/4); point.y=rng.uniform(image.rows/4,image.rows*3/4); circle(image,point,2,Scalar::all(255),2); points.push_back(point); } RotatedRect rotatedRect=minAreaRect(points); Point2f hull[4]; rotatedRect.points(hull); for(int i=0;i<4;i++) { line(image,hull[i],hull[(i+1)%4],Scalar::all(255),2); } cout<<"RotatedRect's angle:"<<rotatedRect.angle<<endl; cout<<"RotatedRect's width:"<<rotatedRect.size.width<<endl; cout<<"RotatedRect's height:"<<rotatedRect.size.height<<endl; cout<<endl; namedWindow(W_CONVEX,WINDOW_NORMAL); imshow(W_CONVEX,image); image=Scalar::all(0); waitKey(2500); } return 0; } |
实验结果如下图所示,左侧最后一组结果对应右侧图片。
综上所述,在使用RotatedRect和minAreaRect时,如果需要用到width/height/angle的规则,需要在代码中限定版本,或者根据实际值进行判断;否则当代码运行于其他版本的opencv时,原来设计的功能可能会失效。