作者丨苹果姐@知乎来源丨https://zhuanlan.zhihu.com/p/508090405编辑丨3D视觉工坊在自动驾驶实际应用中,对相机传感器的要求之一是拥有尽可能大的视野范围,鱼眼相机由于视场角(FOV)极大(标准镜头45°左右,广角镜头可大于60°,鱼眼相机可大于180°,也就是说甚至可以看到镜头后面的场景),在自动驾驶汽车上广泛使用。所以如何直接从鱼眼相机图像中估计深度是自动驾驶实际落地的一个重点问题。 然而鱼眼相机的大FOV带来的是非常大的非线性畸变,这决定了相比一般针孔相机,鱼眼相机图像的深度估计方法将更加复杂。虽然一个思路是对畸变进行矫正后再进行深度估计,但校正必定会引起FOV的缩小,失去使用鱼眼相机的意义,所以直接从鱼眼相机原图进行深度估计是学术界研究的热点问题。目前Valeo公司在鱼眼图像深度估计的研究中处于领先地位,博主之前对该公司的一系列成果做过一个简要综述:https://zhuanlan.zhihu.com/p/495899515该公司的github地址是:https://github.com/rvarun7777,核心代码都存放在omnidet路径下,但与论文omnidet[1]中描述的内容差距较大,仅开放了distance/detection/semantic/motion四个独立任务以及联合多任务的基线版本,其中distance任务即鱼眼深度估计任务,相比FisheyeDistanceNet[2],并没有使用可变卷积、超分辨率网络、速度监督信号、交叉一致性损失等trick,而是仅保留了和monodepth2[3]一致的重投影ssim+l1损失以及平滑损失,但重投影部分是鱼眼图像深度估计的核心,还是值得我们深入学习。以下进行相关部分的原理分析和核心代码解读:[1]OmniDet: Surround View Cameras based Multi-task Visual Perception Network for Autonomous Driving (ICRA 2021)[2] FisheyeDistanceNet: Self-Supervised Scale-Aware Distance Estimation using Monocular Fisheye Camera for Autonomous Driving (ICRA 2020)[3] Digging Into Self-Supervised Monocular Depth Estimation (ICCV 2019)首先,从大的理论上,鱼眼图像自监督深度估计和针孔图像自监督深度估计一致,都是采用sfm-learner[4]的方法:[4] Unsupervised Learning of Depth and Ego-Motion from Video (CVPR 2017)即利用视频连续帧图像之间的重投影对位姿和深度的约束,来实现网络对深度和位姿的学习。本文假设读者已掌握针孔图像重投影的原理,如果不甚了解请先阅读博主的以下文章:https://zhuanlan.zhihu.com/p/462656351现在正式进入鱼眼图像自监督深度估计内容。
for i, _r in tqdm(enumerate(r)): a = np.roots([D[3], D[2], D[1], D[0], -_r]) a = np.real(a[a.imag == 0]) try: a = np.min(a[a >= 0]) angle_LUT[i] = a except ValueError: # raised if `a` is empty. print(f"Field angle of incident ray is empty") pass
在求θ(代码中的angle_LUT))过程中,由于np.roots函数返回的既有实数根又有复数根,所以需要过滤掉复数根,并且选取正的最小值(θ范围为0~FOV/2)。而且由于这个步骤耗时较长,而且是固定值,只需要计算一次,所以可以先离线计算出θ矩阵,在训练阶段直接使用。得到θ和φ后,再根据distance网络输出的距离值d,即可得到P点在相机坐标系下的3D坐标[X, Y, Z]:e.根据几何关系可得:Z = d * cos(θ)X = d * sin(θ)* cos(φ)Y = d * sin(θ)* sin(φ)
x_cam, y_cam, z = [world_coords[:, i, :].unsqueeze(1) for i in range(3)] # angle in the image plane theta = torch.atan2(y_cam, x_cam) # radius from angle of incidence r = torch.sqrt(x_cam * x_cam + y_cam * y_cam) # Calculate angle using z a = np.pi / 2 - torch.atan2(z, r) distortion_coeffs = distortion_coeffs.unsqueeze(1).unsqueeze(1) r_mapping = sum([distortion_coeffs[:, :, :, i] * torch.pow(a, i + 1) for i in range(4)])
同样由于distancenet输出的是点P到光心的距离D,所以在模型评测时需要根据事先保存的θ矩阵先转换成深度值d,再与激光点云投影得到的gt深度图进行对比评测:d = D * cos(θ)其中点云投影过程与第三节过程相似,只是在生成gt深度图的时候有自己特殊的处理:a. 已知激光点云坐标[X, Y, Z], 通过外参矩阵T转换到相机坐标系坐标[x, y, z]b. 执行第三节的a-d步骤,但保留z坐标,得到像素坐标系坐标[u, v, z]c.按照h*w分辨率初始化深度图gt_depth(h*w*1),过滤掉超出边界的坐标、z<0的坐标d.由于分辨率的限制,和雷达与相机视角不同的影响,可能存在多个点映射到同一个像素坐标的情况,只保留深度值z最小的点。e.根据d和gt_depth进行相关指标的评测注:由于c、d步骤中的过滤操作并不影响图像重构(target帧多个点映射到source帧同一个点的情况),所以在第三节中并不需要进行过滤。具体实现请参照monodepth2代码中generate_depth_map方法,并自行扩充到鱼眼相机投影模型。至此,关于鱼眼图像自监督深度估计原理分析和核心代码解读告一段落,希望读者留言交流。本文仅做学术分享,如有侵权,请联系删文。