一个做计算成像的朋友跟我说过一句话,我一直记着:所有相机的数学起点,都是那个不起眼的小孔。不管你现在用的是手机、单反、工业相机还是车载摄像头,你看到的每一张照片、每一个像素,追根溯源都能回到针孔相机模型。这一讲我围绕K [R | t]这条核心主线,把针孔相机、内参外参、景深和卷帘快门这四块内容串起来讲清楚。搞懂了这些,你再去读任何跟三维重建、视觉SLAM、计算摄影有关的论文,都会顺很多。
1. 针孔相机:一切相机模型的起点
1.1 为什么针孔是“理想的相机”
针孔相机的结构极其简单:一个不透光的盒子,一侧开一个小孔,光线只能穿过这个小孔,在对侧的底片上形成倒立的像。正因为只有通过小孔的光线能进入,每个三维物点都对应像平面上的一个确定位置,不存在镜头的复杂折射,也不存在畸变。换句话说,针孔成像在几何上是完美的中心投影,这就是它成为一切相机模型起点的原因。
我在实际项目里,遇到那种对三维坐标系转换特别头疼的新人,都会先让他忘掉镜头和CMOS,只留一个盒子、一个小孔。为什么?因为把镜头拿掉之后,成像关系就只剩一个最简单的相似三角形。设相机坐标系下三维点坐标为(Xc, Yc, Zc),针孔在原点,成像平面在Z = f处,像点坐标(x, y)就是:
x = f * Xc / Zc y = f * Yc / Zc这个公式我建议你亲手推一遍,推完你对焦距、光轴、主点这些概念的理解会完全不一样。很多教材直接给你一个矩阵,但你不知道每一行、每一列是怎么来的,后面做相机标定、做双目重建的时候就很容易出错。
1.2 从针孔到真实镜头有哪些差异
真实的相机当然不会只靠一个小孔成像,因为小孔进光量太少,曝光时间必须拉长,拍出来的东西又暗又糊。所以实际镜头用一组透镜代替小孔,把光线汇聚起来,进光量大幅提升。但透镜也有问题:它有一定厚度、有一定折射特性,边缘部分还会产生畸变。这里的核心近似是,透镜组可以看作一个“等效针孔”,所有光线经过透镜后等效于穿过一个叫做光心的点,光轴与像平面交点附近的位置,就是等效针孔位置。
真实镜头和理想针孔的差异,集中在几个地方:主点不一定在图像正中心;焦距可能不是单一的,x和y方向的像素尺寸可能不一致;镜头边缘有径向畸变和切向畸变。这些差异如果不管,直接用理想针孔公式去还原三维信息,误差会非常大,尤其是在广角镜头和鱼眼镜头上。所以工程上先要用“针孔模型”去描述几何关系,再用“畸变模型”去补偿镜头偏差,这两件事是配套的。
2. 内参矩阵K:三维世界如何“落入”像素平面
2.1 K矩阵里每一格的含义
内参矩阵K是一个 3×3 上三角矩阵,通常写成:
K = [fx s cx 0 fy cy 0 0 1 ]每个元素都不是凭空来的。fx和fy是焦距在像素坐标系下的表达,单位不是毫米,而是像素。cx、cy是主点的像素坐标,也就是光轴与成像平面交点的像素位置。s是倾斜因子,用来描述像素坐标系两条轴不完全垂直的情况,现代相机里它通常非常接近0,很多算法直接忽略它。
我经常被问:为什么焦距不直接给一个毫米值,非要整出fx = f / dx这种东西?因为工程最终要的是像素坐标,而像素尺寸本身是物理尺寸除以感光元件分辨率换算出来的。f是物理焦距,dx是每个像素在x方向的物理宽度,fx = f / dx就把物理焦距换算成了以“像素宽度”为单位的数值。这一步换算如果你漏了,后面所有涉及像素大小和实际距离的换算都会出错。
2.2 像素坐标系与成像平面坐标系的换算逻辑
内参的关键作用,是把相机坐标系里的三维点先投影到成像平面物理坐标,再转成像素坐标。物理成像坐标(x, y)到像素坐标(u, v)的换算关系是:
u = fx * x + cx v = fy * y + cy这就是为什么 K 矩阵要把fx、fy放在左上角,把cx、cy放在第三列。你从公式里能直接看出,像素坐标不是从图像中心开始算的,而是从图像左上角开始算的,所以主点偏移(cx, cy)才必须被加回去。如果搞反了这个关系,三维点在图像上的投影位置就会整体平移,标定结果对不上,重建出来全是错的。
内参矩阵还有一个细节我特别想提醒:K矩阵描述的是“相机坐标系到像素坐标系”的映射,它只跟相机本身的物理属性有关,跟相机放在哪、朝向哪没有任何关系。所以同一个相机,无论你把它装在哪台机器人上、摆在哪个场地,它的K是不变的。这也是为什么标定一次内参,可以长期复用。
2.3 畸变与内参标定的关系
镜头畸变通常分为两类:径向畸变和切向畸变。径向畸变是光线在透镜边缘发生更大偏折导致的,表现为画面边缘的直线变弯;切向畸变是由透镜安装时与成像面不完全平行导致的,表现为点在切向方向发生偏移。工程上常用下式描述畸变模型:
x_distorted = x * (1 + k1*r^2 + k2*r^4 + k3*r^6) + ...这里r是像点到主点的半径距离,k1、k2、k3是径向畸变系数。实际标定棋盘格的时候,用的就是这种模型,标定完输出内参矩阵、畸变系数、旋转和平移外参。我见过很多项目主点标出来表面上看“还算准”,但畸变系数不收敛,最终三维重建偏差很大,后来发现是标定板不平整、角点检测出错了。
实操心得:标定棋盘格时,不要只拍正对着相机的图,那样径向畸变的约束不够。要让棋盘格出现在画面的边缘和角落,并且适当倾斜,才能把畸变模型的参数“钉死”。我个人的习惯是拍20到30张,覆盖九宫格各区域,并且每次改变倾斜姿态和距离,这样标定结果才稳定。
3. 外参 [R | t]:相机在世界中的“位置与朝向”
3.1 世界坐标系与相机坐标系
内参解决的是“相机坐标系下的三维点如何变成像素”,但相机本身也有自己的位置和朝向。在实际项目中,我们真正关心的是三维点在世界坐标系里的坐标,比如激光雷达点云在车辆坐标系里的位置、机械臂末端在基地坐标系里的位置。要把世界坐标系的点转换到相机坐标系,就需要外参。
外参用[R | t]表示,R是 3×3 的旋转矩阵,t是 3×1 的平移向量。它们合起来是一个 3×4 的增广矩阵,描述了从世界坐标系到相机坐标系的刚体变换。注意,这个变换是刚体变换,它不会改变物体大小,只改变坐标系的旋转和平移。
3.2 R 和 t 的物理意义
R的每一列,其实是世界坐标系的三个轴在相机坐标系下的方向向量。这句话反过来说也一样:R的每一行,是相机坐标系的三个轴在世界坐标系下的方向向量。初学的时候容易搞混,我建议你用右手定则自己画一个小的坐标系旋转图,把X轴转到Y轴方向,观察旋转矩阵怎么变化,一下就通了。
t是世界坐标系原点在相机坐标系下的坐标。这里有个反直觉的点要特别注意:外参描述的是“把世界坐标系变成相机坐标系”,所以t是“世界原点在相机坐标系里的位置”,而不是“相机在世界坐标系里的位置”。如果你从标定结果里读出一个t = [1, 2, 3],那意味着世界原点位于相机坐标系的 (1, 2, 3) 处,而相机在世界坐标系里的位置是-R^T * t。这个方向反了,再多计算都是白搭。
3.3 坐标变换的完整顺序
从世界坐标系的点Pw到相机坐标系点Pc的完整变换如下:
Pc = R * Pw + t很多教材写成齐次形式:
[ Pc ] [ R t ] [ Pw ] [ 1 ] = [ 0 1 ] [ 1 ]这个顺序不能乱。你先做旋转,再做平移,等价于把世界坐标系先转到与相机坐标系同方向,再把原点挪过去。如果你先平移后旋转,结果就完全不一样。
项目中我常用的一个验证方法是:把一个已知坐标的点(比如棋盘格第一个角点的世界坐标)代入,手算一遍投影,再用代码跑一遍,看像素坐标是否一致。这一步花不了几分钟,但能一次性排查掉坐标系方向搞反、矩阵转置这类低级错误。
4. 完整投影 K [R | t]:从三维点到像素的旅程
4.1 齐次坐标与投影公式
内参和外参合在一起的完整投影公式是:
z_c * [u, v, 1]^T = K * [R | t] * [x_w, y_w, z_w, 1]^T这里的z_c是相机坐标系下的深度值,它不是一个常量,而是每个三维点自己的Zc。公式左边的[u, v, 1]^T是像素坐标的齐次形式,右边的[x_w, y_w, z_w, 1]^T是世界坐标的齐次形式。你把这个矩阵展开,会得到三个等式,最后一个等式一定是 1 = 1,而前两个等式里有z_c,所以实际是用第三个等式的值去归一化前两个。
我遇过不止一个人在这里懵掉:为什么明明是三维点投影到二维,公式里却要除以一个z_c?因为针孔成像的本质就是相似三角形,物距越远、像点越靠近光轴,这个z_c就是那个“物距”在相机坐标系下的值。除以它是整个投影矩阵里最核心的非线性环节,也是它保证了远处的物体看起来更小。
4.2 计算流程拆解:从世界坐标到像素坐标
我给你一个可以直接照着算的流程:
- 已知世界坐标点
Pw = (Xw, Yw, Zw); - 用外参旋转平移:
Pc = R * Pw + t,得到相机坐标(Xc, Yc, Zc); - 判断
Zc > 0,如果Zc <= 0,说明点在相机后方,压根不该成像; - 归一化坐标:
x = Xc / Zc,y = Yc / Zc; - 应用内参:
u = fx * x + cx,v = fy * y + cy; - 如果做畸变校正,在第4步和第5步之间插入畸变修正模型。
这个流程看起来很机械,但每一步都对应了明确的物理意义。我在写代码时会把每一步都拆出来,不直接调一个大封装函数,这样出了问题能定位到是哪一步。
4.3 反投影:从像素到射线的意义
正向投影是把三维点变成像素,反过来,单个像素能不能直接还原成唯一的三维点?答案是否定的。一个像素只对应一条射线:从相机光心出发、穿过该像素中心的三维射线。在这条射线上的任意深度点,投影到图像上都是同一个像素。
所以反投影得到的永远是一条射线,不是点。这也是为什么单目相机无法直接测距,必须借助双目、多视图或者运动恢复结构。你只有知道深度Zc,才能把射线上的点“钉”在某个确定位置。这个概念在三维重建里特别重要,凡是做深度估计、三角测量的人,都必须把“像素对应射线”这个思维刻在脑子里。
5. 景深:为什么照片有的地方清楚有的地方模糊
5.1 弥散圆与景深公式
前面讲的针孔模型,默认光线是理想汇聚的,真实镜头可不是这样。真实镜头聚焦时,只有位于对焦平面上的物体能清晰成像,其他位置的物点成像后会在像平面上形成一个圆斑,这个圆斑叫弥散圆。如果弥散圆直径小到人眼无法分辨,看起来仍然是清晰的,这个可接受的清晰范围就是景深。
景深计算涉及一组公式,工程上常用的简化版是这样的:
前景深 = ( F * δ * L^2 ) / ( f^2 + F * δ * L ) 后景深 = ( F * δ * L^2 ) / ( f^2 - F * δ * L )其中F是光圈值,δ是允许的弥散圆直径,L是对焦距离,f是镜头焦距。注意这里的F是 f-number,数值越小光圈实际开口越大。
我把这组公式自己推导过一遍,核心思路就是:对焦距离L处的物体正好聚焦在像平面,物距稍微变小时,像点落在像平面后方,形成弥散圆;再用相似三角形算出这个圆斑的直径,让它小于允许的δ,就能反推出前景深和后景深。
5.2 光圈、焦距、物距如何影响景深
很多摄影教程只说结论,我想把背后的机制讲透。景深受三个因素影响:
- 光圈:光圈越大(f值越小),进入镜头的锥形光束开口越大,同样的离焦量产生的弥散圆直径就越大,所以景深越浅。这就是为什么大光圈人像背景容易虚化。
- 焦距:焦距越长,放大倍率越大,微小的物距偏差在像平面上的偏移也被放大,景深越浅。长焦镜头背景虚化能力比广角强,本质原因在这里。
- 物距:对焦距离越近,景深越浅。宏观上看,公式里的分母中
L^2的增速快过分子里的其他项,微观上理解,离得越近,物体前后的微小距离变化在角度上的变化越明显。
把这三个因素叠加,你就知道想要“前景后景都清晰”的风景照,应该用广角小光圈,对焦在远处;想要“突出主体”的人像,应该用长焦大光圈,离主体近一点。
5.3 计算成像里的景深控制经验
我在一个工业检测项目里,要同时拍一个平面上的细小字符和它前方几毫米处的透明薄膜,结果薄膜的纹理和字符叠在一起,怎么都对不上焦。后来测量了景深范围,发现按工作距离和光圈算出来的景深只有不到2毫米,薄膜恰好超出范围。解决办法是缩小光圈到f/8,同时把相机安装角度尽量垂直于被测平面,景深一下就够用了。这里我想说的是,景深不是摄影专属概念,它在机器视觉里同样决定成败。
另一个经验是,生产线的光源如果用点光源,物体会产生强烈阴影,这虽然不是景深问题,但会让自动对焦系统误判。所以我在做视觉检测方案时,一般会建议使用低角度环形光或面光源,减少对比度跳变,让对焦和后续特征提取都更稳。
6. 卷帘快门:为什么高速运动经常“拍弯”
6.1 全局快门与卷帘快门的本质区别
CMOS 传感器的曝光方式主要分两类:全局快门和卷帘快门。全局快门让所有像素在同一时刻开始曝光、同一时刻结束,画面捕捉的是一个瞬间;卷帘快门则是让每一行像素依次曝光,通常是自上而下逐行进行的。为什么会有卷帘快门?因为 CMOS 传感器面积大,逐行读出可以把电路设计得更简单、成本更低,手机和大多数民用相机都采用这种方式。
卷帘快门的曝光不是瞬间完成的,而是像窗帘一样扫过传感器的。这意味着画面最顶部一行和最底部一行实际记录的并不是同一个时间点的场景,而是有一段时间差。这个时间差在静态场景下毫无影响,但在快速运动场景下,就会产生明显的几何失真。
6.2 果冻效应的成因
最典型的果冻效应,就是拍高速转动的螺旋桨时,叶片看起来不是平的,而是弯的。原因是螺旋桨顶部先曝光,底部后曝光,等曝光轮到底部时,叶片已经转到另一个位置了,于是同一个螺旋桨在画面里被扭成了弧形。
我还踩过一个更隐蔽的坑:在移动平台上装相机,边走边拍侧面建筑,结果画面里竖着的电线杆歪了,像是被风吹弯。这不是镜头畸变,也不是相机没校准,完全是因为车辆移动过程中,卷帘快门逐行曝光,每一行对应的车体位置不同,导致竖直物体在图像里产生了倾斜。很多人一开始会误判成相机安装松动,我建议你先看画面里是不是所有竖直边缘都朝同一个方向倾斜,如果是,大概率就是卷帘快门效应。
6.3 工程中的规避手段
应对卷帘快门,有几个实用思路。首先是降低曝光时间,曝光时间越短,各行之间的时间差对整个场景位移的影响就越小,但快门扫过整幅画面的总时间并没有变短多少。其次是调整拍摄方向,让物体的主要运动方向与逐行扫描方向平行,可以有效减少变形,比如拍竖直运动物体时旋转相机90度,让扫描方向变成水平。
在视觉SLAM和三维重建项目中,如果特征点会被卷帘快门效应污染,我一般建议直接换全局快门相机,尤其是在快速旋转或移动的场景。全局快门相机相对贵一些,但省掉后续的几何校正。如果只能用卷帘快门相机,就得在做运动补偿时加入IMU数据,或者在算法里把每行的时间戳都建出来。
7. 几个常见问题与排查心得
7.1 相机标定总是不过,大概率是这些原因
我帮别人排查过很多标定失败问题,频率最高的原因有三个。第一,标定板尺寸输错了,内角点距离填成了外角点距离,误差直接翻倍。第二,标定板反光和镜面反射导致角点检测失败,我建议用哑光材质打印,必要时用背光板增强对比度。第三,图像数量太少或覆盖不均匀,只在画面中心拍,畸变参数根本约束不住,多拍带边缘和倾斜的图,结果会稳定很多。
另一个常被忽略的细节是,标定过程需要让棋盘格保持在一个固定平面内,不要用手拿得弯弯曲曲,板子不平整,角点位置就会有毫米级的偏差。我在项目里习惯用平板贴纸或玻璃夹板,标定精度肉眼可见地提升。
7.2 景深没控制好,画面“糊”的不是焦点怎么办
有人问我:为什么我光圈已经开到 f/8 了,画面边缘还是糊?这里有两种可能。一种是透镜在边缘的像场本身就差,广角镜头最明显,收小光圈能缓解但不能完全消除。另一种是镜头有场曲,中心和边缘的最佳焦面不在同一个平面上,这时你就算把光圈收到 f/16,边缘也清晰不了。这类镜头问题在工业相机里非常常见,选购时一定要看 MTF(调制传递函数)曲线,而不能只看分辨率数值。
机器视觉里还有一个经典误区:为了景深拼命缩小光圈,结果光照不够,强行加大光源亮度,又引入了严重反光。我的做法是先选合适的工作距离和焦距,让景深计算出来刚好覆盖被测物体的高度差,再用中等光圈配合均匀照明,而不是一味追求小光圈。
7.3 卷帘快门造成误差,能不能后期救回来
后期能不能救?可以,但要看场景。如果物体运动轨迹可以用匀速模型近似,算法上可以对图像逐行进行反向位移补偿,把每行像素按时间戳挪回同一个基准时刻。很多手持扫描仪就是这么做的。但如果物体做的是复杂非线性运动,比如旋转搅拌、液体飞溅,单帧图像就救不回来,只能靠高速全局快门相机。
有个更实用的经验是,在拍摄前先判断一下有没有必要关心卷帘快门。静止场景、慢速场景,拍出来的卷帘快门失真子像素级别都不到,完全不用纠结;一旦拍摄目标在画面中占据较大面积且移动速度很快,就要警惕了。我在做运动捕捉项目时,会先用手机拍一段快速挥手的视频,观察手是不是有倾斜变形,以此快速判断相机是否会被卷帘快门影响。
最后分享一个让我印象很深的教训。有一次我在做多相机同步采集,两台相机型号不同,一台是全局快门,一台是卷帘快门。外参标定和投影建模的时候,直接用同一个相机模型去处理,结果重建出来的三维点错位很严重。排查到最后才发现,卷帘快门相机的每一行都有一个不同的采集时间,而全局快门相机只有一个时间,二者的时间基准根本对不上。后来我在搭建同步采集系统时,一定会把快门类型写进相机参数清单里,并给卷帘快门每一行加上独立时间戳。这个细节在所有文档里都不会写,但实际项目里真的会坑人。