智能车竞赛逆透视变换(含编程实现)

在阅读本文之前,应该先阅读朱葛峻的《逆透视变换的一种方法》(下简称《方法》),本文的实现是基于这篇文章的推导的。
本文中提到的方法主要用于智能车竞赛。虽然在智能车有限的视野和像素内,这种方法有时并不是一种很好的选择。

一、基本理论

在谈逆透视变换之前,我们先谈一下真实世界和智能车上安装的摄像头所获取的照片的关系。世界坐标系下的点要经历平移变换、旋转变换、缩放变换、投影变换而得到摄像头坐标系下的点。前三种变换都是一一映射的变换,即原坐标下的点唯一对应新坐标下的点。只要知道规则,逆变换就可以完成。
投影变换不是一一映射的变换。如图,B,C两点在以A点为投影点的变换中落到了屏上同一点。B,C因为缺少深度的信息,在逆变换中无法区分。
在智能车的应用场景中,所有点都在地面,即Z=0。就是说,深度信息没有丢失,所以逆透视变换是可以完成的。
在这里插入图片描述

如下图,一张矩形的由摄像头获得的照片,在逆透视变换后变换成了倒梯形,而蓝色阴影所表示的道路则通过变换呈现出俯视图的样子。
在这里插入图片描述

二、实现原理

在下面的推导中,二维平面内的坐标点会用三维的列向量表示。 x , y x,y xy为摄像头坐标系内点的坐标。 X , Y X,Y XY为世界坐标系内点的坐标。
[ x y w ] 与 [ x / w y / w 1 ] \begin{bmatrix}x\\y\\w\end{bmatrix}与\begin{bmatrix}x/w\\y/w\\1\end{bmatrix} xywx/wy/w1表示同一个点。
按照小孔摄像机的数学模型,有
[ x ‾ y ‾ s ‾ ] = [ f x 0 c x 0 f y c y 0 0 1 ] [ r 11 r 12 r 13 t 1 r 21 r 22 r 23 t 2 r 31 r 32 r 33 t 3 ] [ X Y 0 1 ] \begin{bmatrix}\overline{x}\\\overline{y}\\\overline{s}\end{bmatrix}=\begin{bmatrix} f_x&0&c_x\\0&f_y&c_y\\0&0&1\end{bmatrix}\begin{bmatrix} r_{11}&r_{12}&r_{13}&t_1\\ r_{21}&r_{22}&r_{23}&t_2\\ r_{31}&r_{32}&r_{33}&t_3 \end{bmatrix}\begin{bmatrix}X\\ Y\\ 0\\ 1\end{bmatrix} xys=fx000fy0cxcy1r11r21r31r12r22r32r13r23r33t1t2t3XY01
化简得
[ x ‾ y ‾ s ‾ ] = [ H 11 H 12 H 12 H 21 H 22 H 23 H 31 H 32 1 ] [ X Y 1 ] \begin{bmatrix}\overline{x}\\\overline{y}\\\overline{s}\end{bmatrix}=\begin{bmatrix} H_{11}&H_{12}&H_{12}\\H_{21}&H_{22}&H_{23}\\H_{31}&H_{32}&1\end{bmatrix}\begin{bmatrix}X\\ Y\\ 1\end{bmatrix} xys=H11H21H31H12H22H32H12H231XY1
H = [ H 11 H 12 H 12 H 21 H 22 H 23 H 31 H 32 1 ] H=\begin{bmatrix} H_{11}&H_{12}&H_{12}\\H_{21}&H_{22}&H_{23}\\H_{31}&H_{32}&1\end{bmatrix} H=H11H21H31H12H22H32H12H231 H H H的逆为 H d H_d Hd
H d = [ H d 11 H d 12 H d 12 H d 21 H d 22 H d 23 H d 31 H d 32 1 ] H_d=\begin{bmatrix} H_{d11}&H_{d12}&H_{d12}\\H_{d21}&H_{d22}&H_{d23}\\H_{d31}&H_{d32}&1\end{bmatrix} Hd=Hd11Hd21Hd31Hd12Hd22Hd32Hd12Hd231

[ X ‾ Y ‾ S ‾ ] = [ H d 11 H d 12 H d 12 H d 21 H d 22 H d 23 H d 31 H d 32 1 ] [ x y 1 ] \begin{bmatrix}\overline{X}\\\overline{Y}\\\overline{S}\end{bmatrix}=\begin{bmatrix} H_{d11}&H_{d12}&H_{d12}\\H_{d21}&H_{d22}&H_{d23}\\H_{d31}&H_{d32}&1\end{bmatrix}\begin{bmatrix}x\\ y\\ 1\end{bmatrix} XYS=Hd11Hd21Hd31Hd12Hd22Hd32Hd12Hd231xy1
于是有了变换公式
X = X ‾ S ‾ = H d 11 x + H d 12 y + H d 13 H d 31 x + H d 32 y + H d 33 X=\frac{\overline{X}}{\overline{S}}=\frac{H_{d11}x+H_{d12}y+H_{d13}}{H_{d31}x+H_{d32}y+H_{d33}} X=SX=Hd31x+Hd32y+Hd33Hd11x+Hd12y+Hd13
Y = Y ‾ S ‾ = H d 21 x + H d 22 y + H d 23 H d 31 x + H d 32 y + H d 33 Y=\frac{\overline{Y}}{\overline{S}}=\frac{H_{d21}x+H_{d22}y+H_{d23}}{H_{d31}x+H_{d32}y+H_{d33}} Y=SY=Hd31x+Hd32y+Hd33Hd21x+Hd22y+Hd23

H H H H d H_d Hd的推导,贴出《方法》中的原公式。
在这里插入图片描述
在这里插入图片描述
要想方程组有唯一解,需要带入四个点在世界坐标系和摄像头坐标系的坐标。可以参考第一部分中的示意图。
注意在实际操作中, x , y , X , Y x,y,X,Y xyXY可以直接使用从零开始、从左至右、从上到下增加的像素坐标。还要注意 x , X x,X xX是纵坐标, y , Y y,Y yY是横坐标。

三、代码实现

下面的matlab代码,用来计算 H H H H d H_d Hd

x=[30,30,119,119]  % 依次为A、B、D、C在摄像头获取的照片中的的纵坐标
y=[0,187,0,187] % 依次为A、B、D、C在摄像头获取的照片中的的横坐标
X=[4000,4000,5465,5465]  % 依次为A、B、D、C在逆透视变换后的照片中的的纵坐标
Y=[3000,5720,4132,4580] % 依次为A、B、D、C在逆透视变换后的照片中的的纵坐标

A = [X(1),Y(1),1,0,0,0,-x(1)*X(1),-x(1)*Y(1);
    0,0,0,X(1),Y(1),1,-y(1)*X(1),-y(1)*Y(1);
    X(2),Y(2),1,0,0,0,-x(2)*X(2),-x(2)*Y(2);
    0,0,0,X(2),Y(2),1,-y(2)*X(2),-y(2)*Y(2);
    X(3),Y(3),1,0,0,0,-x(3)*X(3),-x(3)*Y(3);
    0,0,0,X(3),Y(3),1,-y(3)*X(3),-y(3)*Y(3);
    X(4),Y(4),1,0,0,0,-x(4)*X(4),-x(4)*Y(4);
    0,0,0,X(4),Y(4),1,-y(4)*X(4),-y(4)*Y(4)];
B = [x(1);y(1);x(2);y(2);x(3);y(3);x(4);y(4)];

H=A\B;  % 求解H11到H18
H=H'  % 把列向量改成行向量
H=[H,1]  % 获得完整的H的行向量

H11 =H(1);H12 =H(2); H13 =H(3); H21 =H(4); H22 =H(5); H23 =H(6); H31 =H(7); H32 =H(8); H33=H(9);
H=[H11,H12,H13;H21,H22,H23;H31,H32,H33]  % 获得完整的H的矩阵
Hd=inv(H)  % 获得Hd矩阵

fprintf("[[%f, %f, %f], [%f, %f, %f], [%f, %f, %f]]",Hd(1,1),Hd(1,2),Hd(1,3),Hd(2,1),Hd(2,2),Hd(2,3),Hd(3,1),Hd(3,2),Hd(3,3));  % 把Hd矩阵以符合python列表的形式输出
def process(self,img):
    img = np.array(img)

    height = 10000  # 高度限制
    width = 8000  # 宽度限制
    output = np.full((height, width), 0, dtype=np.int)

    Hd=[[1075.629450, 0.000000, -19146.217384], [814.163487, 47.718786, -14582.905023], [0.186940, 0.000000, -2.327535]]
    #  这里把刚从matlab那里得到的
    for x in range(img.shape[0]):
        for y in range(img.shape[1]):
            X = round((x * Hd[0][0] + y * Hd[0][1] + Hd[0][2]) / (x * Hd[2][0] + y * Hd[2][1] + Hd[2][2]))
            Y = round((x * Hd[1][0] + y * Hd[1][1] + Hd[1][2]) / (x * Hd[2][0] + y * Hd[2][1] + Hd[2][2]))
            # 注意由于python的索引从0开始计数,这里的索引和公式里略有差别
            if X>=height:
                X = height-1
            if X<0:
                X=0
            if Y >= width:
                Y = width-1
            if Y<0:
                Y=0
            output[X,Y]=img[x,y]

    return output

效果展示如下。
原图:在这里插入图片描述
逆透视变换后(因为没有填充,需要放大看):
在这里插入图片描述

  • 26
    点赞
  • 250
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
透视算法(perspective transform)是指将一个有透视效果的图像进行变换,使其恢复为原本的平面图像。智能车摄像头透视算法的主要作用是对车辆行驶过程中捕捉的视频进行处理,使得图像中的车道线、交通标志等物体在图像中呈现出平面视图,方便车辆的跟踪和分析。 以下是一个简单的透视算法的C语言代码: ```c #include <stdio.h> #include <stdlib.h> #include <math.h> #define PI 3.141592653589 typedef struct { float x, y; } point; int main(int argc, char *argv[]) { // 定义原始图像和变换后的图像的宽和高 int width = 640, height = 480; // 定义变换矩阵 float mat[3][3] = { {1, 0, 0}, {0, 1, 0}, {0, 0, 1} }; // 定义变换前和变换后的四个点 point src[4] = { {0, 0}, {0, height}, {width, height}, {width, 0} }; point dst[4] = { {0, 0}, {0, height}, {width, height}, {width, 0} }; // 定义变换后的图像缓存 unsigned char *out = (unsigned char*)malloc(sizeof(unsigned char) * width * height * 3); // 计算变换矩阵 mat[0][0] = (dst[1].y - dst[0].y) / (src[1].y - src[0].y); mat[1][1] = (dst[3].x - dst[0].x) / (src[3].x - src[0].x); mat[1][0] = (dst[1].x - dst[0].x) / (src[1].y - src[0].y); mat[0][1] = (dst[3].y - dst[0].y) / (src[3].x - src[0].x); mat[2][0] = -mat[0][0] * src[0].x - mat[1][0] * src[0].y; mat[2][1] = -mat[0][1] * src[0].x - mat[1][1] * src[0].y; // 遍历变换后的图像像素 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // 计算变换前的坐标 float nx = mat[0][0] * x + mat[1][0] * y + mat[2][0]; float ny = mat[0][1] * x + mat[1][1] * y + mat[2][1]; // 判断是否越界 if (nx >= 0 && nx < width && ny >= 0 && ny < height) { // 将变换前的像素值赋给变换后的像素 out[(y * width + x) * 3 + 0] = in[(int)(ny * width + nx) * 3 + 0]; out[(y * width + x) * 3 + 1] = in[(int)(ny * width + nx) * 3 + 1]; out[(y * width + x) * 3 + 2] = in[(int)(ny * width + nx) * 3 + 2]; } } } // 释放变换后的图像缓存 free(out); return 0; } ``` 这段代码实现了一个简单的透视算法,其中主要的计算逻辑在计算变换矩阵的代码段中。透视变换矩阵的计算过程比较复杂,需要根据变换前后的点坐标来确定。在这段代码中,我们先定义了变换前后的四个点,然后根据这些点的坐标计算出变换矩阵。接着,我们遍历变换后的图像像素,计算出变换前的坐标,并根据坐标值将变换前的像素值赋给变换后的像素。最后,我们释放变换后的图像缓存即可。 这段代码只是一个简单的透视算法实现,对于实际的智能车摄像头应用还需要根据实际情况进行调整和优化。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值