神奇的透视变换

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. 成果展示

       

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

时间: 2024-11-23 06:24:34

神奇的透视变换的相关文章

东方14模拟赛之noip2015/day1/3/神奇的幻方

总时间限制:  10000ms 单个测试点时间限制:  1000ms 内存限制:  128000kB 描述 幻方是一种很神奇的N*N 矩阵:它由数字 1,2,3, … …,N*N 构成,且每行.每列及两条对角线上的数字之和都相同. 当N为奇数时,我们可以通过以下方法构建一个幻方: 首先将 1 写在第一行的中间. 之后,按如下方式从小到大依次填写每个数K(K= 2,3, …,N*N ): 若 (K−1) 在第一行但不在最后一列,则将K填在最后一行,(K−1) 所在列的右一列: 若 (K−1) 在最

【第二章】神奇的张大炮

一只剃了毛的猫崽和一条受了伤的手臂能做什么? 十个人里有九个人觉得剃了毛的猫崽应该埋到土里喂蚯蚓. 十个人里有九个人认为手臂受了伤应该去医院. 可是偏偏十个人里还剩下一个,这第十位不但认为剃了毛的猫崽和受了伤的手臂可以做文章,简直可以做大文章. 不但可以做大文章,简直还可以赚大钱. 最关键的是,他不但做了大文章,还赚了大钱. 因为这第十位不是别人,就是这位神奇的张大炮. 当一个人被认为神奇的时候,他就必定有了称得上神奇的地方. 张大炮就有张大炮神奇的地方. 张大炮神奇的地方实在不少,他的脏实在只

AC日记——神奇的幻方 洛谷 P2615(大模拟)

题目描述 幻方是一种很神奇的N*N矩阵:它由数字1,2,3,……,N*N构成,且每行.每列及两条对角线上的数字之和都相同. 当N为奇数时,我们可以通过以下方法构建一个幻方: 首先将1写在第一行的中间. 之后,按如下方式从小到大依次填写每个数K(K=2,3,…,N*N): 1.若(K−1)在第一行但不在最后一列,则将K填在最后一行,(K−1)所在列的右一列: 2.若(K−1)在最后一列但不在第一行,则将K填在第一列,(K−1)所在行的上一行: 3.若(K−1)在第一行最后一列,则将K填在(K−1)

一个神奇的递推公式--转自2108

志远兄发现了一个神奇的递推公式, 某些递推的题目可以看作, 一个个上三角阵, 而问题的解为(1,1) 至 (n,n) 的路径个数, 废话不多说, 上题上代码 以下转自http://www.cnblogs.com/--ZHIYUAN/p/5971367.html 小兔的棋盘 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 9447    A

JAVA注释的另一种神奇用法

每个JAVA程序员在写程序的时候一定都会用到注释,本篇博客不是讲怎么定义注释,而是说明注释神奇的一种写法. 1 /** 2 * 这是一个测试类 3 */ 4 public class Test { 5 /** 6 * 程序的入口 7 */ 8 public static void main(String[] args) { 9 new Test(); 10 } 11 } 以上是两个普通的多行注释,在IDEA的环境下,选中方法或者类名,按住Ctrl+Q(Eclipse开发环境下直接按住Ctrl然后

NOIP 2015普及组复赛Day1 T1 == Codevs4510 神奇的幻方

时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题目描述 Description: 幻方是一种很神奇的N∗N矩阵:它由数字 1,2,3, … … ,N∗N构成,且每行.每列及两条对角线上的数字之和都相同. 当N为奇数时,我们可以通过以下方法构建一个幻方: 首先将 1写在第一行的中间.之后,按如下方式从小到大依次填写每个数(K= 2,3, … ,N∗N ): 1.若 (K−1)在第一行但不在最后一列,则将 填在最后一行,(K−1)所在列的右一列: 2.若 (K

神奇的幻方【够造奇数阶的魔方阵】

http://noi.openjudge.cn/ch0108/22/ 总时间限制:  1000ms 内存限制:  65535kB 描述 幻方是一个很神奇的N*N矩阵,它的每行.每列与对角线,加起来的数字和都是相同的.我们可以通过以下方法构建一个幻方.(阶数为奇数)1.第一个数字写在第一行的中间2.下一个数字,都写在上一个数字的右上方:    a.如果该数字在第一行,则下一个数字写在最后一行,列数为该数字的右一列    b.如果该数字在最后一列,则下一个数字写在第一列,行数为该数字的上一行    

神奇的scanf

神奇的scanf 作为标准输入输出函数组中的一个重要的输入的函数,scanf/sscanf/vscanf函数和printf/sprintf/vsprintf有个重要的区别:如果格式参数和后面的参数不匹配,printf系列函数可能会导致打印出的格式或者数据不是自己期望的 ,而scanf系列函数如果格式参数和后面的参数不匹配,可能导致有待输入的参数附近的内存发生变化,甚至导致程序崩溃. 以下面的函数为例: 8 #include<stdio.h> 9 #include<string.h>

神奇的网页阅读模式

今天突然之间发现了一个网页中含有阅读模式,打开之后网页中的所有广告被屏蔽掉了,确实非常的舒服,看网页的感觉就像看书一样.可以这个功能究竟是什么回事呢? 操作步骤: 1.在浏览器中输入,about:config 2.搜索 reader.parse-on-load.enabled,确定值为“true”即可 注意事项: 有部分人,可能会神奇的发现有些页面是看不到阅读模式的,这是什么原因呢?这是因为该页面本身不支持阅读模式导致,并非浏览器本身的问题.目前火狐.谷歌等浏览器好像都支持这个功能了.其实这个功