本节摘要:针孔相机模型用内参矩阵描述三维点到像素的映射,畸变模型修正镜头的物理缺陷。本节拆解投影链的每一步,回答"深度在哪一步丢掉",为多视图重建与深度相机解读做铺垫。
上一节解决了坐标系之间的搬家,本节处理最频繁的一次搬家:三维世界到二维照片。它是第四章多视图几何的入口——不理解投影的不可逆,就无法理解重建为何需要"多视角"这个前提。
针孔模型里,空间点沿直线穿过小孔落在像面上。相似三角形关系给出:像素坐标等于焦距乘以世界坐标再除以深度。问题就出在这个"除以深度"——所有落在同一条过光心直线上的点,无论离相机一米还是十米,投影到照片上是同一个像素。深度信息在成像瞬间被数学性地压缩掉了,这不是镜头不够好,而是投影几何的必然。

完整投影链是三个矩阵接力:外参(刚体变换,上一节的主角)把世界坐标搬进相机坐标系;内参矩阵把相机坐标映射到像素;畸变模型在两者之间修正镜头弯曲。
内参矩阵包含四个自由度:焦距两个分量(fx、fy,像素单位)与光心坐标两个分量(cx、cy,约等于图像中心)。焦距的物理含义是"一个角度的物体在照片上占多少像素"——焦距越大,视野越窄、物体越大。
# 手写投影链:世界点到像素 import numpy as np K = np.array([[800.0, 0, 320.0], [0, 810.0, 240.0], [0, 0, 1.0]]) # 内参:焦距约 800 像素,光心在图像中心 P_world = np.array([0.5, -0.2, 4.0, 1.0]) # 世界系中的点,齐次坐标 T_cw = np.eye(4) # 假设相机恰在世界原点、朝 z 轴 P_cam = T_cw @ P_world uvw = K @ P_cam[:3] # 投影(还未除以深度) u, v = uvw[0]/uvw[2], uvw[1]/uvw[2] print('像素坐标:', round(u, 1), round(v, 1)) # 输出: 像素坐标: 420.0 199.5 # 验算: x/z*fx+cx = 0.5/4*800+320 = 420;y/z*fy+cy = -0.2/4*810+240 = 199.5
把同一个点沿视线推远到深度八米并放大一倍(坐标乘二),投影结果分毫不变——这就是"近小远大无法区分"的代码版证明。
针孔是理想模型,真实镜头为了成本和光圈弯曲玻璃,直线在照片边缘变成曲线。径向畸变使点沿半径方向内外偏移(桶形、枕形),切向畸变来自透镜与传感器的装配偏心。标定的任务就是用棋盘格照片把这些系数连同内参一起解出来。OpenCV 的相机标定流程会输出内参矩阵与五到八个畸变系数,去畸变后再按针孔模型处理,误差可压到零点一个像素以内。
⚠️ 常见坑:拿畸变系数去"修正"已经是去畸变后的图像,或者反过来。管线里必须约定唯一一个环节做畸变处理,SLAM 前端通常在读取图像后立即做一次去畸变,之后全链路按针孔模型运算。
标定是远征前的必修课。未标定的内参会把尺度误差直接注入重建结果:焦距差百分之一,远处点的深度误差就被放大。棋盘格标定时建议采集十五到二十五张不同角度的照片,重投影误差低于零点三像素可视为合格。
| 项目 | 针孔模型 | 带畸变模型 | 鱼眼模型 |
|---|---|---|---|
| 视场角 | 中小 | 中 | 超广角 |
| 参数量 | 4 | 加 5 项畸变 | 等距投影加多项式 |
| 典型场景 | 长焦监控 | 普通工业相机 | 水下机器人、全景 |
| 不标定的后果 | 尺度漂移 | 边缘弯曲 | 边缘投影完全失效 |
重投影误差是验收货币。标定、SfM、视觉 SLAM 的优化目标几乎都是"三维点投回照片与实际观测像素的距离",这个指标贯穿全册,第三章配准、第四章重建、第五章后端都会反复出现它的身影。
💡 关键直觉:内参是相机的"出厂性格",外参是相机的"当下姿势"。重建与定位算法做的一切,可以概括为已知一方求另一方。
背景:深度相机(第二章的主角)输出的是"配了对的彩色图与深度图",把像素还原成三维点云是每天都要写的样板代码。操作:对每个有效像素,用内参的逆运算反投影。
# 深度图反投影为点云(核心三行) depth = np.load('frame.npy') # 形状 480x640,单位毫米 rows, cols = np.nonzero(depth > 0) z = depth[rows, cols] / 1000.0 # 毫米转米 x = (cols - K[0, 2]) * z / K[0, 0] # (u-cx)*z/fx y = (rows - K[1, 2]) * z / K[1, 0] # (v-cy)*z/fy points = np.stack([x, y, z], axis=1) print('点数:', len(points)) # 输出: 点数: 264831(640x480 中有效像素) print('深度范围:', round(z.min(),2), '~', round(z.max(),2), '米') # 输出: 深度范围: 0.4 ~ 7.9 米
结果解读:反投影是投影的严格逆运算,条件是深度已知——这正是深度相机的价值:它用主动测距把投影丢掉的深度重新买回来。变式:若深度图与彩色图分辨率不同,需先做配准对齐(多数设备驱动提供对齐接口),否则点云颜色错位。
标定板要拍多少张? 十五张是下限,二十五张、覆盖画面四角与中心、带明显俯仰变化,畸变系数才收敛得稳。只拍正面平移的十几张,边缘畸变基本没被观测到,标定报告好看但一到广角边缘就露馅。
换镜头后内参还能用吗? 不能。变焦或换镜后焦距与畸变全变,必须重标;定焦镜头固定好后标一次即可,但长途运输颠簸后建议复查一次重投影误差,两分钟就能确认。
深度图与彩色图分辨率不一致怎么办? 先查设备驱动是否提供对齐接口,有就开对齐;没有则按外参把深度点投影到彩色图上采样颜色,切勿直接按行列号硬配——错位一个像素,红蓝物件的颜色就会互换。
反投影出的点云有洞? 深度图里的零值与无效值就是洞的来源,先按 2.1 节的数据体检统计空洞分布再决定处理方式。
深度既然能"买回来",用什么仪器买、价格几何?下一章清点采集装备。