opencv仿射变换和透视变换门牌号实践总结
程序员文章站
2023-12-27 09:03:21
...
前几日在门牌号识别优化过程中发现当摄像头拍摄角度倾斜或者相机仰头拍摄出来的门牌号发生了畸变,即使能够找到门牌号区域也大大降低了识别的准确度,因此想到了倾斜矫正——仿射变换和透视变换,关于这两个概念网上讲解很多,公式不再详述,一句话说一下我看了别人写的一些理解吧,如有理解不到位之处,欢迎指正!
1、概念简述
仿射变换(Affine Transformation):变换前后平行关系保持不变,可以对图像平移旋转,加缩放操作,opencv中对应的函数为 warpAffine 函数。
透视变换(Perspective Transformation):将透视画面变为正视画面,需要透视变换图像前后四点相互对应,实现透视图像的恢复,不保留平行关系,opencv中对应的函数为warpPerspective 函数。(参考知乎链接,图码并茂,不再重复!)
2、仿射变换实例
仿射变换实例参考:http://www.cnblogs.com/skyfsm/p/6902524.html
关于里面一段角度的代码(稍做修改)我简单说一下,其余代码参看:
//获取待矫正轮廓的四个角点坐标
CvPoint2D32f rectpoint[4];
CvBox2D rect =minAreaRect(Mat(contours[max_id]));//max_id 为需要矫正的图像的轮廓的index
cvBoxPoints(rect, rectpoint); //获取4个顶点坐标
//cout<<" 1:"<<rectpoint[0].x<<rectpoint[0].y<<endl;//<<" 2:"<<rectpoint[1]<<" 3:"<<rectpoint[2]<<" 4:"<<rectpoint[3]<<endl;
//cout<<" 2:"<<rectpoint[1].x<<rectpoint[1].y<<endl;
//cout<<" 3:"<<rectpoint[2].x<<rectpoint[2].y<<endl;
//cout<<" 4:"<<rectpoint[3].x<<rectpoint[3].y<<endl;
//与水平线的角度
float angle = rect.angle;
//cout <<"angle is :"<< angle << endl;
int line1 = sqrt((rectpoint[1].y - rectpoint[0].y)*(rectpoint[1].y - rectpoint[0].y) + (rectpoint[1].x - rectpoint[0].x)*(rectpoint[1].x - rectpoint[0].x));
int line2 = sqrt((rectpoint[3].y - rectpoint[0].y)*(rectpoint[3].y - rectpoint[0].y) + (rectpoint[3].x - rectpoint[0].x)*(rectpoint[3].x - rectpoint[0].x));
if (line1 > line2)
{
//cout<<"till to right!"<<endl;
angle = 90 + angle;
}
//创建一个旋转后的图像
Mat RatationedImg(grayImage.rows, grayImage.cols, CV_8UC1);//grayImage为待矫正部分boundingRect()切出来的部分
RatationedImg.setTo(0);
//对RoiSrcImg进行旋转
//Point2f center = rect.center; //中心点
float center2_x = (roi_rect.br().x-roi_rect.tl().x)/2;
float center2_y = (roi_rect.br().y-roi_rect.tl().y)/2;
Point2f center2 = {center2_x,center2_y};//需要重新计算相对与grayImage部分的中心点,不然M2计算错误roi_rect是grayImage的外框
//cout<<"center:"<<center.x<<"y"<<center.y<<endl;
//cout<<"center2:"<<center2.x<<"y"<<center.y<<endl;
Mat M2 = getRotationMatrix2D(center2, angle, 1);//计算旋转加缩放的变换矩阵
warpAffine(grayImage, RatationedImg, M2, grayImage.size(),1, 0, Scalar(0));//仿射变换
//imshow("After Rotated", RatationedImg);
关于图像左倾和右倾为便于大家理解,我画了个图,不同的倾斜方向四个角点的位置是不一样的!3、透视变换实例
透视变换实例参考:通过透视变换矫正变形图像
根据参考博客并结合上一篇的仿射变换部分内容,我稍作修改代码,写了个透视变换函数如下:输入为原图和待矫正轮廓index,返回提取出的矫正后的图片。
Mat perspectivetransfer(Mat srcImage,int max_id){
//获取门牌号角点,and perspective transform to 180,110 size picture
std::vector<cv::Point2f> poly;
cv::approxPolyDP(contours[max_id], poly, 30, true); // 多边形逼近,精度(即最小边长)设为30是为了得到4个角点
cv::Point2f pts_src[] = { poly[0],poly[3],poly[2],poly[1]};
//此处用于输出多边形逼近的角点的位置关系
/*cout<<" p1:"<<poly[0].x<<poly[0].y<<endl;
cout<<" p2:"<<poly[1].x<<poly[1].y<<endl;
cout<<" p3:"<<poly[2].x<<poly[2].y<<endl;
cout<<" p4:"<<poly[3].x<<poly[3].y<<endl;*/
int line1 = sqrt((poly[1].y - poly[0].y)*(poly[1].y - poly[0].y) + (poly[1].x - poly[0].x)*(poly[1].x - poly[0].x));
int line2 = sqrt((poly[3].y - poly[0].y)*(poly[3].y - poly[0].y) + (poly[3].x - poly[0].x)*(poly[3].x - poly[0].x));
if (line1 > line2)
{
cout<<"till to left!"<<endl;
pts_src[0] =poly[1];
pts_src[1] =poly[0];
pts_src[2] =poly[3];
pts_src[3] =poly[2];//{ poly[1],poly[0],poly[3],poly[2]};
}
//透视变换后的角点
Point2f pts_dst[] = {Point(10, 10),Point(190, 10),
Point(190, 120) ,Point(10, 120) };
Mat &&M = cv::getPerspectiveTransform(pts_src, pts_dst);
Mat warp;
Mat PerspectivedImg(130, 200, CV_8UC1);
PerspectivedImg.setTo(0);
cv::warpPerspective(srcImage, warp, M, PerspectivedImg.size(), cv::INTER_LINEAR , cv::BORDER_REPLICATE);
imshow("After Perspectived", warp);
return warp;
}
多边形逼近获取的角点的顺序和上一个的cvBoxPoints(rect, rectpoint); //获取4个顶点坐标
获取的点的顺序不一致,必须保证角点变换前后相互对应,才能正确对图像进行透视变换!我又画了一副图如下:
4.门牌矫正效果
提取出门牌并矫正后的结果: