3. 环视系统架构
各位同学,今天我们进入环视系统最核心的一步——摄像头标定。说实话,我在刚入行时觉得标定就是个“拍棋盘格、跑脚本”的流水线活。直到有一次,我在某量产项目上发现环视拼接图总是有重影,怎么调参数都搞不定。最后排查了三天,才发现是标定板本身就不合格。嗯,从那以后,我再也不敢小看标定这个环节了。
标定的目的是什么?
简单说,标定就干两件事:去畸变和求内参。你想想看,车载摄像头用的都是广角镜头,甚至鱼眼镜头。拍出来的画面边缘是弯的——直线变成弧线,这就是畸变。如果不做矫正,环视拼接出来的图像根本对不齐。
核心目标:
- 去畸变:把弯曲的直线拉直,消除径向畸变和切向畸变
- 求内参:获取焦距(fx, fy)、光心(cx, cy)和畸变系数(k1, k2, p1, p2, k3)
- 求外参:确定摄像头相对于车辆坐标系的位置和姿态(这个我们后面章节细讲)
内参矩阵长这样:
内参矩阵 K = [fx 0 cx]
[0 fy cy]
[0 0 1]
fx和fy是焦距(单位是像素),cx和cy是光心偏移。畸变系数里,k1/k2/k3管径向畸变(就是那种“桶形”或“枕形”变形),p1/p2管切向畸变(镜头和传感器不平行导致的)。
我个人习惯把标定理解为“给摄像头做体检”。体检报告就是内参和畸变系数。有了这份报告,我们才能把摄像头看到的扭曲世界,还原成真实世界的几何关系。
标定板怎么选?棋盘格 vs 圆点阵
标定板是标定的“尺子”。选错了,后面全白干。市面上主流就两种:棋盘格和圆点阵。我分别说说它们的脾气。
| 对比项 | 棋盘格 | 圆点阵 |
|---|---|---|
| 角点检测 | 亚像素角点,精度高 | 圆心拟合,受透视影响小 |
| 光照敏感度 | 中等,反光时角点易丢 | 较低,圆形轮廓更鲁棒 |
| 部分遮挡 | 角点丢失后无法使用 | 只要看到足够多圆,仍可标定 |
| OpenCV支持 | findChessboardCorners 非常成熟 | findCirclesGrid 需要调参 |
| 车载场景推荐 | 室内/实验室标定 | 产线/户外标定 |
我在项目中遇到过一个问题:用棋盘格在户外标定,阳光一照,棋盘格反光严重,角点检测直接崩了。后来换成哑光材质的圆点阵标定板,问题就解决了。所以我的建议是——实验室用棋盘格,产线或户外用圆点阵。
避坑指南:
我曾经在某个项目里用了A4纸打印的棋盘格,结果标定出来的内参偏差很大。后来发现纸不平,有褶皱。标定板必须贴在刚性平板上,最好是亚克力或铝板。纸板?千万别用。
标定板尺寸怎么定?
这里有个经验公式:标定板在画面中占比至少30%,最好50%以上。格子边长和圆点直径,我一般选20-40mm。太小了,远距离看不清;太大了,近距离拍不全。
棋盘格的格子数,我推荐9×6或11×8(内角点数)。圆点阵推荐7×7或9×9。记住:格子数必须是奇数×偶数,这样角点才有唯一的方向性。
OpenCV标定流程简介
OpenCV的标定流程,说白了就是三步:找角点 → 算内参 → 去畸变。我画了个流程图,你们感受一下。
核心代码其实不长,我贴一段最常用的:
import cv2
import numpy as np
# 1. 准备棋盘格角点的世界坐标
pattern_size = (9, 6) # 内角点数
square_size = 25.0 # 格子边长 (mm)
objp = np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2)
objp *= square_size
# 2. 存储所有图像的世界坐标和像素坐标
objpoints = [] # 世界坐标
imgpoints = [] # 像素坐标
# 3. 遍历图像,找角点
for fname in image_files:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
if ret:
# 亚像素精细化
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
objpoints.append(objp)
imgpoints.append(corners2)
# 4. 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
# 5. 去畸变
img_undistorted = cv2.undistort(img, mtx, dist, None, mtx)
注意:
标定用的图像至少拍15-20张。要覆盖画面的各个区域——左上、右下、中间、倾斜角度都要有。我曾经只拍了10张,结果标定出来的内参在画面边缘误差很大。后来补到20张,重投影误差从0.8像素降到了0.2像素。
重投影误差怎么看?
标定完成后,OpenCV会返回一个重投影误差(reprojection error)。这个值越小越好。我一般这样判断:
- < 0.3 像素:优秀,可以量产
- 0.3 - 0.5 像素:合格,需要检查是否有异常图像
- > 0.5 像素:不合格,检查标定板或拍摄质量
如果误差偏大,别急着调参数。先检查:标定板平不平?图像有没有模糊?角点检测有没有漏检?我遇到过最离谱的一次,是标定板上有指纹印,导致角点检测偏移了半个像素。
我的习惯:
标定完成后,我会把去畸变后的图像和原图叠在一起看。重点关注画面边缘的直线——比如停车场的白线、建筑物的边缘。如果这些直线在去畸变后变直了,说明标定没问题。如果还有弯曲,那就得重新来过了。