视觉基础:针孔相机模型、畸变模型、相机标定原理
做视觉SLAM,第一个绕不开的就是相机模型。说白了,相机是怎么把三维世界变成一张二维图片的?这个问题搞不清楚,后面什么特征匹配、三角化、BA优化,全是空中楼阁。
我个人习惯,讲视觉基础一定从针孔模型开始。它是最简单、最直观的模型,也是理解所有复杂相机模型的基础。
针孔相机模型
想象一个密闭的黑盒子,前面戳一个小孔,光线穿过小孔,在后面的感光平面上成像。这就是针孔相机的基本原理。
数学上怎么描述?我们建立四个坐标系:世界坐标系、相机坐标系、图像坐标系、像素坐标系。嗯,这里要注意,初学者最容易搞混的就是图像坐标系和像素坐标系。
- 世界坐标系:描述物体在真实世界中的位置,单位是米。
- 相机坐标系:以相机光心为原点,Z轴指向相机前方,单位是米。
- 图像坐标系:在成像平面上,以光轴与平面的交点为原点,单位是毫米。
- 像素坐标系:在图像上,以左上角为原点,单位是像素。
从世界坐标到像素坐标,经历了三次变换:刚体变换(外参)、透视投影、仿射变换(内参)。
核心公式:
s * p = K * [R | t] * P
其中:
- P 是世界坐标点 (X, Y, Z, 1)
- R, t 是相机外参(旋转和平移)
- K 是相机内参矩阵
- p 是像素坐标 (u, v, 1)
- s 是尺度因子
内参矩阵K长这样:
K = [fx 0 cx]
[ 0 fy cy]
[ 0 0 1]
fx, fy 是焦距在像素单位下的表示,cx, cy 是光心在像素坐标系中的偏移。我在项目中遇到过,有些廉价摄像头的光心偏移特别大,如果不标定直接用,重建出来的三维点云会严重变形。
畸变模型
现实中的镜头不是完美的针孔。为了收集更多光线,镜头是凸透镜,这就带来了畸变。说白了,就是直线变弯了。
畸变主要分两类:
- 径向畸变:光线在镜头边缘弯曲更厉害。表现为「桶形畸变」或「枕形畸变」。
- 切向畸变:镜头和成像平面不平行。表现为「梯形畸变」。
数学上怎么矫正?我们用多项式模型来描述畸变:
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
其中 k1, k2, k3 是径向畸变参数,p1, p2 是切向畸变参数。r 是点到光心的距离。
避坑指南:
我曾经在标定一个广角镜头时,只用了k1, k2两个参数,结果图像边缘的畸变矫正效果很差。后来加了k3,效果才明显改善。对于普通镜头,k1, k2就够了;但广角或鱼眼镜头,一定要用三个径向畸变参数。
相机标定原理
相机标定,就是求解内参和畸变参数的过程。你想想看,我们怎么知道fx, fy, cx, cy是多少?怎么知道镜头畸变有多大?
最经典的方法是张正友标定法。它的核心思想是:拍摄不同角度的棋盘格,利用棋盘格上角点的已知几何关系,来求解相机参数。
具体步骤:
- 打印一张棋盘格,贴在平面上。
- 用相机从不同角度拍摄10-20张照片。
- 检测每张图片中的角点坐标。
- 利用角点的世界坐标和像素坐标,建立方程组。
- 用最小二乘法求解内参和畸变参数。
- 用非线性优化(如Levenberg-Marquardt)进行精化。
注意事项:
- 棋盘格要平整,不能有褶皱。
- 拍摄角度要覆盖各个方向,包括倾斜、旋转。
- 棋盘格在图像中的占比要适中,太小或太大都会影响精度。
- 光照要均匀,避免反光。
我个人习惯,标定完成后会做一件事:用标定结果去矫正一张图片,然后肉眼检查矫正后的直线是不是真的直了。如果还有弯曲,说明标定不够好,需要重新拍。
下面这张图展示了整个视觉基础的知识体系:
标定完成后,你会得到一组参数。我建议你把这些参数保存成YAML文件,方便后续调用。格式大概是这样:
%YAML:1.0
---
camera_matrix:
rows: 3
cols: 3
data: [535.4, 0, 320.1, 0, 536.2, 240.5, 0, 0, 1]
distortion_coefficients:
rows: 1
cols: 5
data: [-0.283, 0.075, 0.001, -0.002, 0.0]
有了这些参数,你就可以对图像做去畸变处理,然后进行后续的视觉SLAM操作了。记住,标定是视觉SLAM的第一步,也是最重要的一步。标定不准,后面全是白费功夫。
实用建议:
如果你用的是ROS,可以直接用camera_calibration包来标定,非常方便。它会实时显示标定进度和重投影误差。我一般要求重投影误差小于0.5像素才算合格。