资讯动态

MATLAB相机内参坐标系详解:投影、反投影与OpenCV对接避坑指南

发布时间:2026/10/13 21:40:20 来源:尧图企业网站定制
你打开 MATLAB 标定完相机导出一个cameraParams或者新的cameraIntrinsics看着里面的IntrinsicMatrix、FocalLength、PrincipalPoint这些字段感觉好像理解了但真拿它去写投影代码、像素转射线、或者跟 OpenCV 的代码对接时经常对不上。这事我刚开始做的时候也是反复折腾。相机内参坐标系说到底就是一条从三维点变成像素点的链路而 MATLAB 在表示这条链路时用了和教科书不完全一样的约定。这篇就围绕相机内参坐标系和它在 MATLAB 里的实际表示把投影、反投影、常见坑位和一些能直接落地的验证方法讲清楚。1. 内参坐标系是怎么把三维点变成像素的1.1 从世界坐标到像素坐标中间隔了几层相机成像可以被拆成一条坐标变换链。一个三维点先在世界坐标系里通过旋转矩阵和平移向量转到相机坐标系然后用内参矩阵把相机坐标系下的点投影到图像像素平面。很多人把内参和外参混在一起说其实两者分工非常明确世界坐标系到相机坐标系由外参旋转矩阵 R、平移向量 t决定。相机坐标系到像素坐标系由内参矩阵 K 和畸变系数决定。相机坐标系的原点在镜头光心z 轴指向相机前方x 轴朝右y 轴朝下。这个约定在 MATLAB 和 OpenCV 里基本一致。但很多人在做机器人或者三维视觉时会习惯用右手坐标系z 轴朝前、y 轴朝上所以一旦看到 y 轴朝下的定义就会觉得别扭。实际图像里像素的 v 坐标本来就是从上往下增大的让相机坐标系的 y 轴朝下反而跟图像的 row 方向顺过来了。内参矩阵通常写成K [fx 0 cx; 0 fy cy; 0 0 1]它做的事情可以拆成两步。先把相机坐标系里的点除以 z 得到归一化坐标x_n Xc / Zc y_n Yc / Zc再变成像素坐标u fx * x_n cx v fy * y_n cy这里的fx、fy不是物理焦距而是以像素为单位的等效焦距。如果相机的物理焦距是f单个像素的物理尺寸在 x 方向是dpx、y 方向是dpy那么fx f / dpx fy f / dpy弄清楚这个链路后面所有问题都清楚了。世界坐标系和相机坐标系之间是刚体变换变换完之后内参只负责“从相机坐标系映射到像素”。1.2 K 矩阵每一项背后都有物理含义很多人背 K 矩阵的把式是左上角两个是焦距右上角是主点。但如果追问一句fx和fy为什么不相等、主点为什么不在图像正中心可能就答不上了。fx和fy的差异主要来自像素不是正方形。现在的工业相机在很多分辨率下像素宽高比接近 1但不代表完全相等。镜头畸变、传感器装配、图像缩放处理都可能让 fx 和 fy 产生细微差别。如果做高精度测量不能直接假设 fx 等于 fy。cx、cy是主点的像素坐标。主点理论上是光轴穿过图像平面的位置因为镜头和传感器的装配不可能绝对完美主点通常在图像中心附近但不等于图像中心。标定出来的cx、cy落在几十像素以内的偏移是非常正常的。还有一个容易被忽略的项是 skew 系数也就是标准 K 矩阵里的s位置在 fx 和 fy 之间K [fx s cx; 0 fy cy; 0 0 1]现代相机传感器基本可以认为 skew 为 0但 MATLAB 的老版本cameraParameters里还是保留了估计 skew 的选项。如果标定时选了估计 skew导出的IntrinsicMatrix里第二行第一列可能会有一个非零的s这是转置后的位置别搞错。1.3 为什么 MATLAB 里的 IntrinsicMatrix 看起来像被“转置”了这是新手最容易踩的坑。MATLAB 的几何变换函数大量使用行向量约定而教科书里的投影公式基本是列向量约定。先看教科书写法[ u ] [Xc] [ v ] K * [Yc] [ 1 ] [Zc]这里三维点是列向量K 左乘。MATLAB 的cameraIntrinsics对象里也有一个IntrinsicMatrix属性但它存的是教科书 K 的转置K_matlab K_std也就是K_matlab [fx 0 0; s fy 0; cx cy 1]如果你直接拿IntrinsicMatrix去乘列向量结果当然是错的。正确做法要么先转置回来要么按照行向量约定去写。我见过不少人把 MATLAB 的IntrinsicMatrix原封不动搬到 Python 代码里然后怎么算都不对最后发现就差一个转置。这个问题看着小但会浪费一整天。2. 用 MATLAB 对象承载内参字段、构造与读取2.1 cameraIntrinsics 的字段到底在说什么MATLAB 的 Computer Vision Toolbox 里内参最常用的表达是cameraIntrinsics对象。它的核心字段如下字段含义注意点FocalLength1x2 向量[fx fy]单位是像素不是毫米PrincipalPoint1x2 向量[cx cy]单位是像素不是图像中心ImageSize1x2 向量[nrows ncols]先是高后是宽RadialDistortion[k1 k2 k3]可能只有两个系数TangentialDistortion[p1 p2]顺序和 OpenCV 不同IntrinsicMatrix3x3 矩阵标准 K 的转置注意ImageSize是[rows, cols]也就是先高后宽。很多人在做投影验证或者图像缩放时拿width和height的顺序去取结果全反了。这是我帮别人调代码时最常看到的问题。FocalLength和PrincipalPoint都有明确的像素单位而RadialDistortion跟像素单位无关只和归一化坐标有关。这决定了你在做去畸变时必须先算归一化坐标再套畸变公式。2.2 手动构造一个内参对象验证自己的理解理解cameraIntrinsics最好的方式就是手动构造一个。假设你已知相机的fx800、fy800、cx320、cy240图像分辨率是 480 行、640 列畸变系数初步设为零可以这样构造focalLength [800 800]; principalPoint [320 240]; imageSize [480 640]; intrinsics cameraIntrinsics(focalLength, principalPoint, imageSize);构造完以后看看里面的IntrinsicMatrixK_matlab intrinsics.IntrinsicMatrix输出会是K_matlab 800 0 0 0 800 0 320 240 1如果你想得到教科书里那种标准 K就需要转置K_std intrinsics.IntrinsicMatrix这时输出才是K_std 800 0 320 0 800 240 0 0 1这一来一回的转置关系直接决定了你后面写投影代码的方式。我建议在做标定和写代码之前先这样手动构造一次把所有字段的排列方向都验证清楚比任何文档都直观。2.3 从 Camera Calibrator 导出后应该读哪些字段如果用的是 MATLAB 的Camera CalibratorApp标定完可以导出两类对象。比较老的是cameraParameters里面除了内参还有标定用的所有外参信息包括每个棋盘格图像的旋转矩阵、平移向量、世界点坐标、重投影误差等。从cameraParameters里读内参时重点看这几个字段IntrinsicMatrix同样是标准 K 的转置。FocalLength1x2 向量。PrincipalPoint1x2 向量。RadialDistortion、TangentialDistortion畸变系数。MeanReprojectionError平均重投影误差单位是像素。我一直建议把标定结果保存成.mat文件而不是只截图记录。因为后面做三维重建、测距、去畸变时随时需要这些对象的方法和属性。直接存对象比存散乱的数字可靠得多。save(camera_params.mat, cameraParams);下次加载load(camera_params.mat);3. 像素坐标和三维点之间的往返实操3.1 手算投影把 K 用回教科书形式无论是验证算法还是调试 bug手算一次投影都是值得的。假设相机坐标系下有一个点Xc [100; 50; 2000]; % 单位毫米用标准 K 投影到像素坐标K_std intrinsics.IntrinsicMatrix; % 先转置回来 uv_h K_std * Xc; u uv_h(1) / uv_h(3); v uv_h(2) / uv_h(3);如果你不想转置也可以用 MATLAB 的IntrinsicMatrix配合行向量uv_h_row Xc * intrinsics.IntrinsicMatrix; u uv_h_row(1) / uv_h_row(3); v uv_h_row(2) / uv_h_row(3);这两种写法结果一样但第二种符合 MATLAB 的存储习惯。关键是别混着用。列向量配IntrinsicMatrix或者行向量配K_std都会得到莫名其妙的结果。实际标定得到的 K 通常还带畸变所以上面这个投影是理想针孔模型的结果。如果要考虑畸变得先把归一化坐标算出来加上径向和切向畸变再映射到像素。MATLAB 去畸变比较简单的方式是直接做图像级别处理undistortedImage undistortImage(I, intrinsics);但如果你是处理标定点而不是整张图最好自己掌握点坐标的去畸变流程因为undistortImage只能给你一张新图不能直接给你某个点的新坐标。3.2 反投影出一条空间射线内参坐标系的另一个重要用途是反投影。给定一个像素坐标(u, v)你不能直接得到三维点但能得到一条空间射线。这条射线的方向在相机坐标系里可以用 K 的逆矩阵计算pixel [u; v; 1]; direction K_std \ pixel; direction direction / norm(direction);这里的direction就是相机坐标系下从光心出发的射线方向。拿到射线之后再结合外参就能把射线变换到世界坐标系。很多应用场景比如单目测距、目标定位本质上都是“像素坐标 已知平面约束”的问题。只知道内参你只能得到一条线要确定点落在线上哪个位置必须再给一个约束比如地面平面、目标高度、已知深度范围等等。不是拿内参就能直接测距。3.3 一个利用平面约束的伪测距小例子假设相机水平安装离地高度是 1.2 米相机坐标系 y 轴朝下。地面上任意一点在相机坐标系里的 y 分量就是 1.2 米。现在图像里有一个像素(u, v)我们想求它在相机坐标系下的坐标。先算射线方向u 400; v 300; pixel [u; v; 1]; direction K_std \ pixel; lambda 1.2 / direction(2); point_cam lambda * direction;这时point_cam(1)是横向偏移point_cam(3)是它离相机平面的水平距离。这个例子看着简单但非常实用。它清楚地说明了内参、射线、平面约束三者是怎么配合的。注意这里我用的是理想无畸变模型。如果有畸变必须先对(u, v)做去畸变处理再代入反投影。很多单目测距代码算出来误差大不是模型问题而是漏了去畸变。4. 坐标系和参数约定里的高危雷区4.1 y 轴方向一个很容易自我矛盾的约定前面说过MATLAB 的相机坐标系是 x 轴朝右、y 轴朝下、z 轴朝前。第一次听到这个约定的人总会犹豫因为很多几何课本里 y 轴朝上。如果应用的后续环节需要转换到世界坐标系这个方向不一致很容易导致旋转矩阵写错。举个例子相机水平摆放时地面上一个点相对相机的高度差在相机坐标系里体现为 y 分量大于零而如果按传统的 y 轴向上约定这个点的 y 分量应该是负的。你在用pointsToWorld函数或者自己做平面交点计算时如果搞反了正负号测出来的距离会完全错误。我自己的习惯是在代码文件头部写清楚当前坐标系约定% Camera coordinate: x right, y down, z forward % Image coordinate: u right, v down这样过几个月回来再看代码也不会因为忘记约定而出错。4.2 主点不是图像中心但它和图像中心有关PrincipalPoint是标定出来的主点像素坐标不等于ImageSize的一半。比如一张 640x480 的图像理论图像中心在(320, 240)但标定出来的cx可能是 310cy可能是 245。这很正常。主点不仅取决于相机本身还会随着图像裁剪和缩放变化。如果把原图中心裁剪掉一块新的主点坐标要减去裁剪偏移如果把图像缩放一半fx、fy、cx、cy全部要乘 0.5。这条规则在标定板重拍、图像预处理、模型部署时特别重要。scale 0.5; newFocalLength scale * oldFocalLength; newPrincipalPoint scale * oldPrincipalPoint;不更新这些值内参就跟新图像不匹配后续所有投影和反投影结果都会偏。4.3 像素焦距和物理焦距不要混用很多时候资料里会给出镜头焦距 8 毫米、像素尺寸 3 微米这类数据然后让你算内参。换算公式fx 8 / 0.003注意单位统一8 毫米除以 0.003 毫米得到的是像素。但这种方式只能作为初值。真正准确的做法是通过标定得到fx、fy因为镜头实际焦距和标称值有偏差物理像素尺寸也可能不是整数微米。标定出来的值才是最接近真实系统的。物理焦距和像素焦距转换还有一种常见坑从 EXIF 信息读到的焦距单位是毫米直接拿来当像素用。除非你知道准确的传感器像素尺寸和图像缩放因子否则不要做这个假设。使用 MATLABcameraIntrinsics时所有内参都是像素单位这一点是最不需要怀疑的。4.4 畸变系数的顺序两套模型差一个元素MATLAB 的径向畸变系数是[k1, k2, k3]切向畸变是[p1, p2]。OpenCV 的distCoeffs向量顺序是[k1, k2, p1, p2, k3]。也就是说在 MATLAB 里同样是切向畸变你可能写p1、p2而到 OpenCV 里要放到第三、第四位。如果要把 MATLAB 的内参转给 OpenCV 使用一定要重新排列畸变系数k1 intrinsics.RadialDistortion(1); k2 intrinsics.RadialDistortion(2); k3 0; if numel(intrinsics.RadialDistortion) 3 k3 intrinsics.RadialDistortion(3); end p1 intrinsics.TangentialDistortion(1); p2 intrinsics.TangentialDistortion(2); distCoeffsOpenCV [k1, k2, p1, p2, k3];此外两边的坐标方向约定也有细微差别。虽然大体都是 y 轴朝下但在某些相机模型、图像坐标系定义上仍可能不一致转出去之后最好用一组已知点做交叉验证别只靠看代码。5. 把 MATLAB 内参用到其他语言前必须做的换算5.1 转到 OpenCV 的完整例子如果你在 MATLAB 里标定完想在 Python/OpenCV 里做去畸变或者投影需要做的其实只有两件事恢复标准 K 矩阵重新排列畸变系数。假设已经加载了cameraParams或者intrinsics在 MATLAB 侧先导出数据K_std intrinsics.IntrinsicMatrix; dist [k1, k2, p1, p2, k3];然后在 Python 侧读取import numpy as np K np.array([ [800.0, 0.0, 320.0], [0.0, 800.0, 240.0], [0.0, 0.0, 1.0] ]) dist np.array([k1, k2, p1, p2, k3])这里最容易出错的还是 K 矩阵。很多人从 MATLAB 的IntrinsicMatrix里直接复制数字出来忘记转置结果在 OpenCV 里图像坐标全对不上。我个人建议跨语言使用前写一个很小的投影验证脚本在 MATLAB 里选几个相机坐标系的点投影成像素坐标再到 OpenCV 里用同一套 K 做同样的事情对比结果。这一步只要做一次后面就放心了。5.2 相机坐标系约定不统一时的处理不同框架对相机坐标系的定义并不完全一样。除了常见视觉框架的 y 轴朝下机器人领域经常出现 z 轴朝前、y 轴朝上的坐标系。这时如果直接把视觉里的旋转矩阵拿过去用三维坐标会整体错乱。处理方式是把外参也一起做一次坐标系变换而不仅仅是转置 K 矩阵。比如机器人常用坐标系的 y 轴向上视觉相机坐标系的 y 轴向下两者之间相差一个绕 x 轴的 180 度旋转。具体怎么处理要看你自己的应用定义但最稳妥的方法永远是在标定完之后做一个真实场景的三维验证比如已知距离的点反算回来是否一致。5.3 一个用重投影误差验内参的小办法标定完成后最简单的验证方法是用平均重投影误差。如果MeanReprojectionError大于 0.5 像素我会觉得标定质量不够稳。优秀的标定通常在 0.1 到 0.3 像素之间。但这个数字只是参考。更接近实际的做法是用标定出的内参把棋盘格角点重新投影回去看看所有角点误差的分布。不要只看平均值要看有没有个别角落误差特别大。如果有通常是标定板拍糊了、图像序列覆盖不够或者角点提取偏差。在 MATLAB 的cameraCalibratorApp 里可以逐张查看每张图的误差。如果某张图的误差明显高于其他图直接把这张图剔除重新标定效果往往马上改善。6. 我处理内参坐标系时的一些工作习惯6.1 保存内参时连坐标约定一起存我最早保存标定结果时只存一个cameraParams.mat后来几个月后再打开已经忘了里面矩阵到底是行向量约定还是列向量约定。现在我习惯在.mat文件里同时放一个说明变量cameraConvention.intrinsicMatrixIsTranspose true; cameraConvention.cameraCoordYDown true; cameraConvention.imageCoordOriginTopLeft true;别觉得多余过段时间再看代码这个说明能帮你省下大量查文档的时间。甚至可以进一步把标定时的图片分辨率、是否裁剪、是否缩放也一起存进去。6.2 先用合成图像跑通投影代码接手一个新相机、新内参、新代码框架时我最先做的不是直接拿真实图像测而是造一个虚拟相机给定一个 K造出几个三维点投影成像素坐标然后再用这些像素坐标去反推射线。如果最终能回到原来的三维点方向说明整条链路没问题。这个习惯帮我排掉了很多低级错误。比如某个函数的参数顺序是[x, y]还是[row, col]图像坐标是(u, v)还是(v, u)在合成数据上一测就露馅。真实场景里错误会被噪声、标定误差、环境因素掩盖反而不容易定位。6.3 标定图片的采集策略比算法参数更影响内参最后说一个标定本身的经验。内参坐标系再怎么理解得清楚标定数据质量差一切都白搭。我建议采集 15 到 30 张以上图片覆盖图像中心、四角、上下左右各个位置同时要有明显的角度倾斜和远近变化。不要让棋盘格只出现在画面中央那样会把内参约束得很差尤其是主点和畸变系数容易不稳定。拍标定板时每张图的棋盘格不要太靠近图像边缘但也要让边缘位置有标定板覆盖。边缘对畸变系数的估计特别重要如果棋盘格永远只在图像中央附近移动标出来的边缘畸变就会完全失真。另外一定避免运动的运动模糊。标定板边角模糊时角点提取精度会下降标出来的主点和焦距也会跟着飘。按照这个习惯采集图片配合 MATLAB 标定工具内参坐标系这部分的基础就能打得比较扎实了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑