透视变换

最近根据项目需要,需要用到透视变换,如果直接调用cv::warpPerspective()虽然可以直接完成任务,但是因为后期需要移植到GPU里,所以需要自己实现。(本想直接改写cv::warpPerspective()的,但是OpenCV源码又调用了各种函数而且还有乱七八糟的东西,所以才打算自己搞。)目前kernel函数还没改写完,过几天测试好了再更。由于透视变换矩阵是个常量,只需要计算一次,所以可以调用cv::getPerspectiveTransform(),实际上就是借用这个函数来求解一个八元八次线性方程组。
先上点数学公式,根据计算机视觉的多视图几何一书中的定义
在这里插入图片描述
和像素级的计算公式
在这里插入图片描述
这一点和OpenCV中计算透视矩阵的公式是一样的。

 OpenCV计算透视矩阵的公式
 * Calculates coefficients of perspective transformation
 * which maps (xi,yi) to (ui,vi), (i=1,2,3,4):
 *
 *      c00*xi + c01*yi + c02
 * ui = ---------------------
 *      c20*xi + c21*yi + c22
 *
 *      c10*xi + c11*yi + c12
 * vi = ---------------------
 *      c20*xi + c21*yi + c22
 *
 * Coefficients are calculated by solving linear system:
 * / x0 y0  1  0  0  0 -x0*u0 -y0*u0 \ /c00\ /u0\
 * | x1 y1  1  0  0  0 -x1*u1 -y1*u1 | |c01| |u1|
 * | x2 y2  1  0  0  0 -x2*u2 -y2*u2 | |c02| |u2|
 * | x3 y3  1  0  0  0 -x3*u3 -y3*u3 |.|c10|=|u3|,
 * |  0  0  0 x0 y0  1 -x0*v0 -y0*v0 | |c11| |v0|
 * |  0  0  0 x1 y1  1 -x1*v1 -y1*v1 | |c12| |v1|
 * |  0  0  0 x2 y2  1 -x2*v2 -y2*v2 | |c20| |v2|
 * \  0  0  0 x3 y3  1 -x3*v3 -y3*v3 / \c21/ \v3/
 *
 * where:
 *   cij - matrix coefficients, c22 = 1
 *

所以代码是这样的:

for (int y = 0; y < image.rows; y = y + 1) {    //x, y are src,  u, v are dst.
    for (int x = 0; x < image.cols; x = x + 1) {
        int v = (int)((M10*x+M11*y+M12) / (M20*x+M21*y+M22));
        int u = (int)((M00*x+M01*y+M02) / (M20*x+M21*y+M22));
        if (v >=0 && u >= 0 && v < dst_size.height && u < dst_size.width) {
            image_Perspective.at<cv::Vec3b>(v, u) = image.at<cv::Vec3b>(y, x);
        }
    }
}

原图是这样的:
在这里插入图片描述
效果是这样的:
在这里插入图片描述
这是直接coding的结果,显然需要优化,第一直觉是插值,但是这是一个非线形图,我还没想到一个对于非线性图片比较好的插值算法。导致这种结果的原因是,对源图像进行遍历,在做坐标变换之后,因为强制类型转换,所以导致有的相近的源点被映射成同一个目标点,于是在目标图像上,有的点就没有被赋值,所以还是黑色,像素值还是(0, 0, 0)。晚上,我梦到了一个优化的方法——用逆变换coding。
这是目标图像dst中的点(u,v)关于源图像src中的点(x,y)的函数(u=u(x, y), v=v(x, y)),现在给它写成src关于dst的函数(x=x(u,v), y=y(u, v)),就是无脑解一个二元线性方程组,不展示具体推倒细节了,结果是
在这里插入图片描述
原来好比是满城尽带黄金甲发哥的一句台词“朕给你,才是你的,朕不给,你不能抢。”然而,现在我就是要抢。具体就是,遍历目标图像,利用逆变换去源图像中索要像素值,这样每个像素点都能要到像素值。
利用逆变换得到的代码是这样的:

for (int v = 0; v < dst_size.height; v = v + 1) {   //x, v are src,  u, v are dst.
    for (int u = 0; u < dst_size.width; u = u + 1) {
        int x = (int)(((M02-u*M22)*(v*M21-M11)-(M12-v*M22)*(u*M21-M01)) / ((u*M20-M00)*(v*M21-M11)-(v*M20-M10)*(u*M21-M01)));
        int y = (int)(((M02-u*M22)*(v*M20-M10)-(M12-v*M22)*(u*M20-M00)) / ((u*M21-M01)*(v*M20-M10)-(v*M21-M11)*(u*M20-M00)));
        if (x >= 0 && y >=0 && x < image.cols && y < image.rows) {
            image_Perspective.at<cv::Vec3b>(v, u) = image.at<cv::Vec3b>(y, x);
        }
    }
}

效果是这样的:
在这里插入图片描述
这才是我想要的结果!打包成函数:

void warpPerspectiveTransform(const cv::Mat& src, cv::Mat& dst, const cv::Mat& TransformMatrix, const cv::Size& dstSize) {
    const double M00 = TransformMatrix.at<double>(0, 0);			//(0,0)              X
    const double M01 = TransformMatrix.at<double>(0, 1);            //   ---------------->
    const double M02 = TransformMatrix.at<double>(0, 2);            //	|
    const double M10 = TransformMatrix.at<double>(1, 0);            //  |
    const double M11 = TransformMatrix.at<double>(1, 1);            //  |
    const double M12 = TransformMatrix.at<double>(1, 2);            //  |
    const double M20 = TransformMatrix.at<double>(2, 0);            //  |
    const double M21 = TransformMatrix.at<double>(2, 1);            //  |
    const double M22 = TransformMatrix.at<double>(2, 2);            // Y\/
    for (int v = 0; v < dstSize.height; v = v + 1) {   //x, y are src, u, v are dst. Search in dst.
        for (int u = 0; u < dstSize.width; u = u + 1) {
            int y = (int)(((M02-u*M22)*(v*M20-M10)-(M12-v*M22)*(u*M20-M00)) / ((u*M21-M01)*(v*M20-M10)-(v*M21-M11)*(u*M20-M00)));
            int x = (int)(((M02-u*M22)*(v*M21-M11)-(M12-v*M22)*(u*M21-M01)) / ((u*M20-M00)*(v*M21-M11)-(v*M20-M10)*(u*M21-M01)));
            if (y >=0 && x >= 0 && y < src.rows && x < src.cols) {
                dst.at<cv::Vec3b>(v, u) = src.at<cv::Vec3b>(y, x);
            }
        }
    }
}

整个demo的代码:

#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core.hpp>
#include <cuda.h>
#include <cuda_runtime.h>
#include "device_launch_parameters.h"
#include <stdio.h>

using namespace std;
using namespace cv;

void warpPerspectiveTransform(const cv::Mat& src, cv::Mat& dst, const cv::Mat& TransformMatrix, const cv::Size& dstSize) {
    const double M00 = TransformMatrix.at<double>(0, 0);
    const double M01 = TransformMatrix.at<double>(0, 1);            //(0,0)          X
    const double M02 = TransformMatrix.at<double>(0, 2);            //   ------------->
    const double M10 = TransformMatrix.at<double>(1, 0);            //  |
    const double M11 = TransformMatrix.at<double>(1, 1);            //  |
    const double M12 = TransformMatrix.at<double>(1, 2);            //  |
    const double M20 = TransformMatrix.at<double>(2, 0);            //  |
    const double M21 = TransformMatrix.at<double>(2, 1);            //  |
    const double M22 = TransformMatrix.at<double>(2, 2);            // Y\/
    for (int v = 0; v < dstSize.height; v = v + 1) {   //x, y are src, u, v are dst. Search in dst.
        for (int u = 0; u < dstSize.width; u = u + 1) {
            int y = (int)(((M02-u*M22)*(v*M20-M10)-(M12-v*M22)*(u*M20-M00)) / ((u*M21-M01)*(v*M20-M10)-(v*M21-M11)*(u*M20-M00)));
            int x = (int)(((M02-u*M22)*(v*M21-M11)-(M12-v*M22)*(u*M21-M01)) / ((u*M20-M00)*(v*M21-M11)-(v*M20-M10)*(u*M21-M01)));
            if (y >=0 && x >= 0 && y < src.rows && x < src.cols) {
                dst.at<cv::Vec3b>(v, u) = src.at<cv::Vec3b>(y, x);
            }
        }
    }
}

int main()
{
    cv::Mat image = cv::imread("../warpPerspectiveTransform/front.jpg");
    cv::Mat image_Perspective(720, 1280, CV_8UC3);
    cv::Size dst_size = cv::Size(1280, 720);
    cv::Point2f src_points[] = {
             cv::Point2f(400, 360),
             cv::Point2f(879, 360),
             cv::Point2f(1279, 719),
             cv::Point2f(0, 719)
    };
    cv::Point2f dst_points[] = {
            cv::Point2f(0, 0),
            cv::Point2f(dst_size.width - 1, 0),
            cv::Point2f(dst_size.width - 1, dst_size.height - 1),
            cv::Point2f(0, dst_size.height - 1)
    };
    cv::Mat tfMatrix = cv::getPerspectiveTransform(src_points, dst_points);

    int row = dst_size.height;
    int col = dst_size.width;

//    直接code的code
//    for (int y = 0; y < image.rows; y = y + 1) {    //x, y are src,  u, v are dst.
//        for (int x = 0; x < image.cols; x = x + 1) {
//            int v = (int)((M10*x+M11*y+M12) / (M20*x+M21*y+M22));
//            int u = (int)((M00*x+M01*y+M02) / (M20*x+M21*y+M22));
//            if (v >=0 && u >= 0 && v < dst_size.height && u < dst_size.width) {
//                image_Perspective.at<cv::Vec3b>(v, u) = image.at<cv::Vec3b>(y, x);
//            }
//        }
//    }

    warpPerspectiveTransform(image, image_Perspective, tfMatrix, dst_size);
    cv::imshow("image_Perspective", image_Perspective);
    cv::imwrite("../warpPerspectiveTransform/front_perspective.jpg", image_Perspective);
    cv::waitKey(0);
    
    return 0;
}

CMakeLists.txt:

cmake_minimum_required(VERSION 2.8)

project(warpPerspectiveTransform)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories(include
${CUDA_INCLUDE_DIRS}
${OpenCV_INCLUDE_DIRS}
)

link_directories(${OpenCV_LIBRARY_DIRS})

find_package(CUDA REQUIRED)
find_package(OpenCV REQUIRED)

#INCLUDE(/home/psdz/cmake-3.9.0/Modules/FindCUDA.cmake)

FILE(GLOB SOURCES "*.cu" "*.cpp" "*.c" "*.h")

set(CUDA_NVCC_FLAGS "-g -G")

CUDA_ADD_EXECUTABLE(${PROJECT_NAME} main.cu)
#add_executable(${PROJECT_NAME} "main.cpp")

target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})

这是个.cu文件,改成.cpp文件只需要把和CUDA有关的东西注释掉,然后把 #add_executable(${PROJECT_NAME} “main.cpp”) 这一句给取消注释。
(ps1:做透视变换的八个点是我随便选的)
(ps2:透视变换对整张图都有效果,而不仅仅是dst_points选中的区域)

希望改写成内核函数顺利。

更新:算法优化。既然利用透视变换的逆矩阵,那么可以在这里将dst_points和src_points颠倒个位置,求得的就是逆变换了。

cv::Mat inversal_tfMatrix = cv::getPerspectiveTransform(dst_points, src_points);

3月24日更新:最近研究视觉SLAM,看到视觉SLAM十四讲的7.3.3单应矩阵一节,发现单应矩阵和透视矩阵是一个东西!具体地,应该比较一下cv::findHomography()和cv::getPerspectiveTransform()这两个函数的计算结果。已经有人抢先了——>https://blog.csdn.net/abc20002929/article/details/8709902

4月6日更新:原来这种求dst目标值像素的方法叫做后向映射,而之前的方法叫做前向映射,借用哈工大许毅立硕士论文中的一张图。
在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值