opencv 仿射变换和透视变换
一、话说仿射变换和透视变换
对于平面区域,有两种方式的几何转换:一种是基于2×3矩阵进行的变换,叫仿射变换;另一种是基于3×3矩阵进行的变换,叫透视变换或者单应性映射。关于仿射变换和透射变换的矩阵变换,这篇博文不做重点讨论,因为图像本质就是矩阵,对矩阵的变换就是对图像像素的操作,很简单的数学知识。
仿射变换可以形象的表示成以下形式。一个平面内的任意平行四边形ABCD可以被仿射变换映射为另一个平行四边形A’B’C’D’。通俗的解释就是,可以将仿射变换想象成一幅图像画到一个胶版上,在胶版的角上推或拉,使其变形而得到不同类型的平行四边形。相比较仿射变换,透射变换更具有灵活性,一个透射变换可以将矩形转变成梯形。如下图:
1.1 仿射变换的API
void cvWarpAffine( const CvArr* src,//输入图像 CvArr* dst,//输出图像 const CvMat* map_matrix,//2×3变换矩阵->传个矩阵进来? int flags=CV_INTER_LINEAR|CV_WARP_FILL_OUTLIERS,//插值方法 CvScalar fillval=cvScalarAll(0))
从上面的函数中,我们可以看到其中需要传进去一个2×3变换矩阵,因此我们在调用cvWarpAffine()函数之前,要计算仿射映射矩阵,因此在OpenCV中有函数cvGetAffineTransform()来计算仿射映射矩阵。
CvMat* cvGetAffineTransform( const CvPoint2D32f* pts_src, const CvPoint2D32f* pts_dst,//src,dst三个二维点(x,y)的数组 CvMat* map_matrix//得到的仿射映射矩阵参数)
另一个获取变换矩阵的方法
CV_EXPORTS_W Mat getRotationMatrix2D( Point2f center, double angle, double scale );
1.2 透视变换API
CV_EXPORTS Mat getPerspectiveTransform( const Point2f src[], const Point2f dst[] );
void warpPerspective( InputArray src, OutputArray dst,
InputArray M, Size dsize,
int flags = INTER_LINEAR,
int borderMode = BORDER_CONSTANT,
const Scalar& borderValue = Scalar());
1.3 二者的联系
仿射变换:二维空间的变换 ; 线性变换 ;已知3对坐标点就可以求得变换矩阵
透视变换:三维空间的变换 ; 非线性变换 ;已知4对坐标点可以求得变换矩阵
二、应用之文字校正
纸质文档扫描中经常会发生扫描出来的图像有一定角度的偏斜,对后期的文档信息化OCR提取造成很大的干扰,导致OCR识别准确率下降从而影响文档信息化的结果。这个时候可以使用OpenCV对文档进行纠偏,最常见的文本纠偏算法有两种,分别是
基于FFT变换以后频率域梯度
基于离散点求最小外接轮廓
这两种方法各有千秋,相对来说,第二种方法得到的结果更加准确,第一种基于离散傅立叶变换求振幅的方法有时候各种阈值选择在实际项目中会有很大问题。
三、基于离散点求最小外接轮廓
其主要思路是先把图像二值化,得到一系列离散的前景像素点集合,然后利用轮廓的最小外接矩形函数,得到偏斜的矩形大小与角度,通过仿射变换完成校正。代码实现如下:
int main()
{
Mat src, gray,dst;
src = imread("D:\\cv_study\\Exercise\\文字倾斜\\text.png");
cvtColor(src, gray, CV_BGR2GRAY);
threshold(gray, dst, 0, 255, CV_THRESH_BINARY_INV | CV_THRESH_OTSU);
vector<Point>points;
findNonZero(dst, points);
RotatedRect box = minAreaRect(points);
double angle = box.angle;
if (angle < -45.)
angle += 90.;
printf("angle : %.2f\n", angle);
Point2f vertices[4];
box.points(vertices);
for (int i = 0; i < 4; ++i)
line(src, vertices[i], vertices[(i + 1) % 4], Scalar(0, 0, 255), 2);
Mat rot_mat = getRotationMatrix2D(box.center, angle, 1);
Mat rotated;
warpAffine(src, rotated, rot_mat, src.size(), cv::INTER_CUBIC, 0, Scalar(255, 255, 255));
waitKey();
return 0;
}
可以看到效果还是不错的。另一种方法再次就不做介绍,因为以上的方法是比较准确的。