相机就是一个带小孔的盒子
想象一个密封的鞋盒,一面扎了一个极小的针孔,对面贴着一张纸。外面世界反射的光线从这个孔射进来。因为孔非常小,物体上每个点都只有一条细细的光线能够穿过,并且正好落在纸上的某一个位置。对每个点都这样处理,整个场景的完整图像就会在后壁上成形——虽然是上下颠倒的,但确确实实是真实的。这就是相机的全部原理:它把一个光线四面八方传播的三维世界,压缩成一幅平坦的二维图像。
针孔迫使每一条光线都穿过同一个点,所以图像本质上是一束在同一处交汇的直线光线。现代相机用镜头代替针孔(为了收集更多光线并保持清晰),用数字传感器代替纸张——也就是由数百万个计光单元组成的网格,这些单元叫做像素。但几何关系是一样的:世界以光线的形式抵达,传感器记录每条光线落在网格上的位置。
一个三维点如何变成一个像素
鞋盒的整洁数学版本叫做针孔相机模型。它把针孔放在原点,并在它前方一个焦距处放一个想象出来的成像平面(放在前方,这样图像就是正立的而非颠倒的)。世界中的一个点用一条直线与针孔相连;这条光线穿过成像平面的地方,就是这个点出现的位置。剩下的交给相似三角形。
两个数字主宰着这次压缩。焦距是成像平面与针孔之间的距离;焦距长会把场景摊开(放大、视野窄),焦距短则把场景挤紧(视野宽)。主点是中心光线——也就是正前方那条——打在传感器上的位置;它在像素坐标里标出图像中心。知道这两者,模型就能把光线的方向换算成精确的像素位置(即它的行与列)。
u = fx * (X / Z) + cx # pixel column v = fy * (Y / Z) + cy # pixel row # (X, Y, Z): point in front of the camera, Z = depth # fx, fy: focal length in pixels cx, cy: principal point
两组参数:镜头本身,以及它在世界中的位置
要投影一个点,模型需要两类知识,而把它们分开来看会很有好处。第一类是固化在相机本身里的一切:焦距、主点,以及像素网格的形状。这些是相机内参——相机自身的内部几何。机器人四处行驶时它们不会改变,因为它们描述的是这个盒子,而不是盒子在哪里。
第二类是相机所处的位置以及它朝向哪个方向。世界中的一个点通常是在某个固定参考系里测量的——房间、机器人的底座——但投影的数学却想要从相机自己的视角来测量它。相机外参就是连接这两个坐标系的旋转和平移。它们恰恰就是相机的位姿:位置加上朝向。把相机装在一条移动的机械臂上,外参时时刻刻都在变;而内参纹丝不动。
标定:在信任相机之前先把它量准
没有人会递给你一张可以精确到像素的内参规格表——同一型号的两台相机也会有差异,而真实镜头弯折光线的方式是整洁的针孔模型所忽略的。广角镜头边缘附近的直线会向外鼓出或向内收缩;这就是镜头畸变。相机标定就是测量真实内参和畸变的过程,好让软件把两者都纠正回来。
- 打印一张棋盘格,方格尺寸精确已知,把它贴在一个平整、坚硬的物体上。
- 从许多角度和距离拍十几张以上的照片,让它出现在画面的不同区域。
- 软件在每张照片里找出每个角点——而它早已知道这些角点在真实棋盘上的位置。
- 它寻找一组内参和畸变,使投影出的角点与检测到的角点最为吻合,留下最小的残余误差。
回报既直接又实在。假设一条机械臂发现了一个零件,从图像算出它的距离,然后伸手去抓。如果焦距偏差了百分之几,或者畸变没有纠正,几何就会撒谎:机械臂会误判零件有多远、有多大,于是要么够不着,要么撞过去。同样的缺陷会污染立体相机对算出的深度,以及之后每一个依赖它的步骤——所以标定是任何机器人被允许去看、去抓之前那个不动声色的第一步。它正是机器人上其他每个传感器都需要的传感器标定在视觉上的同类。