1. 理论公式
透视变换(Pespective Transform)是将一个视平面上的物体转换到一个新的视平面。变换公式如下:
其中等式右边的u,v是源图片的坐标,在变换后图像中的对应坐标x, y,可以用下式计算得到:
据此,原图像和透视变换后的目标图像中的点,对应转换关系如下:
变换矩阵的子矩阵表示线性变换,比如scaling(缩放),shearing和rotation(旋转)。表示平移。产生透视变换。所以可以认为仿射变换是透视变换的特殊形式。到此,我们解释了透视变换的理论公式,那透视变换矩阵中的9个参数该如何求解呢?强大的OpenCV库粉墨登场。
2. OpenCV的getPerspectiveTransform函数和warpPerspective函数
在第一部分介绍的透视变换矩阵可以使用OpenCV库的getPerspectiveTransform函数求解,它在OpenCV 2.4.13中的函数原型如下:
Mat getPerspectiveTransform(InputArray src, InputArray dst)
参数: src为原图像四边形定点的坐标集合。dst为目标图像对应四边形定点的坐标集合。在这里建议使用std::vector<point2f> 数据结构存储四个点的坐标。Note: 坐标值必须是32f,也就是float类型,使用std::vector<point>是不行的。
warPerspective函数是对一个图像做透视变换,它的C++格式的函数原型声明如下:
void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
参数: src是源图像,也就是我们想操作的图像,dst是变换后的目标图像,M就是我们之前得到的透视变换矩阵。dsize是得到的目标图像的尺寸,这是个很有意思的参数,我一般是设为,之前计算透视矩阵时,选取的目标四边形的大小。如若不然,会得到很丑的黑色填充区域。至于剩下的参数,他们都有缺省值,一般用不到,读者如果感兴趣,可以翻阅一下reference manual,目前最新版本是2.4.13.
3. 撸代码
来来来,代码撸起来!!!程序思路如下:使用OpenCV的鼠标点击响应函数,手动选取出四边形四个点坐标(坐标点自动识别比较难,对于一些特殊的图形,可以试试直线霍夫变换,角点检测实现自动识别)。然后设定目标图像的尺寸,计算透视转换矩阵,完成透视转换。最后,我希望我在原图像里点击一个位置,转换后图像的对应位置,也能有一致的响应。
#include <opencv2/core/core.hpp> #include <opencv2/calib3d/calib3d.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace cv; using namespace std; void mouse(int event, int x, int y, int flags, void*); Mat src, gray, dst_img, h; vector<Point2f> selected; vector<Point2f> dst; int width =500; int height =400; int flag=0; int main() { src = imread("book.jpg", 1); imshow("book", src); setMouseCallback("book", mouse, 0); //void setMouseCallback(const string& winname, MouseCallback onMouse, void* userdata=0) dst.push_back(Point2f(0, 0)); dst.push_back(Point2f(width-1, 0)); dst.push_back(Point2f(width-1, height-1)); dst.push_back(Point2f(0, height-1)); waitKey(); } void mouse(int event, int x, int y, int flags, void*) { if(event == EVENT_LBUTTONDOWN) //如果鼠标按下了。 { circle(src, Point(x,y), 3, Scalar(0,0,255), -1); imshow("book", src); selected.push_back(Point2f(x,y)); ++flag; if(flag==4) { h= getPerspectiveTransform(selected, dst); warpPerspective(src, dst_img, h, Size(width, height)); imshow("dst", dst_img); waitKey(1); } if(flag>4) //我在透视转换前的图像里点击一个位置,我们希望在透视转换后的图像里,也可以有相应的响应. { double h11=h.at<double>(0, 0); double h12=h.at<double>(0, 1); double h13=h.at<double>(0, 2); double h21=h.at<double>(1, 0); double h22=h.at<double>(1, 1); double h23=h.at<double>(1, 2); double h31=h.at<double>(2, 0); double h32=h.at<double>(2, 1); double h33=h.at<double>(2, 2); int tr_x=(int)(h11*x+h12*y+h13)/(h31*x+h32*y+h33); int tr_y=(int)(h21*x+h22*y+h23)/(h31*x+h32*y+h33); circle(dst_img, Point(tr_x,tr_y), 3, Scalar(0,0,255), -1); imshow("dst", dst_img); waitKey(); } } }
4. 成果展示
左图是源图像,我选取了它的四个角,进行透视变换,得到了右边方方正正的书!至于点击响应,羽毛的中间部分已经被我点满啦!