第二章:相机模型与标定——让机器看懂世界的“眼睛”
大家好,我是你们的老朋友。今天我们来聊聊视觉SLAM里最基础、也最容易被忽视的一环——相机模型与标定。
你想想看,SLAM系统要定位、要建图,所有信息都来自图像。如果相机本身没搞清楚,后面的算法再牛也是白搭。说白了,相机标定就是给机器配一副“度数准确”的眼镜。
核心观点:没有准确的相机内参,SLAM的精度就是空中楼阁。我见过太多新手,花大量时间调后端优化,结果发现是标定板没拍好——白忙一场。
2.1 针孔相机模型——最朴素的成像原理
先别急着上代码,我们得把原理搞透。针孔相机模型,说白了就是小孔成像的数学版本。
想象一个暗箱,前面戳个小孔,光线穿过小孔在后面的胶片上成像。嗯,这就是最原始的相机。但现实中,我们用的是透镜,不是小孔。不过数学上,我们可以等效成针孔模型。
核心公式就一个:
s * [u, v, 1]^T = K * [R|t] * [X, Y, Z, 1]^T
其中K就是内参矩阵,包含fx, fy(焦距),cx, cy(主点偏移)。
我个人习惯把内参矩阵写成这样:
K = [fx, 0, cx;
0, fy, cy;
0, 0, 1]
fx和fy为什么可能不一样?因为像素不是正方形的,或者传感器有缩放。我在项目中遇到过一台工业相机,fx和fy差了3%,如果不标定,建图直接歪了。
小技巧:主点(cx, cy)理论上应该在图像中心,但实际总有偏差。我建议标定后检查一下,如果偏差超过10个像素,可能是标定板没放正。
2.2 畸变模型——镜头不是完美的
现实中的镜头都有畸变。你想想看,广角镜头拍出来的照片,边缘的直线都弯了。这就是畸变。
畸变主要分两种:
- 径向畸变:光线穿过透镜时,边缘弯曲更厉害。用k1, k2, k3三个参数描述。
- 切向畸变:透镜和传感器不平行。用p1, p2两个参数描述。
我曾经在调试一个扫地机器人时,发现地图边缘总是扭曲。查了两天,最后发现是切向畸变没标定——镜头装歪了0.5度。从那以后,我每次标定都会检查p1, p2的值。
畸变矫正的公式长这样:
x_distorted = x * (1 + k1*r^2 + k2*r^4 + k3*r^6) + 2*p1*x*y + p2*(r^2 + 2*x^2)
y_distorted = y * (1 + k1*r^2 + k2*r^4 + k3*r^6) + p1*(r^2 + 2*y^2) + 2*p2*x*y
看着复杂?其实你不需要手算。OpenCV和Matlab都帮你搞定了。
避坑指南:我曾经用一张标定板拍了20张图,结果标定出来的畸变参数特别大。后来发现,标定板上有污渍。记住:标定板必须干净、平整、无反光。
2.3 相机标定实战——用OpenCV搞定
好了,理论讲完了,咱们来点实际的。我用OpenCV标定过不下50台相机,流程早就烂熟于心。
核心步骤就三步:
- 准备标定板:打印一张棋盘格,贴在硬纸板上。格子数建议9x6或8x6。
- 采集图像:从不同角度拍15-20张。注意:标定板要占画面1/4以上,角度要多样。
- 运行标定:用OpenCV的calibrateCamera函数。
下面是我常用的标定代码,你直接拿去用:
import cv2
import numpy as np
import glob
# 棋盘格参数
CHECKERBOARD = (9, 6) # 内角点数量
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 世界坐标系中的点
objp = np.zeros((CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32)
objp[:,:2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1,2)
objpoints = [] # 3D点
imgpoints = [] # 2D点
images = glob.glob('calib_images/*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None)
if ret:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
imgpoints.append(corners2)
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None)
print("内参矩阵:\n", mtx)
print("畸变系数:\n", dist)
跑完这段代码,你就得到了内参和畸变系数。我建议你再用重投影误差检查一下——误差小于0.5像素才算合格。
我的习惯:标定完成后,我会拍一张标定板放在画面边缘的照片,手动检查畸变矫正效果。如果边缘的直线还是弯的,说明标定有问题。
2.4 用Matlab标定——更直观的选择
如果你不想写代码,Matlab的Camera Calibrator是个好选择。我个人觉得它比OpenCV更直观,特别适合初学者。
操作步骤:
- 打开Matlab,输入
cameraCalibrator - 导入标定图片
- 输入棋盘格尺寸(单位:毫米)
- 点击Calibrate,等几秒钟
- 查看结果:重投影误差、畸变矫正效果
Matlab会直接显示每张图片的误差,哪张拍得不好一目了然。我一般会剔除误差大于0.5像素的图片,重新标定。
| 工具 | 优点 | 缺点 |
|---|---|---|
| OpenCV | 免费、可集成到代码、批量处理 | 需要写代码、调试麻烦 |
| Matlab | 可视化好、操作简单、适合验证 | 收费、不能直接集成到嵌入式 |
我的建议是:先用Matlab验证标定板质量和拍摄方法,再用OpenCV集成到实际项目中。
2.5 标定中的常见坑——我踩过的都告诉你
标定看起来简单,但坑不少。我列几个最常见的:
- 标定板不平:打印纸贴在弯曲的纸板上,标定出来全是错的。我建议用亚克力板或玻璃板。
- 图片太少:少于10张,标定结果不稳定。我一般拍20张。
- 角度不够:只拍正面,不拍倾斜角度。这样标定不出畸变参数。
- 对焦没锁:标定过程中改变了焦距,内参全变了。记住:标定和实际使用时焦距必须一致。
我曾经踩过的坑:有一次标定双目相机,左右目分别标定都很好,但联合标定后基线长度差了5mm。查了半天,发现是标定板在左右目图像中位置不一致——标定板没固定好,拍摄时移动了。
2.6 本章知识体系
下面这张图总结了本章的核心内容,你可以把它当作一个快速参考:
这张图把本章的知识点串起来了。你从针孔模型出发,理解内参外参;然后处理畸变;最后用工具标定。每一步都踩实了,后面的SLAM才能跑得稳。
最后说一句:标定这件事,看起来简单,但做扎实了能省你后面很多时间。我见过太多人花几周调SLAM参数,结果发现是标定没做好。嗯,别走弯路。
公众号:蓝海资料掘金营,微信deep3321