最近在看一个车牌识别开源项目时,对其中RotatedRect的角度属性理解得不清楚,也查找了大量博客,得出了基本结论,最后通过实验进一步进行了验证。
RotatedRect该类表示平面上的旋转矩形,有三个属性:
- 矩形中心点(质心)
- 边长(长和宽)
- 旋转角度
旋转角度angle的范围为[-90,0),当矩形水平或竖直时均返回-90,请看下图:
来源:
看了这幅图,我明白了一些,但还是有疑虑,这个角度如何产生?究竟是那条边(宽?高?)与哪条坐标系的角度呢?矩形的宽和高究竟如何确定?
带着这个疑问,继续查找,下面的博客给出了解释(图均来自该博客):
也就是说,opencv的原点在左上角,横向为x轴,纵向为y轴,将x轴逆时针转动,碰到的第一条边(延长线)就为宽(Winth),而与之所成角度就为angle。那么问题又来了,矩形宽不等于高,若宽>高,当x轴逆时针转动碰到的第一条边是短边,将这个短边作为宽Winth,那不是和 宽>高 这个实际情况矛盾么?且看下图:
角度确实已经确定了,但是width“有问题”,当我们算宽高比时,若不加判断,有些情况下(宽>高的实际情况)就会出错(结果相反),那如何解决呢?
当然,opencv这样不区分短长边就是为了照顾正方形。所以计算时我们首先要判断:width/height <1?,若是,则需要交换width和height 的值,这样在后续操作(车牌矫正,数学运算)才不会出错。
为了验证是否是这样我做个小实验,结果如下:
width>height
width<height
顺便做了个仿射变换:
综合上面的内容,角度和边长的确定已经弄清楚了。
我发现学习openCV,仅仅会用函数,只知道参数和属性还不行啊,太容易忘了,两年后真不一定记得呢,何况真正的开发都是要挖源码的,所以我决定以后在调用函数的同时,要看opencv的源码,理解它的实现过程,看这些大牛是如何写代码的,同时会记录在博客上,方便以后查看,嗯,就这样吧。
实验源码:
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;
void main()
{
//轮廓最小外接矩形的绘制
Mat srcImg = imread("pen4.jpg");
//srcImg = srcImg(Rect(10,10,1000,690));
namedWindow("scr", 0);
imshow("scr", srcImg);
Mat dstImg = srcImg.clone();
cvtColor(srcImg, srcImg, CV_BGR2GRAY);
threshold(srcImg, srcImg, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); //二值化
//medianBlur(srcImg, srcImg, 9);
imshow("threshold", srcImg);
vector<vector<Point>> contours;
findContours(srcImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
Rect boundRect; //定义外接矩形
RotatedRect box; //定义最小外接矩形
vector<vector<Point>>::iterator itc = contours.begin();
Point2f rect_point[4];
int i = 0;
for (; itc!=contours.end(); itc++)
{
box = minAreaRect(Mat(*itc)); //计算每个轮廓最小外接矩形(旋转)
boundRect = box.boundingRect();
//boundRect = boundingRect(Mat(*itc));
circle(dstImg, Point(box.center.x, box.center.y), 5, Scalar(255,0, 0), -1, 8); //绘制最小外接矩形的中心点
// rectangle(dstImg, Point(boundRect.x, boundRect.y), Point(boundRect.x + boundRect.width, boundRect.y + boundRect.height), Scalar(0, 255, 0), 2, 8);
rectangle(dstImg, boundRect.tl(), boundRect.br() , Scalar(0, 255, 0), 3, 8);
box.points(rect_point); //把最小外接矩形四个端点复制给rect数组
for (int j = 0; j<4; j++)
{
line(dstImg, rect_point[j], rect_point[(j + 1) % 4], Scalar(0, 0, 255), 3, 8); //绘制最小外接矩形每条边
}
cout << "angle " << i << " :" << box.angle << endl;
cout << "width " << i << " :" << box.size.width << endl;
cout << "height " << i << " :" << box.size.height << endl<<endl;
char width[20], height[20];
sprintf(width, "width=%0.2f", box.size.width);//
sprintf(height, "height=%0.2f", box.size.height);//
putText(dstImg, width, box.center, CV_FONT_HERSHEY_COMPLEX_SMALL, 0.85, Scalar(0, 0, 255));
putText(dstImg, height, box.center + Point2f(0, 20), CV_FONT_HERSHEY_COMPLEX_SMALL, 0.85, Scalar(0, 0, 255));
}
namedWindow("rec_dst", 0);
imshow("rec_dst", dstImg);
//旋转校正
float angle_rotation;
if ((box.size.width / box.size.height) < 1)
angle_rotation = 90 + box.angle;//正数,逆时针旋转
else
angle_rotation = box.angle; //负数,顺时针旋转
double scale = 0.78;//缩放比例
Mat rot_m = getRotationMatrix2D(box.center,angle_rotation,scale);//获得旋转矩阵
warpAffine(dstImg, dstImg, rot_m, dstImg.size());//仿射变换
namedWindow("dst", 0);
imshow("dst", dstImg);
waitKey(0);
}