资讯详情

资讯详情

车载360°全景影像实战:鱼眼相机标定与鸟瞰拼接全解析

gods-eye-view这个词直译过来是上帝视角放在车载影像、安防监控、机器人导航这些方向里指的是用一个从上往下看的俯视画面观察全场景。前两年我做了一套基于四路鱼眼摄像头的车载360°全景影像系统也就是常说的AVM环视影像真正跑完一轮之后最大的感受是成像不难拼接才难。本文会把整个项目拆开来讲从相机标定、鸟瞰变换、多路融合到实车调试把我踩过的坑和验证过的方案一并整理出来。这套内容更适合正在做图像算法、嵌入式开发或者想自己动手搭一套环视demo的工程师。我会尽量用工程语言而不是论文语言所有结论都基于我当时在Linux平台、C/OpenCV环境下实际跑通的经验。不同SoC平台在处理单元上会略有差异但底层思路是一致的。1. 需求拆解车辆四周无死角为什么是个伪命题1.1 我们说的上帝视角到底是什么纯从字面理解gods-eye-view就是假设有一台摄像机悬停在车辆正上方垂直向下拍摄。实际工程里没有谁会真去装一根十几米的支架所以业界普遍的做法是在车身前后左右装四颗广角鱼眼摄像头然后把各自拍摄的画面做畸变矫正、透视变换和拼接最后在屏幕上拼出一张从车顶鸟瞰的效果图。听起来不复杂但这里有三个容易被忽略的点。第一是遮挡不可消除。车身本身在正上方视角下是看不到的所以通常会在画面中央叠一个车模图片盖住中间区域把四路摄像头的有效画面围在车模四周。这个车模不是随便找一张图贴上去就行它的尺寸比例要和俯视图坐标系严格对齐否则会出现车模看着在车道中间、实际车身已经压线的情况。第二是视野重叠区只能靠算法猜。前置摄像头和前侧摄像头的覆盖范围一定有重叠重叠区域里同一个地锁、同一段车道线从两个镜头看过去的位置和角度都不同。拼接算法做得不好重叠区就会出现重影、断裂或者模糊。第三是动态场景下的一致性。车辆起步、转弯、倒车时四周场景是连续变化的四路摄像头的曝光、白平衡如果差异较大拼接边界就会像贴了一块块补丁。系统必须在一个很短的时间内完成多路画面的同步采集、校正和融合这对算力和时序设计都有要求。1.2 硬件布局与视野覆盖常见的四路环视采用前后左右布局前摄像头装在车头格栅或前保险杠中央后摄像头装在牌照灯附近左右摄像头分别装在两侧后视镜下方。这个布局决定了单颗摄像头的视场角必须足够大一般选用水平视场角180°到190°的鱼眼镜头。如果视场角不够车头左右两侧和车身侧方就会出现盲区如果太大边缘畸变区域会变成无效像素对分辨率反而是浪费。硬件选型上还有一个经常被忽视的参数——畸变程度。鱼眼镜头并不是畸变越小越好它用边缘大量畸变换来了大视场角。标定算法的任务就是把这些畸变画面还原成接近人眼习惯的透视效果。如果镜头质量太差边缘分辨率衰减严重标定参数再准也很难还原出可用画面。我当时用的方案是四颗1280x72030fps的摄像头通过MIPI接口接入一颗带ISP的SoCISP输出RAW图之后由算法模块做去畸变和拼接。这套方案的关键在于ISP和算法模块必须共享同一套标定参数不然后期调试时你会在画面上看到一种很奇怪的边缘跳动——其实就是ISP做了默认裁剪和算法校正的区域没有对应上。2. 鱼眼畸变校正所有后续算法的基础也是翻车重灾区2.1 鱼眼相机的成像模型普通镜头用针孔模型描述光线直线穿过光心投影到像素平面畸变相对较小用径向畸变和切向畸变参数就能修正。鱼眼镜头不一样它为了让视场角超过180°采用极端的非线性投影方式比如等距投影 r fθ、等立体角投影 r 2f·sin(θ/2) 等。简单理解入射角θ经过镜头之后在传感器上的成像半径r和θ不再是正切关系而是近似正比关系。这就带来两个工程问题。第一个问题是OpenCV里 calibrateCamera 和 fisheye.calibrate 不能混用。很多初学者拿普通镜头的标定代码去标鱼眼镜结果发现矫正后边缘出现严重拉伸、中心区域反而产生漩涡状扭曲。原因就是模型不对普通针孔模型的畸变多项式在鱼眼这种超大畸变下拟合不住。第二个问题是标定的棋盘格角度必须覆盖大视场角的边缘区域。鱼眼镜头的边缘畸变是最严重的如果标定图片里棋盘格只出现在画面中心边缘像素的校正参数完全没有约束输出结果必然不准。实际操作中我会准备一块A2大小的棋盘格板让标定板在画面各个角落都出现并且倾斜角度控制在30°到60°之间确保边缘区域有足够的特征点。2.2 用棋盘格完成内参标定内参标定是获取相机本身的焦距、主点坐标和畸变系数。以OpenCV的fisheye模型为例核心步骤分四步。第一步打印一张7x9的棋盘格每格边长建议30mm到50mm之间。注意棋盘格一定要用哑光纸打印并且贴在完全平整的硬板上我用的是亚克力板平整度比纸板好很多。第二步在不同位置、不同角度下采集20到30张图片。采集时保持棋盘格完整出现在画面内不要有遮挡也不要让棋盘格边缘被画面截断。光照要均匀避免玻璃反光和阴影干扰角点检测。第三步用 cv2.findChessboardCorners 提取角点然后调用 cv2.fisheye.calibrate 计算出内参矩阵K和畸变系数D。第四步检查重投影误差。我的经验是误差小于0.5像素算合格超过1.0像素就要检查是否存在标定板弯曲、角点误检或图片模糊的问题。误差太大时直接重新采集比调参更有效率。这里给出一段我当时精简过的标定代码片段方便你快速跑通流程import cv2 import numpy as np CHECKERBOARD (7, 9) # 内角点数 square_size 0.03 # 单格边长单位米 objp np.zeros((CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32) objp[:, :2] np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) objp * square_size objpoints [] imgpoints [] for f in sorted(image_files): img cv2.imread(f) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, CHECKERBOARD, None) if ret: objpoints.append(objp) imgpoints.append(corners) K np.zeros((3, 3)) D np.zeros((4, 1)) rvecs [None] * len(imgpoints) tvecs [None] * len(imgpoints) ret, K, D, rvecs, tvecs cv2.fisheye.calibrate( objpoints, imgpoints, gray.shape[::-1], K, D, rvecs, tvecs, cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC | cv2.fisheye.CALIB_CHECK_COND | cv2.fisheye.CALIB_FIX_SKEW ) print(K , K) print(D , D)跑通之后你会得到相机的内参矩阵K和畸变系数D。拿到这两个参数就可以对单路鱼眼图像做去畸变了。这一步的输出质量直接决定后续鸟瞰变换的效果所以在标定阶段多花时间是值得的。2.3 外参与垂直俯视的坐标关系内参解决的是像素点来自哪个方向的问题外参解决的是从哪个位置、什么角度观察这个方向的问题。要得到垂直俯视的鸟瞰图必须知道每一个摄像头相对地面的高度、俯仰角和朝向这些就构成外参矩阵。外参赛的获取有两种常见做法。第一种做法是在车身周围摆放标定布标定布上有规则的棋盘格或圆点图案然后通过检测特征点解算单应矩阵。缺点是标定布要足够大而且必须和车辆完全平行放置铺设要求很高。第二种做法是用多块小标定板分别放置在前后左右四个区域每颗摄像头单独解算它和地面之间的外参。这种做法比较灵活适合在实验室或车间里快速搭建。我的建议是如果条件允许直接用车规级标定布。它上面带有印好的特征图案把这套图案的角点坐标作为世界坐标再去计算每个摄像头对应的映射关系整个过程比单独用棋盘格标定板稳定得多。此外还要注意车辆的前后左右摄像头安装角度不同外参差异很大不能共用一组参数。3. 鸟瞰变换与四路拼接把四幅图变成一幅图的完整思路3.1 单应矩阵为什么能完成鸟瞰变换同一平面上的点在两个不同视角的相机图像之间的投影关系可以用一个3x3的单应矩阵H描述。车辆周围的地面可以近似看作一个平面所以每颗摄像头捕捉到的地面图像和从正上方看地面的图像之间也满足单应关系。换句话说我只要找到四个或更多地面上的对应点比如标定布上已知坐标的特征角点就能解出每颗摄像头图像到鸟瞰图坐标系的映射矩阵H。OpenCV里直接用 cv2.findHomography 就能算# src_points: 图像坐标 # dst_points: 俯视图坐标 H, _ cv2.findHomography(src_points, dst_points, cv2.RANSAC) bird_view cv2.warpPerspective(img, H, (output_w, output_h))这个变换看起来很简单但它有几个重要前提。第一地面必须近似平整。如果车位地面有较高减速带或凹陷对应区域的鸟瞰图会产生扭曲这是单应变换天生的局限不是参数调不好。第二单应矩阵只能保证地面这一个平面上的点对齐。立体物体比如旁边停的车辆会在俯视图里产生拉伸和形变。所以你会看到AVM画面里车辆四周的地面是拼接好的但旁边立着的车看起来是歪着的这其实是正常现象。第三H矩阵是4个摄像头各自独立的。前后左右每颗镜头都要单独计算一份映射关系在运行时把这四路映射叠加到同一个俯视图坐标系中。3.2 四路拼接的坐标统一与融合策略四路图像变换到同一个坐标系之后下一步是把它们拼起来。这一步有两个子问题一是确定每路图像在最终输出画面里的摆放位置二是处理重叠区域。摆放位置在标定阶段就已经确定了。我以车辆几何中心为原点前后左右四路图像的鸟瞰图分别放到对应的象限区域。为了让接缝尽可能落在车道线概率较低的位置我会在标定阶段就把每路图像的边界往外扩一点让重叠区域控制在一个合理范围内。重叠区域的处理方式我先后试过三种可以给个明确的对比结论融合方式实现成本边缘重影亮度过渡适用范围简单Alpha融合低容易重影一般快速验证加权均值融合中重影较少较好大多数场景拉普拉斯金字塔融合高重影最小最自然高质量前装实际项目里如果算力有限加权均值融合是最有性价比的选择。具体做法就是给重叠区域的两侧分别设置一个距离权重从A图过渡到B图时A权重从1递减到0B权重从0递增到1。权重选择可以基于像素点到接缝中心线的距离来计算也可以直接用OpenCV的 distanceTransform 生成一张平滑权重图。拉普拉斯金字塔融合效果最好原理是把图像分解成不同频率的带通分量对每个频带分别做加权融合再重建能有效避免高频细节在接缝处被抹掉。缺点是内存占用大、计算量高用在实时系统中要仔细评估帧率是否满足要求。3.3 曝光、亮度与缝隙处理影响全景图真实感的三件事如果只做几何对齐拼接出来的画面仍然可能出现很明显的明暗分界线。原因很简单四颗摄像头朝向不同周围环境光照差异大同一时刻四路图像的曝光值不可能完全一致。解决曝光不一致的方法业内常用的是全局亮度均衡。我当时的做法是先统计四路图在重叠区域的平均亮度然后选一个目标亮度对每路图做全局增益补偿。更精细的做法是对每个通道单独计算增益也就是在YUV空间里对Y通道做亮度均衡对U/V通道做色彩均衡。另一个很关键的细节是白平衡一致性。四路摄像头如果白平衡各自漂移拼接图上会出现同一个区域一边发青一边发黄。解决方案有两个层面一个是硬件层面锁死白平衡另一个是算法层面做色彩校正矩阵。对前装项目锁死白平衡是基本要求因为自动白平衡在真实场景里一定会把画面弄得花花绿绿。缝隙处理方面除了亮度均衡还要注意对齐偏差。即使标定做得很好车辆在行驶中也会有轻微震动导致镜头位置变化拼接边界会出现细微错位。比较好的工程手段是把接缝隐藏在多纹理区域或车辆车模的覆盖范围内尽量减少用户感知。接缝完全不可见的目标很多时候靠的不是算法而是对齐精度和重叠区宽度之间的平衡。4. 实车调试中真正决定成败的细节4.1 标定数据采集与废帧筛选有很多项目在仿真或桌面上运行得很漂亮一上实车就出问题核心原因就在标定数据采集环节。车规环境里标定布往往铺在户外停车场一阵风、一辆车经过、一片云遮住太阳都会让标定数据质量变差。所以我对标定数据采集有两条强制要求。第一采集前必须确认摄像头画面处于正常曝光状态不能在过曝或欠曝的情况下采集。过曝会让棋盘格白色区域饱和角点检测会偏移半个像素甚至更多欠曝会让黑色区域噪声变大亚像素角点容易跳变。第二采集到的每一帧都要做质量检查。我会用一个脚本自动检测棋盘格角点然后过滤掉以下类型角点数不对的、棋盘格太小或太大导致角点间距异常的、重投影误差超过阈值的。手动一张张筛选当然也可以但效率太低而且容易漏掉一些看起来还行、实际角度很差的图。建议你在标定程序里加一个反馈界面实时显示当前帧检测到的角点数和重投影误差不合格的直接丢弃重新拍摄。这个界面花不了多少时间但能省下后期反复排查标定异常的大量精力。4.2 车模叠加、摄像头延迟与显示时序车模叠加看起来很简单其实涉及坐标系匹配。车模图的尺寸必须和俯视图坐标系的单位一致否则车模和四周画面的比例关系就是错的。我用的是像素到实际距离的比例尺比如俯视图每像素对应5毫米车模实际长度为4.6米那么车模在图像里的宽度就是920像素。按这个尺寸去缩放车模png再进行alpha通道合成不仅在视觉上准确也能保证转向时车身轮廓和周围障碍物的相对位置关系正确。摄像头延迟这个问题很多人容易漏掉。四路摄像头通过MIPI或以太网接入每一路的传输延迟、ISP处理延迟不可能完全一致。如果延迟差异达到几十毫秒车辆在快速移动时拼接接缝处会出现动态撕裂。我当时的处理是用了一个简单的同步锁存机制多路摄像头统一由同一帧同步信号触发曝光或者采集端通过时间戳对齐四路图像后再送入拼接模块。显示时序还有个问题就是拼接算法本身的耗时不能波动太大。如果某几帧因为负载高导致拼接时间翻倍画面就会出现卡顿感。工程上我对拼接模块做了一个很有效的优化把所有标定参数预计算成查找表运行时每路图只需要查表做remap这样frame time可以稳定控制在16ms以内。4.3 先离线再上车的整体调试流程复盘这个项目我最有价值的经验就是任何算法改动都要先离线验证再上车实测。离线环境可以录制四路原始视频通过离线工具反复回放快速对比不同参数、不同融合策略的效果。上车测试只做两件事一是确认离线参数在真实环境里没有明显偏差二是采集新场景数据来补充离线集。这个流程看起来笨但能极大减少在车里苦等的无效时间。车上的环境嘈杂、供电电压波动、摄像头震动干扰因素很多根本不适合做精细的算法调试。录制离线数据时我建议至少覆盖以下几种场景标准车位倒车入库、侧方停车、夜间地下车库、白天逆光环境、雨天地面反光环境。每类场景至少录制30秒录制时车辆要行驶、转向、停顿模拟真实使用状态。把这些数据沉淀下来后面无论换SoC平台还是调融合参数都能快速回归测试。最后分享一个小经验不要一上来就追求看不到任何拼接缝。一款全景影像系统用户真正关注的是倒车和过窄道时能不能看清障碍物、判断距离而不是为了看一条地缝是否完全对齐。把标定精度、亮度一致性和动态稳定性做到位接缝自然就淡了。如果为了消除一条接缝而引入大量运算导致帧率下降反而得不偿失。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →