拼接区域角点提取通过提取拼接区域的角点最终获得的全景鸟瞰图与前面工作的对比如下,左侧为以前的工作中提取棋盘格角点的方法且没有使用光流微调,右侧是我们提取拼接区域角点的拼接结果。差别就很大。
左:提取棋盘格角点 右:提取拼接区域角点再仔细想想这样做是很合理的,因为AVM产品中最影响驾驶体验的就是拼接区域的伪影,其他一切都好说。标定布的测量误差会导致单应矩阵计算不准确,这一问题是无法避免的,我们选择拼接区域的特征点计算单应矩阵,使得拼接区域的拼接结果非常准确。随之而来的可能是其他区域的不准确,例如贴近车身周围的棋盘格区域,但是这些区域用户并不care,虽然车身周围会引入些许误差,但只要车身与周围环境的的相对位置呈现的正确,不会引起碰撞事故,对于驾驶员来讲就是OK的。以上就是为什么我们要开发一种基于自动提取角点算法的汽车标定方法。
标定场景对于标定布上的棋盘格,我们可以使用opencv的角点检测函数findChessboardCorners来提取,建议读者自行阅读该函数源码,我们提出的方法的思路就来源于这个算法。下面我们来简述我们提取大方格角点的方法:算法流程:
1. 双峰自适应阈值二值化 2. 多边形检测 3. 四边形筛选 4. 提取四边形顶点 |
解释下这样做的理论基础:标定布上的大块黑色方格被白色布包围在中间,我们的目的是将标定布上的黑色方格与白色布通过某个阈值分割开。通常白色布的亮度值在直方图亮部阈值附近,黑色方块的亮度值在直方图暗部阈值附近。因此取两个亮度峰值取平均,一定可以将黑色方块与白色标定布分开。当然,光照条件不能过于恶劣,对于这一点即便是opencv提取棋盘格的方法也要限定光照条件。
基于自适应阈值的二值化·多边形检测以及四边形筛选约束条件
1. 多边形拟合结果必须为四边形,即筛选出二值化图中的四边形 2. 四边形面积必须大于某阈值(opencv源码中这个超参适用于筛选棋盘格的,我们要把这个阈值搞得大一些,用于筛选大方格) 3. 相邻边长不可相差过多 4. 选取黑色的四边形,而不是白色的四边形 5. 限制四边形分布范围,即四边形不能过于靠近图像边缘,具体情况与相机的位姿相关 |
(2)对于正前方、正后方,我们提供了广角、超广角。超广角由前方鱼眼相机通过多次投影变换得到。
(3)左右视角根据不同的要求,我们提供三种不同的辅助视角
从以上的demo中不难看出,我们是通过投影变换的方法,将鱼眼相机的图像从本身相机部署的视角,转换为我们想要的视角。例如:对于车轮视角的demo,不难看出这个辅助视角中全部都是左、右两侧的鱼眼相机中的内容。已知这两个鱼眼相机A和B的位姿为:朝向左右两侧的地面;而我们实际想获取的车轮视角对应的相机a和b摆放位姿应该为朝向前方偏下,即朝向车轮的那个位置,这是两个虚拟的相机。那么如果我们通过标定+PNP的方法计算出右侧鸟瞰相机A与右侧虚拟相机a之间的位姿关系Rt,进一步地计算出A、a这两个不同位姿下拍摄某个平面(地面)时,他们之间的投影关系H,就可以将实际的鱼眼相机视角A转换为虚拟的车轮视角a。左侧鱼眼相机B视角与左侧虚拟车轮视角b同理。
图中n表示穿过相机A的光心垂直于平面π的单位向量,d为相机A与平面π之间的距离。π平面上的点X分别投影到A、B相机平面上的m和m'上。X在A相机坐标系下坐标为X1,在B坐标系下坐标为X2。因此有:
假设相机A与B之间的相对位姿为R t,即旋转和平移,那么有:
根据相机成像模型,其中K为相机内参,s1为某像素的实际深度
因此推导到最后,H结果为:
由于H有尺度不变性,因此
经过上述推导后我们可以得出结论:假设我们可以获取到两个相机之间的位姿关系R和t、相机的内参、相机与π平面之间的向量n和距离d,那么我们就可以计算出π平面在两个相机图像平面上的投影关系H。以上即为单应矩阵求解的一种方法(基于真实的物理模型),也是本章中AVM辅助视角所用到的方法。那么,如何才能获取到相机之间的位姿关系呢?我们用到了PNP的方法。
其中(X,Y,Z,1)为某一个相机坐标系下的三维点齐次坐标,(u,v)为另一个相机图像平面的二维坐标点。这两组点在真实世界中应该是一一对应的关系。K为相机内参,Rt为相机之间的外参。这个模型其实就是SLAM14讲中的单目相机物理模型,世界坐标系->相机坐标系->图像坐标系的过程。求解方法有“直接线性法”、“最小化重投影误差”等,我们就不展开来说了。针对于我们的AVM算法:先验信息必然是由标定布来提供的(即第一章中讲到的标定布上的大方格角点信息,包含图像上的二维坐标和标定的三维坐标)。虚拟相机的位置、朝向由我们自己设定,换句话说虚拟相机坐标系是根据不同功能(例如车轮视角、超广角等)由算法工程师自行设计的,因此我们是可以获取到标定布上的角点的三维坐标。二维点:图像中提取(1.1 1.2中已经讲述了提取方法)三维点:标定
车轮视角算法坐标系示意图
车轮视角算法坐标系示意图2d图像坐标系没什么可说的,就是真实相机拍摄到的图像上黑色方格角点的坐标3d相机坐标系是一个虚拟的视角,模拟的是朝向正前方的一个视角,即y垂直地面,x垂直车身向右,z朝正前方由于我们已知标定布的全部尺寸参数,因此可获取虚拟相机坐标系下标定布上黑色方格角点的3维坐标。用PNP算法可以计算出实际相机与虚拟相机之间的位姿关系R,t。
//函数定义 void solveRtFromPnP(const vector &corners2D, const vector &obj3D, const Mat &intrinsic, Mat &R, Mat &t) { Mat r = Mat::zeros(3, 1, CV_32FC1); solvePnP(obj3D, corners2D, intrinsic, cv::Mat(), r, t); Rodrigues(r, R); } //函数调用 solveRtFromPnP(corners_2D, obj_3D, m_intrinsic_undis, R, t);
以上,我们获取到了虚拟相机->真实部署相机之间的位姿R,t。至于R,t为什么不是从真实部署相机->虚拟相机,可以从PNP算法的公式推导中得知(前面已经讲过),或者见视觉SLAM第二版P180-P181。再回到投影变换H模型图中,图中的A就是虚拟相机,B就是真实部署相机。我们已经通过PNP计算出虚拟相机->真实部署相机的R,t。但是我们想要的是如何将真实相机拍摄到的某个平面(对于车轮视角而言是地面)通过H转换到虚拟相机的视角下。因此我们需要对R,t做一些处理,将其转化为真实部署相机->虚拟相机的位姿关系:公式推导:代码:求解单应矩阵:
//基于Rt计算H Mat solveHFromRt(const Mat &R, const Mat &t, const float d, const Mat &n, const Mat &intrinsic) { Mat intrinsic_inverse; invert(intrinsic, intrinsic_inverse, DECOMP_LU); Mat H = intrinsic * (R + t / d * n.t()) * intrinsic_inverse; return H; }
PNP求解外参:/****************************************************************************************************************/ //R t:PNP算出的结果,从虚拟相机->真实部署相机的位姿 //计算出 真实部署相机->虚拟相机的位姿 Mat R_Camera2Real; invert(R, R_Real2Virtual, DECOMP_LU); Mat t_Real2Virtual = -R_Real2Virtual * t; //虚拟相机到地面的垂直单位向量 Mat n = (Mat_(3, 1) << 0, 1, 0); //真实部署相机到地面的垂直单位向量 n = R * n; // rotation float theta_X = m_wheel_sight_angle / 180.f * 3.14f; Mat R_virtual = (Mat_(3, 3) << 1, 0, 0, 0, cos(theta_X), -sin(theta_X), 0, sin(theta_X), cos(theta_X)); R_Camera2Virtual = R_virtual * R_Camera2Virtual; t_Camera2Virtual = R_virtual * t_Camera2Virtual; //计算单应矩阵H:真实视角->虚拟视角 Mat H = solveHFromRt(R_Camera2Virtual, t_Camera2Virtual, d, n, intrinsic);
几点说明:·n(0,1,0),说明单应矩阵选取的平面为地面。不要忘了我们最开始强调的,H描述的是在不同位姿下的两个相机cam1,cam2拍摄同一个平面(例如标定板),这个平面在两个相机成像平面上的成像结果之间的变换关系。因此这个平面的选择,对最终的投影结果有很大的影响。车轮视角选取地面作为我们要进行投影的平面,最终的效果非常nice。·代码中还包含rotation部分,这是因为我们在标定的时候让虚拟相机朝正前方,但显然理想的车轮视角相机应该朝向车轮,即应加一个俯仰角pitch。远离图像中心的区域被严重拉伸这张图的分辨率已经很大,然而在图像的边缘像素跨度太大,图中左侧的黑色车辆被严重拉伸。也就是说同样一辆车,在图像的中心可能只需要100个像素来呈现(例如中间的白车),而在图像的边缘被拉伸的更长,可能需要1000个像素才能完全呈现出来,如果我们想要获取更大范围的视野,就需要超级大分辨率的图像。垂直地面的柱子是斜的:这是因为另外一种透视畸变(见附录),即”近大远小“。类似于平行的车道线在图像中会交于一点的原理。我们可以记住一个理论:只有垂直于相机光轴的那个平面上的平行线,在相机图像平面上的成像结果是平行的(类似于BEV视角);与光轴不垂直的平面上的平行线,在相机成像平面上肯定交于一点,即”近大远小”。对图像做去畸变之后,图中的柱子是斜的,因为我们的前置摄像头有pitch俯仰角,它的光轴超前下方,并非垂直于柱子所在的平面,即不垂直于汽车正前方的平面。解决方案:我们的思路是:首先通过位姿变换的方法将相机摆正(依然是虚拟相机的思想),从而消除“近大远小”的透视畸变和相机roll角带来的视觉不适,然后将图像分为左、中、右三部分,中间这部分的“拉伸”透视畸变较小;对两侧透视畸变较大的部分强制进行某种投影变换,减小拉伸效果。具体如下:·相机位姿矫正
相机位姿矫正示意图相机位姿矫正算法流程的思路依然是将 真实相机视角->虚拟相机视角,以正前方的相机为例,算法流程如下表:算法流程
1. 假设虚拟相机的位姿为朝向正前方,光轴与正前方的那面墙垂直,坐标系x、y、z如上表中“位姿校正的广角”所示 2. 标定出地面上那些角点在虚拟相机坐标系下的三维坐标 3. 在真实相机拍摄的图像中提取角点的图像坐标(实际上在第一章的标定过程中已经完成) 4. PNP计算虚拟相机->真实相机的位姿 RT 5. 计算真实相机->虚拟相机的位姿rt 6. 我们选定的是垂直于相机光轴的平面,计算这个平面从真实相机->虚拟相机的单应矩阵H,在我们的算法中,这个平面距离相机的距离d=10m,这是一个经验值。 |
广角与超广角实际的实现方法如下图所示,在广角图像上选取4个点(即远离图像中心在图像左右两边的两个小长方形),并设置这四个点做投影变换的结果(这4个点是通过大量实验调试出的一组最优超参数),使用这四对匹配点计算单应矩阵H。即计算将小长方形压缩成梯形的单应矩阵H。
图像两侧的单应变换在图像两侧所进行的单应变换有将像素在x,y方向做压缩的效果,从一定程度上减小了透视畸变带来的拉伸问题。做了投影变换后,在相同幅面的图像中,我们可以看到更大范围的内容,即FOV更大,因此称为超广角。
Mat H_left = getPerspectiveTransform(p_src_left, p_dst_left); Mat H_right = getPerspectiveTransform(p_src_right, p_dst_right);
前、后、左、右
纹理映射示意图注意:世界坐标系、3dsmax坐标系、车身统一坐标系,这三个表达的是同一个意思。坐标原点在汽车中心,右手X,汽车前方Y,垂直地面向上Z。黄色坐标轴为前置相机坐标系。如图所示,3dsmax中的小长方体代表现实世界中在汽车正前方的柱子。该立柱上的一点(第二张图上长方体上的圆圈)通过相机成像模型穿过相机光心映射到成像平面上(即第一张图中柱子上的圆圈位置)。在成像过程中,同时也要穿越我们的3D碗模型。我们通过相机成像模型,可以想象这条光线的路径:现实世界中的物体->3D碗模型顶点->相机拍摄到的图像(去畸变后),也就是通过相机成像模型我们建立起了3D碗模型顶点与图像纹理之间的映射关系。我们通过3dsmax制作的 3D碗模型是一个OBJ文件,解析出来的是3dsmax坐标系下的坐标。我们需要将其转换到相机坐标系下,才可以进行上面说的纹理映射。3dsmax是以汽车的中心为原点,右手为X,前向为Y,垂直地平面向上为Z的,在标定过程中,我们可以获取上图中角点在3dsmax坐标系下的3维坐标;且可以在前置相机图像中通过算法提取角点的图像坐标,因此3dsmax与相机之间的位姿关系,同样可以通过PNP计算。代码如下:
//calculate pose solveRtFromPnP(img_corners, obj_corners, intrinsic_undis, R, t); //3dsmax->camera Mat pts_3DsMax = (Mat_<float>(3, 1) << vertex[i].x, vertex[i].y, vertex[i].z); Mat camera_points = R * pts_3DsMax + t; //camera coordinate ->img coordinate Mat coor = intrinsic_undis * camera_points / camera_points.at<float>(2, 0);
上述代码将解析出来的3dsmax坐标系下的 3d碗模型的顶点三维坐标转换到相机坐标系下,然后通过相机内参转换到图像坐标系。最终建立起3d碗模型与图像纹理之间的映射关系。算法流程如下:算法流程1. 标定3dsmax坐标系下 标定布上角点的三维坐标。(即车身中心为原点的车身坐标系) 2. 检测相机拍摄到图像中标定布上角点的图像坐标。(第一章中已标定) 3. 利用1、2的坐标信息,通过PNP计算出 3dsmax坐标系与相机坐标系之间的位姿关系 R,t 4. 解析3d碗状模型的obj文件 5. 将解析出来的3d碗状模型的顶点三维坐标,通过R,t转换到相机坐标系下 6. 通过内参将相机坐标系的坐标转换到图像坐标系,建立起3d模型与相机图像之间的纹理映射关系 |
融合区相邻两个3D 碗模型上存在重叠区,由于标定误差和avm 3d算法固有的误差,这部分重叠区域会有较严重的重影(下一节中会讲到)。因此需要用融合算法进行平滑过渡。具体算法可以参考文章[2]
融合算法示意图大体思路就是计算3d模型上的顶点与缝合线之间的角度,然后求一个比值,没什么太大难度。
AVM 3D病态问题现实世界中立柱上的一个点通过相机模型分别映射到前置相机、左侧相机的成像平面上从而生成图像img1,img2。在这个过程中会穿过重叠区的前3D碗模型的A点以及左3D碗模型的B点,AB显然不是同一个点。换句话说,img1和img2上相同的纹理(例如现实世界中的柱子)在纹理映射的过程中并没有映射到3d碗模型的同一个位置上,不能够准确地重合,且通常会在模型重叠区有较大的错位。因此,我们可以得出结论:AVM的3D是一种伪3D,是计算机图形学中使用将纹理图映射到3D模型上呈现3D效果的手段,但它不是真正的3D。根本原因是:单目相机模型丢失了深度信息。针对这种问题,通常的优化方法为:·将拼接缝位置设置在较不明显的位置上,例如靠近左右两侧。因为驾驶员开车的视觉习惯通常是正前方,左右两侧的内容不会过分关注。且在切换到其他视角的时候,拼接错位会被汽车模型挡住。·适当缩小拼接区范围·尽量让碗底大一些,让车身附近的地面有较好的拼接效果。3d碗的优化问题属于经验性的问题,需要通过实验慢慢调整适合自己的碗模型。
近大远小·“拉伸”一张图,言简意赅:
边缘拉伸
全部0条评论
快来发表一下你的评论吧 !