鱼眼环视拼接(AVM)

您所在的位置:网站首页 dw图片拼接 鱼眼环视拼接(AVM)

鱼眼环视拼接(AVM)

2024-04-21 22:31| 来源: 网络整理| 查看: 265

关于车辆的全景环视系统网上已经有很多的资料,然而几乎没有可供参考的代码,这一点对入门的新人来说非常不友好。全景环视系统,又称AVM。在自动驾驶领域,AVM属于自动泊车系统的一部分,是一种实用性极高、可大幅提升用户体验和驾驶安全性的功能。AVM汽车环视影像系统如图所示,由安装在前保险杠、后备箱、后视镜上的四个外置鱼眼相机构成。该系统包含的算子按照先后顺序:去畸变、四路鱼眼相机联合标定、投影变换、鸟瞰图微调、拼接融合、3D模型纹理映射等。下面我们将围绕着算子的先后顺序来对AVM进行介绍。

引用:https://www.guyuehome.com/39649

现实中并不存在这样完全没有畸变的透镜,这主要是制造上的原因,因为制作一个球形透镜比制作一个数学上理想的透镜更容易,另外从机械制作方面考虑也很难把成像仪和透镜保持平行的状态,现实应用中一般只考虑两种透镜畸变,分别是切向畸变和径向畸变,切向畸变产生的原因主要是摄像头生产安装过程中在工艺上的缺陷,而径向畸变则来自于透镜的形状。

鱼眼摄像机径向畸变模型如下图所示,存在着中间大两边小的特点,径向畸变就是沿着透镜半径方向分布的畸变,对于廉价的摄像头产生径向畸变的情况更加明显,径向畸变主要包括桶形畸变和枕形畸变两种。成像仪光轴中心的畸变为0,沿着镜头半径方向边缘移动,畸变越来越严重。对于畸变很大的镜头,如鱼眼镜头,可以利用两个畸变参数K1、K2描述。

切向畸变是由于透镜本身与相机传感器平面(成像平面)或图像平面在制作上放置不平行所产生的,这种不平行的情况多半是透镜被粘贴到镜头模组上安装时产生的偏差导致。畸变模型可以用两个额外的参数K3、K4来描述。

(1)无畸变模型(2)鱼眼畸变模型

1、鱼眼畸变矫正算法

实际中广泛应用的畸变校正方法是基于标定的校正方法,这类方法通过标定获取摄像机的内参数,即车载环视拼接系统的设计与实现(二)提到的摄像机内部参数,再通过摄像机成像过程坐标映射关系建立畸变模型。

假设(Xw,Yw,Zw)为世界坐标系下一个三维坐标点,投影到鱼眼图像中的像素点为(u,v),则可以根据车载环视拼接系统的设计与实现(二)摄像机理想线性坐标系变换即物体世界坐标点到像素坐标点的过程,结合切向径向畸变系数K1、K2、K3、K4推导出畸变图像成像模型的数学表达式。

世界坐标系到相机坐标系转换如下:

可以利用以下数学模型来描述畸变模型,假设摄像机坐标系下归一化坐标点:

注意: opencv3.0以上加入了 fisheye 鱼眼畸变模型,相比于opencv1 opencv2 的普通相机模型的去畸变效果要好。

其中r为像点距离图像中心的径向半径,K1、K2、K3、K4为畸变参数,K1、K2为径向畸变参数,K3、K4为切向畸变参数.

令:

则有鱼眼畸变图像素点:

其中为fx,fy,Cx,Cy摄像机内参。因此我们可以利用上述数学模型来得出世界坐标系中的点(Xw,Yw,Zw)到鱼眼图像的像素位置(u,v)过程,这是非线性过程。只要我们利用上式中无畸变成像模型中像素坐标(u,v),即畸变矫正图像像素坐标(u1,v1),逆推出相机坐标系归一化点,即:

2D AVM:

3D AVM:

2、镜头去畸变

首先我们需要获取每个相机的内参矩阵与畸变系数。以下是视频中四个相机分别拍摄的原始画面,顺序依次为前、后、左、右,并命名为front.png、back.png、left.png、right.png.你可以看到图中地面上铺了一张标定布,这个布的尺寸是6mx10m,每个黑白方格的尺寸为40cmx40cm,每个圆形图案所在的方格是80cmx80cm。我们将利用这个标定物来手动选择对应点获得投影矩阵。

相机去畸变通常使用张正友老师的棋盘格标定方法,首先通过矩阵推导得到一个比较好的初始解,然后通过非线性优化得到最优解,包括相机的内参、外参、畸变系数,然后对鱼眼图像做去畸变处理。内参即:

3、四路鱼眼相机联合标定

接下来我们需要获取每个相机到地面的投影矩阵,这个投影矩阵会把相机校正后的画面转换为对地面上某个矩形区域的鸟瞰图。这四个相机的投影矩阵不是独立的,它们必须保证投影后的区域能够正好拼起来。

这一步是通过联合标定实现的,即在车的四周地面上摆放标定物,拍摄图像,手动选取对应点,然后获取投影矩阵。

每个标定板应当恰好位于相邻的两个相机视野的重合区域中。

在上面拍摄的相机画面中车的四周铺了一张标定布,这个具体是标定板还是标定布不重要,只要能清楚的看到特征点就可以了。

然后我们需要设置几个参数:(以下所有参数均以厘米为单位)

innerShiftWidth, innerShiftHeight:标定板内侧边缘与车辆左右两侧的距离,标定板内侧边缘与车辆前后方的距离。shiftWidth, shiftHeight:这两个参数决定了在鸟瞰图中向标定板的外侧看多远。这两个值越大,鸟瞰图看的范围就越大,相应地远处的物体被投影后的形变也越严重,所以应酌情选择。totalWidth, totalHeight:这两个参数代表鸟瞰图的总宽高,在这个里我们设置标定布宽 6m 高 10m,于是鸟瞰图中地面的范围为 (600 + 2 * shiftWidth, 1000 + 2 * shiftHeight)。为方便计我们让每个像素对应 1 厘米,于是鸟瞰图的总宽高为totalWidth = 600 + 2 * shiftWidth totalHeight = 1000 + 2 * shiftHeight车辆所在矩形区域的四角 (图中标注的红色圆点),这四个角点的坐标分别为 (xl, yt), (xr, yt), (xl, yb), (xr, yb) (l 表示 left, r 表示 right,t 表示 top,b 表示 bottom)。这个矩形区域相机是看不到的,我们会用一张车辆的图标来覆盖此处。设置好参数以后,每个相机的投影区域也就确定了,比如前方相机对应的投影区域如下:

4、投影变换

投影变换的通俗理解就是:假设同一个相机分别在A、B两个不同位置,以不同的位姿拍摄同一个平面(重点是拍摄平面,例如桌面、墙面、地平面),生成了两张图象,这两张图象之间的关系就叫做投影变换。张正友老师的相机标定法使用的就是从标定板平面到图像平面之间的投影模型。

图中相机从两个不同的角度拍摄同一个X平面,两个相机拍摄到的图像之间的投影变换矩阵H(单应矩阵)为:

其中K为相机内参矩阵,R、T为两个相机之间的外参。这个公式怎么推导的网上有很多,我们只需要知道,这个单应矩阵H内部实际是包含了两个相机之间的位姿关系即可。这也就解释了:为什么有的AVM pipeline的方法是需要标定相机的外参,然后通过厂家提供的相机安装参数将四路鱼眼全部统一到车身坐标系下,而我们不需要这个过程,只需要用标定布来做联合标定。其实两种方法内部都是相通的,都绕不开计算相机外参这件事情。

5、鸟瞰图的拼接与平滑

到这一步其实就是最重要的一步了,如何将我们想要的图片进行拼接,并完成图片生成。生成鸟瞰图的过程可以理解为:将鱼眼相机拍摄到的图像,投影到某个在汽车上方平行地面拍摄的相机的平面上去。这个单应矩阵H具体是多少,由去畸变图中检测到的棋盘格角点坐标和联合标定全景图中棋盘格角点坐标来决定。如图所示,以后置相机为例,联合标定已知图(2)中框出棋盘格的坐标,图(1)中的棋盘格坐标可通过opencv的函数进行检测,从而建立单应矩阵H的求解模型。

由于相邻相机之间有重叠的区域,所以这部分的融合是关键。如果直接采取两幅图像加权平均 (权重各自为 1/2) 的方式融合的话你会得到类似下面的结果:

你可以看到由于校正和投影的误差,相邻相机在重合区域的投影结果并不能完全吻合,导致拼接的结果出现乱码和重影。这里的关键在于权重系数应该是随像素变化而变化的,并且是随着像素连续变化。

以左上角区域为例,这个区域是front,left两个相机视野的重叠区域。我们首先将投影图中的重叠部分取出来:

灰度化并二值化,并用形态学操作去掉 噪点(不必特别精细,大致去掉即可):

至此我们就得到了重叠区域的一个完整 mask。

然后将mask加入到拼接当中,通常的做法是分别以AB、CD为边界,计算白色区域像素点与AB、CD之间的距离,然后计算一个权重,距离CD越近的位置,前俯视图权重越大;距离AB越近的位置,左俯视图权重越大。但会出现边界效应如图所示:

实现代码:

main.cpp

#include "birdView.hpp"

int main(){ Mat v[4];

for (int i = 0; i < 4; i++) { char buf[10]; sprintf(buf, "%d.png", i); v[i] = imread(buf); }

BirdView b("config.yml");

b.setCarSize(240, 380); b.setChessSize(60,60); b.setMaskHeigth(200); b.setInternalShift(27,27);

//b.sourcePointClick(v);

while (1) { imshow("bird view", b.transformView(v)); if (waitKey(20) == 27) break; }}

birdView.hpp

//// Created by tanzby on 17-8-30.//

#ifndef BIRDVIEW_HPP#define BIRDVIEW_HPP

#include #include using namespace cv;using namespace std;

// calculate correspondence point for every input// 0: left up 1: right up 2:rigth down 3: left down

class BirdView{public: BirdView(const char* configFile = NULL) { SourcePoint_OK=ParamSet_OK = false; maskHeigth = clickCount = camID = 0; targetPoint.resize(4); sourcePoint.resize(4); try { carPic = imread("../img/car.jpg",CV_8UC4); } catch (...) { std::cout 3) { birdView.clickCount = 0; birdView.Birdtransform[camID] = getPerspectiveTransform(birdView.sourcePoint[camID], birdView.targetPoint[camID]); birdView.camID++; } if (birdView.camID



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3