在上篇的博文中,我们重点讨论了基于霍夫变换的线段和圆检测。其实在图像的变换中,还有一部分是几何操作,这些操作包括各种方式的拉伸,包括一致性缩放和非一致性缩放(即扭曲)。对于平面区域,有两种方式的几何转换:一种是基于2×3矩阵进行的变换,叫仿射变换;另一种是基于3×3矩阵进行的变换,叫透视变换或者单应性映射。关于仿射变换和透射变换的矩阵变换,这篇博文不做重点讨论,因为图像本质就是矩阵,对矩阵的变换就是对图像像素的操作,很简单的数学知识。
仿射变换可以形象的表示成以下形式。一个平面内的任意平行四边形ABCD可以被仿射变换映射为另一个平行四边形A’B’C’D’。通俗的解释就是,可以将仿射变换想象成一幅图像画到一个胶版上,在胶版的角上推或拉,使其变形而得到不同类型的平行四边形。相比较仿射变换,透射变换更具有灵活性,一个透射变换可以将矩形转变成梯形。如下图:
下面关于OpenCV中进行仿射变换和透射变换的函数进行介绍下:
//仿射变换函数
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//得到的仿射映射矩阵参数
)
下面给出这个仿射变换的完整程序示例:
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
int main()
{
//数组声明
CvPoint2D32f srcTri[3], dstTri[3];
//创建指针
CvMat* warp_mat = cvCreateMat(2, 3, CV_32FC1);
CvMat* rot_mat = cvCreateMat(2, 3, CV_32FC1);
//载入和显示图像
IplImage *src;
src = cvLoadImage("1.jpg", CV_LOAD_IMAGE_UNCHANGED);
cvNamedWindow("原图", CV_WINDOW_AUTOSIZE);
cvShowImage("原图", src);
IplImage *dst = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
dst=cvCloneImage(src);
dst->origin = src->origin;
cvZero(dst);
//计算变换矩阵
srcTri[0].x = 0;
srcTri[0].y = 0;
srcTri[1].x = src->width - 1;
srcTri[1].y = 0;
srcTri[2].x = 0;
srcTri[2].y = src->height - 1;
dstTri[0].x = src->width*0.0;
dstTri[0].y = src->height*0.33;
dstTri[1].x = src->width*0.85;
dstTri[1].y = src->height*0.25;
dstTri[2].x = src->width*0.15;
dstTri[2].y = src->height*0.7;
cvGetAffineTransform(srcTri, dstTri, warp_mat);
//调用函数cvWarpAffine()
cvWarpAffine(src, dst, warp_mat);
cvNamedWindow("仿射图1", CV_WINDOW_AUTOSIZE);
cvShowImage("仿射图1", dst);
cvCopy(dst, src);
//用另外一种方法得到变换矩阵,并进行仿射变换
CvPoint2D32f center = cvPoint2D32f(src->height / 2, src->width / 2);
double angle = -50.0;
double scale =- 0.6;
cv2DRotationMatrix(center, angle, scale, rot_mat);
cvWarpAffine(src, dst, rot_mat);
cvNamedWindow("仿射图2", CV_WINDOW_AUTOSIZE);
cvShowImage("仿射图2", dst);
cvWaitKey();
cvReleaseImage(&src);
cvReleaseImage(&dst);
cvDestroyWindow("原图");
cvDestroyWindow("仿射图1");
cvDestroyWindow("仿射图2");
cvReleaseMat(&rot_mat);
cvReleaseMat(&warp_mat);
return 0;
}
程序运行的结果为下图:
透视变换的函数和得到透视映射矩阵的函数同仿射变换没有太大区别,主要是仿射变换用到的是2×3矩阵,而透视变换用到的是3×3矩阵,关于函数的细节,在这里我就不做细致的介绍了,下面就直接给出程序示例:
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
int main()
{
//数组声明
CvPoint2D32f srcTri[4], dstTri[4];
//创建数组指针
CvMat *warp_mat = cvCreateMat(3, 3, CV_32FC1);
IplImage *src, *dst;
//载入和显示图像
src = cvLoadImage("1.jpg", CV_LOAD_IMAGE_UNCHANGED);
cvNamedWindow("原图", CV_WINDOW_AUTOSIZE);
cvShowImage("原图", src);
dst = cvCloneImage(src);
dst->origin = src->origin;
cvZero(dst);
//构造变换矩阵
srcTri[0].x = 0;
srcTri[0].y = 0;
srcTri[1].x = src->width - 1;
srcTri[1].y = 0;
srcTri[2].x = 0;
srcTri[2].y = src->height - 1;
srcTri[3].x = src->width - 1;
srcTri[3].y = src->height - 1;
dstTri[0].x = src->width*0.05;
dstTri[0].y = src->height*0.33;
dstTri[1].x = src->width*0.9;
dstTri[1].y = src->height*0.25;
dstTri[2].x = src->width*0.2;
dstTri[2].y = src->height*0.7;
dstTri[3].x = src->width*0.8;
dstTri[3].y = src->height*0.9;
//计算透视映射矩阵
cvGetPerspectiveTransform(srcTri, dstTri, warp_mat);
//调用函数cvWarpPerspective()
cvWarpPerspective(src, dst, warp_mat);
//显示透视变换后的图像
cvNamedWindow("透视变换", CV_WINDOW_AUTOSIZE);
cvShowImage("透视变换", dst);
cvWaitKey();
cvReleaseImage(&src);
cvReleaseImage(&dst);
cvDestroyWindow("原图");
cvDestroyWindow("透视变换");
cvReleaseMat(&warp_mat);
return 0;
}
最后运行的结果如下: