首页 > 代码库 > 神奇的透视变换

神奇的透视变换

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=http://www.mamicode.com/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 onm ouse, void* userdata=http://www.mamicode.com/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. 成果展示

技术分享        技术分享

左图是源图像,我选取了它的四个角,进行透视变换,得到了右边方方正正的书!至于点击响应,羽毛的中间部分已经被我点满啦!

神奇的透视变换