ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

相机标定核心:内参矩阵与归一化平面如何连接像素坐标和三维空间

相机标定核心:内参矩阵与归一化平面如何连接像素坐标和三维空间 1. 从像素坐标到归一化平面为什么一定要多此一举搞相机标定、三维重建、SLAM的人迟早都会撞上“归一化平面”这个词。第一次听的时候我其实挺懵的我都已经拿到像素坐标了直接拿去算不就行了为什么非要先转到一个叫“归一化平面”的地方绕一圈再回来直到自己动手标定过几台相机、踩过几次精度翻车的坑之后才明白这一步不是多此一举而是整个相机几何模型里最关键的枢纽。先把这个链条摆出来。一个三维世界中的点最终变成图像上一个像素要经过这样一串变换世界坐标 - 相机坐标 - 归一化平面坐标 - 像素坐标中间那个“归一化平面坐标”英文叫 normalized coordinate它在很多代码里写作(x, y, 1)这个形式。很多教程会告诉你“把相机坐标系下的点除以 Z 就得到归一化坐标”公式很简单但真正理解它为什么要存在、以及它在整个成像模型中扮演什么角色的人其实不多。拿我自己的经历来说第一次真正被归一化平面“教育”到是在做双目立体匹配的时候。当时我拿到一组双目图像直接把像素坐标送进对极约束公式去算本征矩阵结果怎么算怎么不对重投影误差大得离谱。后来排查了半天发现问题是对极几何里所有涉及点与线的关系都必须在一套“去掉内参影响”的坐标下才成立这套坐标就是归一化坐标。像素坐标是“被内参扭曲过”的坐标直接拿去做几何运算等于带着哈哈镜看世界当然对不上。所以这篇博文我想把这条链路里最关键的两个东西——内参矩阵和归一化平面——彻底掰开揉碎讲清楚。适合正在做相机标定、准备入门视觉SLAM或者三维重建的读者哪怕你现在只是刚刚接触 OpenCV 的calibrateCamera这篇文章也能帮你搞明白你标定出来的那些数字到底在干什么。先说结论归一化坐标的本质是“去掉内参之后的纯几何坐标”。它描述的是光线在相机坐标系下的方向而不是某个具体位置。一旦理解了这句话后面所有关于畸变校正、对极约束、PnP 求解的理解都会顺畅很多。2. 内参矩阵拆解标定出来的数字到底在干什么2.1 K 矩阵里那五个数没有一个是多余的说到内参矩阵最常见的写法是这样K [fx 0 cx] [ 0 fy cy] [ 0 0 1]标定完之后你会在标定结果里看到fx、fy、cx、cy这四个数有的模型还会多一个sskew 系数用来描述像素坐标系两轴不正交的偏差。工业相机上这个s通常非常接近 0所以很多实现里直接忽略它。但fx和fy到底是什么很多教程一句话带过“fx 是焦距”。实际上这个说法不严谨。严格来讲fx和使用者拿到的那个物理焦距f比如镜头上写的 8mm、16mm不是一回事它们的换算关系是fx f / dx fy f / dy这里的dx和dy是传感器上单个像素在水平和垂直方向上的物理尺寸。也就是说fx是“焦距长度对应多少个像素宽度”。焦距越长的镜头fx越大像素越小的传感器fx也越大。所以严格来说fx的单位是“像素”它把物理世界的“毫米”转换成了图像世界的“像素”。理解了这个你就知道为什么同样的镜头装在不同分辨率的相机上标定出来的内参完全不同——因为dx变了。至于cx和cy很多人以为它们是图像分辨率的一半比如 640x480 的图像cx就是 320cy就是 240。这个想法可以说是标定理解里最常见的误区。cx和cy的真实含义是“光轴与图像平面的交点坐标”也就是主点principal point。由于镜头安装时候的微小偏差光轴中心几乎不可能和图像传感器的几何中心完全重合所以标定出来的cx和cy通常不是分辨率的一半。默认取图像中心去用就是在给接下来的几何计算埋雷。2.2 内参的本质就是像素坐标系和归一化坐标系之间的桥梁把 K 矩阵放到完整的成像等式里看会更容易明白它的角色。相机坐标系下一个三维点(Xc, Yc, Zc)投影到像素坐标系的过程是[ u ] [fx 0 cx] [Xc / Zc] [ v ] [ 0 fy cy] · [Yc / Zc] [ 1 ] [ 0 0 1] [ 1 ]看到没中间那个向量(Xc/Zc, Yc/Zc, 1)就是归一化坐标。整个投影过程其实是分两段走的先用相机坐标除以 Z 得到归一化平面坐标这一步是纯几何操作和相机内部结构没关系然后再用内参矩阵 K 把这个归一化坐标“拉伸”到像素坐标系下这一步才把镜头和传感器的内部特性引入进来。也就是说内参矩阵做的事情本质上是建立了“归一化坐标”和“像素坐标”之间的仿射变换关系。归一化坐标是几何的、尺度的、与相机无关的像素坐标是成像的、扭曲的、和具体相机强相关的。中间这个桥梁就是 K 矩阵。这么拆分带来的直接好处是任何和镜头无关的几何运算——比如求解本征矩阵、基础矩阵、PnP、三角化——都应该在归一化坐标下进行否则就是在被 K 矩阵扭曲过的空间里做几何推理结果必然错乱。2.3 自己动手算一遍从实际镜头到内参矩阵这部分值得动手算一遍因为推算过程能帮你看清内参矩阵里的数字到底哪来的。假设我手上有一台相机传感器尺寸是 6.4mm x 4.8mm1/2 英寸工业相机的常见规格分辨率是 1280x960镜头焦距 f 是 8mm。单个像素的物理尺寸dx 6.4mm / 1280 0.005mm 5μm dy 4.8mm / 960 0.005mm 5μm内参矩阵fx 8 / 0.005 1600 fy 8 / 0.005 1600cx和cy按理想情况取传感器中心cx 1280 / 2 640 cy 960 / 2 480这个 K 矩阵就是这台相机的理想内参。实际标定出来cx大概率是 638 或者 641.7 这种数字fx和fy也会有零点几个像素的偏差这些偏差正是镜头安装误差和制造公差造成的。有了这个直觉之后再看fx和fy不相等的情况。如果你的相机像素不是正方形dx ! dy那么fx和fy必然不相等。这种像素在工业相机里很少见但手机摄像头、某些特殊的传感器上确实会出现。如果你在标定结果里看到fx和fy差很多先别急着怀疑标定出错了可能是像素本身就不是正方形。提示OpenCV 标定输出的camera_matrix就是 K但它是以像素为单位的。如果你需要把内参换算成物理焦距比如要给三维软件用记得用f_mm fx * dx反向换算。3. 归一化平面的真正用途畸变校正与外参解算的基石3.1 为什么畸变校正非要转到归一化平面来做这是几乎所有标定教程都不会细讲、但实操中特别关键的一个点。我们平时用的畸变模型——无论是径向畸变还是切向畸变——它的数学表达式都是在归一化坐标上定义的。香蕉型畸变的经典模型长这样x_distorted x_n * (1 k1*r^2 k2*r^4 k3*r^6) 2*p1*x_n*y_n p2*(r^2 2*x_n^2) y_distorted y_n * (1 k1*r^2 k2*r^4 k3*r^6) p1*(r^2 2*y_n^2) 2*p2*x_n*y_n注意这里的x_n和y_n指的是归一化坐标不是像素坐标。这个选择的背后是有物理原因的畸变是光学系统的物理特性发生在镜头的光学成像阶段也就是在光线到达传感器之前。所以畸变模型应该在描述光线的几何方向上定义而不是在已经经过传感器采样和像素化之后的坐标系上定义。换句话说畸变发生在“归一化平面”这个层面像素坐标只是归一化坐标被 K 矩阵拉伸后的结果畸变不会在拉伸之后才产生。这在代码实现上怎么体现呢以 OpenCV 的图像去畸变流程为例你把一张带畸变的原始图像重映射remap成一张去畸变的图像。这个流程里先对目标图像上的每个像素坐标(u, v)用内参矩阵的逆变换到归一化坐标然后在归一化坐标上应用畸变模型算出畸变之后的位置再落回到像素坐标去采样原始图的像素值。整个流程绕了一大圈核心就在这一点畸变模型的输入和输出都必须是归一化坐标。如果你直接把畸变模型套到像素坐标上去算会得到完全不正确的结果。我自己最初写去畸变代码时犯过这个错误——当时图省事直接对像素坐标应用畸变公式出来的图像边缘扭曲得更厉害了换回来才正常。3.2 外参标定如何利用归一化坐标做 PnP 求解再来看一个实际应用通过棋盘格标定外参——也就是求解相机坐标系和世界坐标系之间的旋转矩阵 R 和平移向量 t。这里的关键步骤就是 PnPPerspective-n-Point求解。PnP 问题的输入是一组“3D 世界点-2D 像素点”的对应关系。但直接用像素坐标去建立方程方程是非线性的并不方便求解。标准做法是先把像素坐标转成归一化坐标这样整个模型就变得非常干净某个世界坐标系下的点Pw经过外参变换到相机坐标系再投影到归一化平面[ Xc ] [ Pw_x ] [ Yc ] R · [ Pw_y ] t [ Zc ] [ Pw_z ] x_n Xc / Zc y_n Yc / Zc最终和像素坐标的关系是u fx * x_n cx v fy * y_n cy解 PnP 的时候一般会把像素坐标先转成归一化坐标再用 DLT直接线性变换或者迭代优化方式比如 Levenberg-Marquardt 最小化重投影误差去求 R 和 t。OpenCV 的solvePnP传入的像素点内部会先用内参 K 转换一次所以如果你传入的已经是归一化坐标就必须把内参设成单位矩阵——这是很多初学者容易踩的坑。具体来说如果你的输入是像素坐标solvePnP的cameraMatrix就填标定得到的 K如果自己已经手动做了x_n (u-cx)/fx、y_n (v-cy)/fy的转换那么cameraMatrix就填单位矩阵I。两种情况我都在实际代码里见过混用的话结果会非常离谱。3.3 从像素到射线归一化坐标在三维反投影中的作用还有一个非常实用、但很容易被忽略的用途从单个像素点恢复出三维射线方向。我们知道单目相机无法直接得到深度信息。给定一个像素点(u, v)它在三维空间中可能对应的是在这条射线上的任意一点。要做三维重建、射线求交、或者用两个视角做三角化你都得先恢复出这条射线的方向。而这个方向恰恰就是归一化坐标给的。把像素坐标反投影到归一化平面x_n (u - cx) / fx y_n (v - cy) / fy那么这条射线在相机坐标系下的方向向量就是(x_n, y_n, 1)。注意这里只有方向没有长度——因为归一化坐标的 Z 分量恰好是 1它是“距离相机中心 1 个单位处的点”。如果需要得到任意深度 Z 处的三维点只要把这个方向向量乘以 ZXc x_n * Z Yc y_n * Z Zc Z这个“方向乘以深度等于三维点”的关系是整个视觉几何中用到最频繁的公式之一。多视角立体的核心流程本质上就是每个像素点反投影得到射线再在两两相交处找到真实的三维点。没有归一化坐标这个中间层的存在这个流程就无从谈起。4. 实操中高频踩坑内参与归一化平面相关的典型错误4.1 坐标转换顺序错了结果全歪我见过不少刚接触标定的朋友在把像素坐标转归一化坐标的时候顺序搞反。正确顺序是x_n (u - cx) / fx y_n (v - cy) / fy但有人会写成u / fx - cx这就不对了结果差得很远。前者减主点再除以焦距本质上是把“像素坐标系的原点挪到光轴中心再按像素密度换算成物理量纲”后者则是先做了尺度变换再做平移两个操作的几何含义根本不等价。这看起来是个小问题实际操作中却很容易犯。我的习惯是写成一个独立的小函数然后每次用到的时候都先跑一遍标定板角点的验证确认坐标转换正确再做其他操作。一旦你把这个转换写成可复用的函数错误率会大幅下降。4.2 把图像中心当成主点用直接把几何计算做偏在前面已经提到过cx和cy是光轴投影点不是图像中心。但这里我仍然想专门提一下因为我在项目里确实见过因为这种“省事”导致精度严重受损的案例。做视觉测量时主点偏移的影响在图像边缘尤其明显。我曾经做过一个项目用短焦镜头做近距离测量当时为了方便直接设cx width/2结果在图像边缘区域的重投影误差比标定结果大了将近 3 倍。后来改成用标定得到的真实主点误差立刻恢复正常。如果你只是做图像 demo、不求精度那把cx设为分辨率一半也无妨。但只要你做的是测量、导航、三维重建一类对精度有要求的任务就老老实实把标定出来的cx、cy用上哪怕它和图像中心差了好几个像素。4.3 误解深度信息归一化坐标不等于相机坐标还有一个特别常见的概念混淆有人以为归一化坐标就是相机坐标系下的坐标。这两者只差一个因子——深度 Z。相机坐标: (Xc, Yc, Zc) 归一化坐标: (Xc/Zc, Yc/Zc, 1)从归一化坐标恢复相机坐标你还需要深度信息。没有深度的归一化坐标只代表方向。这个区分在做单目 SLAM 的时候尤其关键因为单目情况下这个深度是未知的所以归一化坐标到相机坐标这个环节天然存在尺度歧义。很多新手在这个地方卡很久其实就是没想明白“方向”和“位置”的区别。拿生活类比的话归一化坐标就好像站在你面前 1 米处的那个点它告诉你目标在那个方向上但目标实际可能在 10 米外也可能在 100 米外。要知道真实距离你需要额外的深度信息——可能是立体视觉给出来的也可能是运动恢复结构SFM算出来的。4.4 undistortPoints 和手动计算的区别别踩了迭代求解的坑OpenCV 的undistortPoints函数很多人以为它就是简单地把像素坐标转成理想归一化坐标。实际上它还额外做了一件事它会假设输入的点带有畸变然后去迭代求解“去畸变”之后的归一化坐标。如果你传入的是已经去畸变的像素坐标或者你想要的是带畸变的归一化坐标再调用这个函数就会出问题。我实际使用中的经验如下如果输入是标定板上检测到的角点带畸变的像素坐标用undistortPoints输出的是去畸变后的归一化坐标是正确用法。但如果输入已经是去畸变图像上的像素坐标应该手动做(u - cx) / fx这个转换而不是再过一遍undistortPoints。这两个用法我自己都实际踩过输出的坐标差异在图像边缘还是很明显的。4.5 常见问题速查表症状可能原因解决思路重投影误差大于 0.5 像素角点检测精度不足或畸变模型选择不当提高角点检测亚像素精度尝试更多畸变系数组合外参解算结果剧烈跳动输入的 3D-2D 匹配点中有外点加 RANSAC或者检查坐标转换顺序去畸变后图像边缘扭曲更严重畸变模型作用在了像素坐标而非归一化坐标先反投影到归一化坐标再应用畸变模型双目对极约束怎么都对不上直接用了像素坐标计算本征矩阵统一转成归一化坐标再构建对极约束fx和fy差异巨大像素非正方形或标定板不平整检查传感器规格重新采集多角度标定板图像PnP 结果错得离谱cameraMatrix传错了传了单位矩阵但给的是像素坐标确认坐标类型统一内参传入方式5. 实践建议走向精确成像的几条实用心得在一个实际项目里我体会最深的一件事是标定结果的可靠性影响因素最大的不是算法本身而是数据采集的质量。关于数据采集下面几个经验可以参考第一标定板一定要足够平整。打印的纸贴在不平的硬纸板上标定出来的内参会带着纸面波动的误差。我的办法是用亚克力板或者玻璃板做底板并用透明胶带把标定板完全贴平。标定板不平整造成的误差在后续的测量里很难通过算法弥补。第二采集图像要覆盖视场的各个区域。特别是图像的四个角落一定要让标定板出现在那里。因为畸变在视场边缘最为明显只把标定板放在画面中央采集径向畸变的估算是不可靠的。我的采集习惯是每个角度拍摄 15-20 张确保标定板在画面的中心、左上、右上、左下、右下五个区域至少都出现一次并且倾斜角度也有变化。第三对光照条件有一定控制避免反光。棋盘格角点检测受反光影响非常大。如果你用的是镜面反射明显的标定板建议在柔和光照下拍摄或者在镜面上加一层哑光膜。反正我踩过曝光过度的坑输出图像上棋盘格都快看不出来了检测出来的角点自然也是错的。第四采集完成后检查重投影误差。这个指标在 OpenCV 的calibrateCamera返回结果里可以直接读到。如果 RMS 误差大于 0.3 像素建议检查数据质量重新采集比硬调参数有效得多。我在实际项目里卡在 0.3 以下就能满足绝大多数测量任务高于这个阈值就要追查原因了。第五把去畸变和坐标转换封装成公共工具函数。在团队协作或者自己多个项目复用的时候把“像素坐标转归一化坐标”“归一化坐标转像素坐标”“图像去畸变”“像素点去畸变”这四件事封装成统一的工具能省掉大量重复调试的时间。接口参数就一个是否已经去畸变。这个布尔值造成的差异很多人调试了半天也没发现封装成统一接口之后至少能确保团队内的一致性。写在最后相机成像的标准化核心表面上是几个矩阵和公式本质上是在说一件事像素坐标是“被扭曲过的观测”归一化坐标才是“干净的三维几何”。内参矩阵 K 连接了这两者也让标定、去畸变、PnP、对极几何这些看似散乱的技术点全部统一到一个框架里。我做视觉工作这几年最大的感受是很多算法一开始看不懂不是数学底子不够而是坐标系之间的转换没理清。只要把像素坐标和归一化坐标之间的关系刻进脑子里大部分视觉几何的代码读起来都会顺畅很多。希望这篇博文能帮你少走一些弯路。最后再分享一个小技巧调试任何和相机坐标有关的代码时先在已知纯平移的场景下做一次数值验证——比如把标定板放在相机正前方不同距离处用 PnP 解出来的 t 的 Z 分量应当和实际距离成线性关系。这个验证能快速暴露坐标转换方面的问题比反复看公式高效得多。
返回列表