本文最后更新于:2026年8月5日 下午
相机标定估出内参后,这些估计准到什么程度?本文用 Fisher 信息算出每个内参的精度下界(CRLB)——不管用什么算法,误差都不可能小于它,而且拍照前就能算。理论推导见《Fisher 信息与精度下界》。
问题描述
标定给出一组内参(焦距、主点、畸变系数),但估得准不准、误差能有多大,通常只能凭重投影残差粗略感受。我们想要一个更根本的量:在既定的拍摄条件下,内参估计的精度极限是多少。
这个极限由 Fisher 信息给出,叫 Cramér–Rao 下界(CRLB)。它只依赖观测方程和噪声,与具体算法无关,因此可以在拍照之前就算出来——用来规划拍多少张、什么角度;拍完也能反过来检验标定是否真的逼近了理论极限。
只关心内参时,第 k 个内参的精度下界是
$$ \mathrm{CRLB}_k=\sqrt{[\mathcal I_\beta^{-1}]_{kk}} $$其中 $\mathcal I_\beta$ 是扣掉外参之后、只关于内参的信息矩阵。整套推导见理论篇,下面只说怎么把它一步步算出来。
解决思路
高斯噪声下,信息矩阵只看雅可比——每个像素对各参数的偏导有多灵敏:
$$ \mathcal I=\mathbf J^\top\boldsymbol\Sigma^{-1}\mathbf J $$这一行把"算精度下界"拆成三件事:
- 雅可比 $\mathbf J$:参数如何影响像素。数值差分就能算,不必手推偏导。
- 噪声 $\boldsymbol\Sigma$:角点检测的随机误差。各向同性、各通道不相关时退化为对角阵,对角元是单轴噪声方差 σ_a²。
- 边缘化外参:每张图的位姿也是从同一批数据估出来的,会分走内参的信息,要用 Schur 补扣掉。
三件事自然落成六个步骤:先造出带真值的仿真数据,算雅可比,累加成信息矩阵,边缘化外参,求逆开方得 CRLB,最后用 Monte Carlo 验证估计器是否真达到这个下界。前一步的产出就是后一步的输入。
仿真与真实
这条流水线算的是金标准 CRLB——它在真值处求值,只有仿真算得出(仿真里真值已知)。真实标定没有真值,做法是先标定得到估计 $\hat{\boldsymbol\theta}$,再在估计处算信息(观测信息),不确定度近似为
$$ \mathrm{Cov}(\hat{\boldsymbol\theta})\approx\mathcal I_{\mathrm{obs}}(\hat{\boldsymbol\theta})^{-1} $$OpenCV 的 stdDeviations 就是这么来的。这之所以可行,靠的是最大似然的一致性:估计会收敛到真值,故观测信息与期望信息在大样本下重合。
那么仿真何必费劲算金标准?恰恰是为了验证:拿真值处的 CRLB 去对照真实标定算法的散布,第 6 步里 std/CRLB ≈ 1 就说明这套方法可信,真实标定里用观测信息法才站得住脚。
无真值时的真实标定方法
上面说真实标定走观测信息——具体怎么算?几乎和仿真一模一样,只把"真值"换成"估计":θ_true 换成 θ̂,噪声 σ 换成重投影残差估出的 σ̂,其余代码原样。
| 步 | 仿真(有真值) | 真实标定(无真值) |
|---|---|---|
| 数据 | 正向仿真生成 | 真实角点检测 y |
| 求值点 | 真值 θ | 估计 θ̂(calibrateCamera 输出) |
| 噪声 | σ_a 已知 |
σ̂_a = 重投影 RMS / √2 |
| 雅可比 | J(θ) 在真值处 |
J(θ̂)(同一套差分代码) |
| 信息 | (1/σ_a²)JᵀJ 在真值处 |
(1/σ̂_a²)J(θ̂)ᵀJ(θ̂) |
| 结果 | CRLB = √diag(I_β⁻¹) |
Cov(θ̂) ≈ I_obs⁻¹ |
| 验证 | 第 6 步 Monte Carlo | 做不了(无真值对照) |
不确定度的核心式:
$$ \mathrm{Cov}(\hat{\boldsymbol\theta})\approx\mathcal I_{\mathrm{obs}}(\hat{\boldsymbol\theta})^{-1}=\bigl(\hat\sigma_a^{-2}\,\mathbf J(\hat{\boldsymbol\theta})^\top\mathbf J(\hat{\boldsymbol\theta})\bigr)^{-1} $$噪声怎么估:calibrateCamera 返回的重投影 RMS 就是径向噪声 std σ̂_det,单轴 σ̂_a = RMS/√2(和仿真里 σ_a = σ_det/√2 同一个换算)。
唯一丢掉的是第 6 步验证。仿真有真值,才能拿金标准 CRLB 去对照估计器散布;真实标定没有真值,这一步天然做不了。但这正是仿真的意义——它替你验证过"在估计处算观测信息"和"在真值处算金标准"几乎重合(std/CRLB ≈ 1),所以真实标定里用观测信息法才站得住。
关心外参时
边缘化外参只是因为我们关心内参。若关心位姿(外参)精度,反过来:
- 完整协方差:不边缘化,
Cov(θ̂) ≈ I⁻¹给出所有参数的联合协方差,第 i 张图的位姿不确定度就是I⁻¹里对应那 6 个位姿参数的块。 - 内参已知、只估位姿(PnP):参数只剩 6 个位姿,
I是 6×6,Cov = I⁻¹直接给位姿精度——PnP 不确定度、手眼标定误差传播的基础。 - 内参也估、带不确定度:把内参当 nuisance 边缘化掉,得到扣掉内参不确定性后的纯位姿精度,比假装内参已知更诚实。
其实你已经在用
cv2.calibrateCamera 返回的 stdDeviations,内部就是这套:在 θ̂ 处建 J、JᵀJ、按残差方差缩放、求逆取对角。所以真实标定里早就在用观测信息法了,只是 OpenCV 把它打包成那个返回值。这套流水线的额外价值是:把边缘化(Schur 补)和完整协方差块显式算出来——stdDeviations 只给对角,拿不到"扣掉外参后内参的 CRLB"或"某张图位姿的 6×6 协方差"。
什么时候失准
- 数据太少:θ̂ 离真值远,渐近近似变差 → 多拍、位姿多样。
- 系统误差:角点检测偏置、镜头模型不匹配(真实畸变 ≠ 假设的 5 系数)→ CRLB 只管随机误差,偏置要另算;有偏时 CRLB 不再是有效下界。
- 噪声非高斯:CRLB 形式仍对,但渐近有效要打折扣。
配置
具体怎么干,先定一组贴近真实的参数:
- 图像 2560×1440,焦距 fx = fy = 2200
- 棋盘 9×6 内角点,方格 25mm
- 50 张整板入画的图,位置、俯仰、偏航、滚转、远近都不同(多样性是避免参数耦合退化的关键)
- 角点噪声 σ_det = 0.1px(径向),折合单轴 σ_a = σ_det/√2
文中代码与项目 pipeline/ 下脚本逐字一致,所有数字真跑过。
数据流
1 | |
每个 .npz 存的是上一步的结果,下一步读它,文件名即内容。
| 步 | 脚本 | 做什么 | 输入 → 输出 |
|---|---|---|---|
| 1 | step1_gen_data.py |
生成仿真数据(真值、位姿、f、y) | — → calib_sim.npz |
| 2 | step2_jacobian.py |
雅可比 J = ∂f/∂θ | calib_sim.npz → J.npz |
| 3 | step3_fisher.py |
信息矩阵 I = (1/σ_a²)·JᵀJ | J.npz → I.npz |
| 4 | step4_marginal.py |
Schur 补边缘化外参 → I_β | I.npz → Ibeta.npz |
| 5 | step5_crlb.py |
求逆开方 → CRLB | Ibeta.npz → crlb.npz |
| 6 | step6_validate.py |
Monte Carlo 验证 std/CRLB ≈ 1 | calib_sim.npz+crlb.npz → mc_stats.npz |
理论符号 ↔ 标定里的真实量
| 理论符号 | 标定里的真实量 | 本例取值 |
|---|---|---|
| θ | 内参(9 个)+ 每图外参 | (2200, 2200, 1280, 720, −0.1, 0, 0, 0, 0) + 50 组位姿 |
| y | 全部角点像素 (u, v) | 50 图 × 54 角 × 2 = 5400 个数 |
| f(θ) | 3D 角点投影出的 (u, v) | projectPoints 链 |
| J | 各角点对参数的偏导 | 5400 × 309 |
| Σ | 角点方差 | σ_a²·I = 0.005·I |
| σ_a / σ_det | 单轴 / 径向噪声 std | 0.0707 / 0.1 px |
| I / I_β | 全参 / 内参信息矩阵 | 309² / 9² |
| CRLB_k | 第 k 内参精度下界 | fx → 0.154 px |
实操流程
第 1 步|生成测试数据
做什么:正向仿真——已知真值内参和位姿,把 3D 角点投影成"应有像素" f(θ_true),再加噪声得观测 y。全程没有估计。正向链是板坐标经外参变到相机系、除以深度归一化、再经畸变和内参得到像素。噪声 σ_a = σ_det/√2,因为径向误差平方等于两个轴向误差平方之和。
输入 → 输出:— → data/calib_sim.npz(真值内参、板、外参、img_clean=f、img_noisy=y、σ)。
位姿反解(怎么从"板占多大、中心在哪"反推位姿,只看针孔主项)。由板占视场比例 fill 反解距离:针孔下板宽成像像素等于焦距乘板宽除以距离,要板宽占 fill·W 像素,故距离 Z = fx·board_w / (fill·W),fill 大则板大则离得近。中心放置反解平移 t:把板几何中心投到指定像素,先反针孔算出中心应有的相机坐标,再由 C_c = R·C_w + t 解出 t = 目标相机坐标 − R·板中心,板向四周铺开、容易整块入画。整板入画过滤:只留所有角点都在画面内的位姿——真实标定只看得见画面内角点、只关心画面内畸变,部分出画的板不可用(曾用"3 倍画幅外才挡"导致四成角点出画、虚高精度)。参数独立多样性:位置、俯仰、偏航、滚转、远近 用混合基数各自独立遍历,不锁死联动——多样性是打破参数耦合退化的关键。
代码(逐行注释,与 pipeline/step1_gen_data.py 一致):
1 | |
运行结果解读。整板入画 50/50;100% 在画面内 表示真实约束满足,50 张图全部整块在画面内。[追踪] 角点 #53 跟一个角点走完正向链,证明 img_clean 就这么一步步算出来:板坐标 (200, 125, 0) → 相机系 (−3.7, −98.3, 514.0) → 归一化 (−0.0073, −0.1912) → 像素 (1264.1, 300.8)。残差 std ≈ 0.0708/0.0701 表示观测减应有等于噪声本身,其 std 应回到单轴 σ_a = 0.0707,径向 std 回到 σ_det = 0.1,这是自检:噪声加对了。
输出(下一步的输入)calib_sim.npz:
| 键 | 形状 | 含义 | 谁读它 |
|---|---|---|---|
theta_true |
(9,) | 真值内参 [fx, fy, cx, cy, k1, k2, p1, p2, k3] | 第 2 步(雅可比求值点)、第 6 步(对照真值) |
board |
(54,3) | 棋盘 3D 角点(Z=0,mm) | 第 2 步(投影)、第 6 步(objectPoints) |
rvecs,tvecs |
(50,3)×2 | 每图外参(旋转、平移) | 第 2 步(雅可比的外参列) |
img_clean |
(50,54,2) | 无噪声投影 f(θ_true) | 第 6 步(加噪基准) |
img_noisy |
(50,54,2) | 加噪观测 y | 第 6 步(喂给 calibrateCamera) |
sigma_a |
标量 | 单轴噪声 std | 第 3 步(信息除 σ_a²)、第 6 步(加噪) |
K,dist,image_size,pattern |
— | 内参矩阵、畸变、画面、棋盘规格 | (备用) |
CRLB 用真值算、不需要观测 y(img_noisy);y 只在第 6 步验证时用。
第 2 步|雅可比
做什么:算每个角点 (u, v) 对全部参数(9 内参 + 每图 6 外参 = 9 + 6×50 = 309)的偏导,用数值中心差分(扰动 ±h、重投影、差分),堆成 (5400, 309);再用解析偏导 ∂u/∂k3 = fx·x·r⁶ 交叉验证。参数布局 θ = [fx, fy, cx, cy, k1, k2, p1, p2, k3, 每图 (r0, r1, r2, t0, t1, t2)]。
输入 → 输出:calib_sim.npz → J.npz(雅可比 + 参数名布局)。
1 | |
运行结果解读。J 形状 (5400, 309),5400 行 = 50 图 × 54 角 × 2(u/v),309 列 = 9 内参 + 50×6 外参。角点 #53 ∂u/∂k3 数值 −0.0008 = 解析 −0.0008 表示数值雅可比与解析偏导完全一致,雅可比算对了(自检)。离主点最近角点 ∂u/∂k3 ≈ −2.8e-3,半径小则畸变灵敏度低,符合 fx·x·r⁶ 在小半径处趋零的预期。
输出(下一步的输入)J.npz:
| 键 | 形状 | 含义 | 谁读它 |
|---|---|---|---|
J |
(5400,309) | 雅可比 J = ∂f/∂θ | 第 3 步(算 JᵀJ) |
param_names |
(309,) | 参数布局名(前 9 内参、后外参) | 第 4 步(按名取对角、分块) |
n_intr |
9 | 内参数(分块边界) | 第 4 步(A/B/C 分块) |
n_img,n_pts |
50,54 | 图数、角点数 | (备用) |
深入:数值雅可比的数学原理(中心差分)
这一步用数值微分付算雅可比,不手推偏导。原理拆成五点。
雅可比是什么。f 把参数 θ(309)映射成像素(5400)。雅可比的第 (i, j) 元素是第 i 个观测对第 j 个参数的全偏导,整体是 5400×309 的矩阵。第 j 列就是"只动参数 j,所有像素怎么变"。
中心差分公式。导数定义取不了 h → 0,用对称扰动近似:
$$ \frac{\partial\mathbf f}{\partial\theta_k}\approx\frac{\mathbf f(\boldsymbol\theta+h\,\mathbf e_k)-\mathbf f(\boldsymbol\theta-h\,\mathbf e_k)}{2h} $$代码对每个参数 k(雅可比的一列)扰动 +h 算一次 f、扰动 −h 算一次 f、差除 2h,得到该参数对所有观测的偏导(5400 个数 = 整列)。逐列构建:309 列 × 2 次 f_all = 618 次投影。
为什么中心差分不用前向,看泰勒展开。前向差分 [f(θ+h) − f(θ)]/h 的展开:
$$ f(\theta+h)=f(\theta)+h f'(\theta)+\frac{h^2}{2}f''(\theta)+\frac{h^3}{6}f'''(\theta)+\cdots $$ $$ \frac{f(\theta+h)-f(\theta)}{h}=f'(\theta)+\frac{h}{2}f''(\theta)+\cdots $$首项误差正比于 h,是一阶精度 O(h)。中心差分 [f(θ+h) − f(θ−h)]/(2h),把两边都展开:
$$ f(\theta+h)=f(\theta)+h f'+\frac{h^2}{2}f''+\frac{h^3}{6}f'''+\frac{h^4}{24}f^{(4)}+\cdots $$ $$ f(\theta-h)=f(\theta)-h f'+\frac{h^2}{2}f''-\frac{h^3}{6}f'''+\frac{h^4}{24}f^{(4)}-\cdots $$相减时,偶数阶项(f, f’‘, f⁽⁴⁾, …)同号被抵消,奇数阶项(f’, f’‘’, …)异号翻倍:
$$ f(\theta+h)-f(\theta-h)=2h f'(\theta)+\frac{h^3}{3}f'''(\theta)+\cdots $$ $$ \frac{f(\theta+h)-f(\theta-h)}{2h}=f'(\theta)+\frac{h^2}{6}f'''(\theta)+\cdots $$首项误差正比于 h²,是二阶精度。对称性让一阶误差抵消了。h = 10⁻⁶ 时前向约 10⁻⁶、中心约 10⁻¹²,差六个数量级。
步长 h = 10⁻⁶·max(1, |θ_k|) 由误差极小化定。两种误差在抢 h:截断误差(泰勒舍掉的项)正比于 h²,要 h 小;舍入误差(浮点精度约 2.2×10⁻¹⁶,两个接近的数相减丢有效位)正比于 ε/h,要 h 大。总误差 E(h) ≈ h² + ε/h,对 h 求导令零:
$$ \frac{dE}{dh}=2h-\frac{\varepsilon}{h^2}=0\;\Longrightarrow\;h^3=\frac{\varepsilon}{2}\;\Longrightarrow\;h\sim\varepsilon^{1/3}\approx6\times10^{-6} $$取 10⁻⁶ 接近最优。max(1, |θ|) 自适应:大参数(fx = 2200)用 h = 2.2×10⁻³(相对扰动 10⁻⁶),小参数(k3 = 0)用 10⁻⁶。
数值与解析各有取舍。解析(手推 ∂u/∂fx 等)精确但只覆盖内参、要逐个推;数值万能(含外参、任意模型)、不记公式但慢。本步用数值、用解析(角点 #53 的 ∂u/∂k3)交叉验证。
第 3 步|信息矩阵
做什么:把雅可比按 I = JᵀΣ⁻¹J 累加。各向同性独立噪声时 Σ = σ_a²·I,故 I = (1/σ_a²)·JᵀJ——这就是可加性:观测越多,偏导平方和越大,信息越多。
输入 → 输出:J.npz + calib_sim.npz(取 σ_a)→ I.npz(309×309)。
1 | |
运行结果解读。I 形状 (309, 309) 是方阵,行列都是 309 个参数。I[k3,k3] = 1.54e7 是 k3 的信息:所有角点偏导平方和除以 σ_a²。I[fx,fx] = 25362 是 fx 的信息,但这是未边缘化外参时的表观值,第 4 步会缩水。
JᵀJ 怎么把每个参数的"所有观测偏导平方和"累起来,看一行。JᵀJ 的第 (i, j) 元素是对所有观测 m 求和 J[m,i]·J[m,j]。对角元(i = j)是参数 i 各偏导的平方和,即该参数的自信息;非对角元(i ≠ j)是参数 i、j 偏导的交叉积,刻画两个参数的耦合。整个矩阵除以 σ_a² 折入噪声刻度。
输出(下一步的输入)I.npz:
| 键 | 形状 | 含义 | 谁读它 |
|---|---|---|---|
I |
(309,309) | 信息矩阵 I = (1/σ_a²)·JᵀJ | 第 4 步(分块边缘化) |
param_names |
(309,) | 参数布局 | 第 4 步(分块边界 n_intr) |
n_intr |
9 | 内参数(前 9 行列是内参) | 第 4 步(A/B/C 分块) |
第 4 步|边缘化外参
做什么:外参(nuisance)也从同一批数据估、带不确定度,会"吃掉"内参表观信息。按内参、外参分块,Schur 补扣掉外参,得 9×9 内参信息:
$$ \mathcal I=\begin{pmatrix}\mathbf A&\mathbf B\\ \mathbf B^\top&\mathbf C\end{pmatrix},\qquad \mathcal I_\beta=\mathbf A-\mathbf B\mathbf C^{-1}\mathbf B^\top $$其中 C⁻¹Bᵀ 用 np.linalg.solve 解方程,比直接求逆稳。
输入 → 输出:I.npz → Ibeta.npz(9×9)。
1 | |
运行结果解读。I_β 形状 (9, 9) 只剩内参的信息矩阵,外参 nuisance 已扣掉。I_β[fx,fx] 25362 → 532(缩 47.7 倍) 表示边缘化掉 50 组外参后,fx 的表观信息被外参不确定性吃掉绝大部分;不边缘化、假装外参已知,CRLB 会假性乐观约 √48 ≈ 7 倍。
I_β 是个对称阵、且有负数,正常吗。正常。对称来自 JᵀΣ⁻¹J 的构造(J 的列两两内积,可交换);非对角元为负表示该对内参负相关(如 fx 与 cx:沿对角线方向的偏导一正一负,交叉积为负);矩阵整体仍正定,对任意非零向量 v 都有 vᵀI_βv > 0,这是 CRLB 能求逆的前提。负的耦合元不意味着信息为负,只表示参数间互相牵制。
输出(下一步的输入)Ibeta.npz:
| 键 | 形状 | 含义 | 谁读它 |
|---|---|---|---|
Ibeta |
(9,9) | 内参信息矩阵(边缘化外参后)I_β | 第 5 步(求逆开方得 CRLB) |
intr_names |
(9,) | 内参名 [fx, fy, cx, cy, k1, k2, p1, p2, k3] | 第 5 步(按名打印/对齐) |
深入:np.linalg.solve(C, B.T) 的作用与不用 inv 的原因
代码里 np.linalg.solve(C, B.T) 解的是线性方程组 C·X = Bᵀ,求 X。
C(300×300)是外参信息块,Bᵀ(300×9)是耦合块转置。结果 X = C⁻¹Bᵀ(300×9),然后 B·X = BC⁻¹Bᵀ(9×9),就是从 A 扣掉的渗透量。
两种写法数学等价,但 solve 更好:
solve(C, B.T) |
inv(C) @ B.T |
|
|---|---|---|
| 内部 | LU 分解 + 前代/回代,不显式构造 C⁻¹ | 先求完整 300×300 逆矩阵,再乘 |
| 稳定性 | 更稳(带部分选主元,避免逆放大舍入) | C 病态时显式逆误差大 |
| 速度 | 快(只解方程) | 慢(多算完整逆) |
类比解 3x = 6。inv 法先算 1/3 = 0.333… 再乘 6(多一步中间量,精度损失);solve 法直接 6/3 = 2(不经过 0.333…,更准)。能解方程就别显式求逆,是数值线性代数的标准实践。
第 5 步|CRLB
做什么:对 I_β 求逆、取对角、开方,得到每个内参的精度下界:
$$ \mathrm{CRLB}_k=\sqrt{[\mathcal I_\beta^{-1}]_{kk}} $$输入 → 输出:Ibeta.npz → crlb.npz。
1 | |
运行结果:
1 | |
为什么是对角线之逆、不是逆对角线,这是最容易搞错的地方。直觉上"信息越大精度越高",让人以为 CRLB_k = 1/√I_β[k,k]。用 2×2 的例子看清:
$$ \mathcal I_\beta=\begin{pmatrix}a&b\\b&c\end{pmatrix},\qquad \mathcal I_\beta^{-1}=\frac{1}{ac-b^2}\begin{pmatrix}c&-b\\-b&a\end{pmatrix} $$第 (1,1) 元素是 c/(ac − b²),而不是 1/a。只有 b = 0(无耦合、对角阵)时才等于 1/a。耦合 b ≠ 0 时通过行列式 ac − b² 改变了每个参数的方差下界。标定实例:1/√I_β[fx,fx] = 0.043px(错,假性偏小),√[I_β⁻¹][fx,fx] = 0.154px(对,完整逆),差 3.5 倍。
深入:条件数与数值稳定(求逆出现 NaN 的原因)
求 CRLB 要对 I_β 求逆。条件数衡量矩阵有多病态:
$$ \mathrm{cond}(\mathcal I_\beta)=\frac{\lambda_{\max}}{\lambda_{\min}}\quad(\text{最大/最小特征值;np.linalg.cond 默认即此}) $$对 Fisher 信息,λ_max 是最易辨识方向的信息(如畸变 k3),λ_min 是最难辨识方向的信息(如 fx、cy 耦合方向)。
为什么条件数大会让求逆出 NaN。求逆时 λ_min 方向的方差是 1/λ_min(很大),而浮点精度 ε ≈ 2.2×10⁻¹⁶,相对 λ_max 的舍入"地板"是 ε·λ_max。一旦 λ_min < ε·λ_max,λ_min 落在地板之下,算出来可能接近零甚至为负,求逆爆炸,再开方就是 NaN。
| 配置 | λ_max | λ_min | cond | ε·λ_max | 结果 |
|---|---|---|---|---|---|
| 旧(角点出画面、参数锁死联动) | ~10¹⁴ | ~10⁻⁴ | ~10¹⁸ | ~2×10⁻² | NaN |
| 本例(整板入画、独立多样性) | 8.1×10⁹ | 15.3 | 5.3×10⁸ | ~1.8×10⁻⁶ | 正常 |
本例三件事让条件数可控:整板入画压低 λ_max(不再有出画面的大半径角点把畸变撑到 10¹⁴);参数独立多样性打破 fx、cy 耦合退化、抬高 λ_min;多图累加各方向信息。
运行结果解读。每行是一个内参的 CRLB(1σ 精度下界,像素/无量纲):焦距 fx 0.154、fy 0.158,主点 cx 0.255、cy 0.200,畸变系数更小(k1 3.2e-4、k3 3.5e-3),畸变估得比焦距更准。CRLB_fx = 0.154 表示任何算法估 fx 的 1σ 误差都不可能小于 0.154px,这是精度天花板。1/√I_β[fx,fx] = 0.0434(错) 是警示:CRLB 来自完整逆、不是对角元倒数,差 3.5 倍,因内参强耦合。
输出(下一步的输入)crlb.npz:
| 键 | 形状 | 含义 | 谁读它 |
|---|---|---|---|
crlb |
(9,) | 每个内参精度下界 √[I_β⁻¹]_kk(金标准,真值处算) | 第 6 步(对照实测 std) |
cov_lb |
(9,9) | 内参协方差下界 I_β⁻¹ | (备用:完整协方差) |
intr_names |
(9,) | 内参名 | 第 6 步(按名对齐) |
第 6 步|Monte Carlo 验证
做什么:检验真实标定算法(cv2.calibrateCamera,高斯噪声下等价 MLE)的散布是否真达到第 5 步的金标准 CRLB。固定几何,换噪声种子重复 N 次——每次加新噪声、跑标定、收下内参;算每个内参跨多次的 std,除以 CRLB,应接近 1。这正是"仿真有真值"的价值:用真值处的 CRLB 验证估计器(也即验证真实标定里要用的观测信息法)靠谱。
输入 → 输出:calib_sim.npz(取板、f、σ_a)+ crlb.npz → mc_stats.npz。
1 | |
运行结果(完整输出):
1 | |
逐列含义:
| 列 | 含义 | 作用 |
|---|---|---|
| 真值 | 仿真里设的真值(上帝知道、估计器不知道) | 对照基准 |
| 估计均值 | 60 次标定结果取平均 | 应接近真值 |
| bias | 估计均值 − 真值 | 应接近 0,正负表示偏高偏低 |
| bias/CRLB | bias 除以 CRLB | 应远小于 1(偏差远小于随机散布,判无偏) |
| 实测 std | 60 次估计的散布(标准差) | 应接近 CRLB |
| CRLB | 第 5 步算出的金标准下界 | 理论最优 std |
| std/CRLB | 实测散布 ÷ 理论下界 | 应接近 1(达到下界,判有效) |
fx、fy(焦距)bias 极小(+0.006、+0.014px,不到散布的 4%–9%),无偏;std/CRLB = 0.81,实测散布略小于理论下界,可能因 60 次样本估 std 有轻微下偏,或高斯下界稍保守(渐近成立、有限样本有波动)。cx、cy(主点)bias/CRLB = −0.12、−0.07(最大的一项绝对值 0.12),偏差仍远小于散布(bias 0.03px 对 CRLB 0.25px),可接受;std/CRLB = 1.00,主点估计恰好达到下界。k1(一阶径向畸变)bias/CRLB = −0.03、std/CRLB = 1.01,几乎完美。k2、p1、p2、k3(高阶畸变,真值 = 0)bias/CRLB 绝对值都不超过 0.09,无偏;std/CRLB 在 0.87–1.06,有效。
总结判据:
| 判据 | 公式 | 标准 | 本例结果 | 结论 |
|---|---|---|---|---|
| 无偏性 | bias/CRLB | 远小于 1 | ||
| 有效性 | std/CRLB | 接近 1 | 均值 0.96 | 有效 |
无偏加有效,是最优估计器。这验证了两件事:整条 Fisher 信息到雅可比到信息到边缘化到 CRLB 的流水线,推导和代码都对;真实标定里用的观测信息法(在估计处算、而非真值处)也能给出正确的不确定度——仿真替你验证过了。
输出 mc_stats.npz:
| 键 | 形状 | 含义 |
|---|---|---|
est |
(60,9) | 每次 Monte Carlo 估出的 9 个内参 |
mean_est |
(9,) | 跨 60 次的估计均值 |
bias |
(9,) | bias = 估计均值 − 真值(应接近 0) |
bias_ratio |
(9,) | bias/CRLB(应远小于 1,判无偏) |
std |
(9,) | 跨 60 次的实测 std |
crlb |
(9,) | 金标准(复制自第 5 步) |
ratio |
(9,) | std/crlb(应接近 1,判有效) |
结果一览
| 结果 | 数值 | 说明什么 | 出处 |
|---|---|---|---|
| fx 的 CRLB | 0.154 px | 任何算法估 fx 的 1σ 下限,精度天花板,拍照前可算(仿真里);真实标定用观测信息近似此值 | CRLB |
| 边缘化缩减 | 25362 → 532(48 倍) | 外参 nuisance 吃掉 fx 表观信息;不边缘化 CRLB 假性乐观约 7 倍 | Schur 补 |
| CRLB ≠ 对角倒数 | 0.154 ≠ 0.043 | 内参强耦合,精度来自完整逆 | CRLB |
| 条件数 | cond(I_β) = 5.3×10⁸ | 整板入画加多样性把 cond 压到可控;过大则求逆 NaN | 第 5 步 |
| std / CRLB ≈ 1 | Monte Carlo 实测 | 估计器达下界(渐近有效),验证观测信息法可靠 | MLE 渐近 |
踩过的坑
仿真用真值、真实用估计。CRLB 在真值处算(期望信息,金标准,仅仿真可得);真实标定在估计 θ̂ 处算观测信息、取其逆作不确定度(渐近等价)。
整板必须入画。真实标定只看得见画面内角点,部分出画会虚高精度。
位姿参数要独立多样。锁死联动会让 fx、cy 退化、条件数爆、CRLB 出 NaN。用混合基数独立遍历。
σ_a = σ_det/√2。公式里用 σ_a²,否则偏 √2 倍。径向误差平方是两个轴向平方和,故 σ_det² = 2σ_a²。
外参必须边缘化。Schur 掉 nuisance,否则内参 CRLB 假性偏小。
CRLB 不是对角元倒数。内参强耦合,来自完整逆,差可达数倍。
条件数大时 CRLB 不可信。λ_min < ε·λ_max 会让求逆出 NaN;靠入画(压 λ_max)、多样性(抬 λ_min)、多图控制。
cv2 要 float32。calibrateCamera 的点须 float32 加 ascontiguousarray,float64 会出错。
σ_det 要实测。静态重复法:相机加板静止采 100 帧,取角点径向 RMS。
不能只拍正面的原因
标定时常听到的建议"板要倾斜着拍"不是玄学——只拍正面(板正对相机,pitch、yaw≈0)会让焦距、主点、畸变几乎定不准。做个对照:把第 1 步的 pitch、yaw 从 ±30° 改成 0,其余不变,重跑整条流水线。
| 指标 | 基线(±30°) | 只拍正面(=0) | 放大 |
|---|---|---|---|
| fx 的 CRLB | 0.154 px | 4.2×10⁵ px | 2.7×10⁶ |
| cx 的 CRLB | 0.255 px | 1.0×10⁵ px | 4.1×10⁵ |
| k1 的 CRLB | 3.2×10⁻⁴ | 38 | 1.2×10⁵ |
| p2 的 CRLB | 2.9×10⁻⁵ | 4.75 | 1.7×10⁵ |
| I_β 的条件数 | 5.3×10⁸ | ∞(矩阵不定) | — |
正面拍的退化极其彻底:边缘化后的内参信息矩阵 I_β 出现负特征值(最小特征值从 15 掉到小于 0),条件数直接 ∞,求逆失去意义。焦距的 CRLB 从 0.15px 飙到 4×10⁵px,差两百多万倍;径向畸变 k1 从 3×10⁻⁴ 飙到 38,切向 p2 从 3×10⁻⁵ 飙到 4.75。表中没列的 cy、p2 显示为 0 也不是精度高,而是矩阵已经不定、方差算出负值被截断了——纯属数值崩溃。
原因还是信息矩阵:正面拍时板的透视退化为相似变换,几组参数对像素的影响变得共线,信息矩阵出现近零特征方向——
- 焦距 f ↔ 深度 t_z(尺度模糊):正面板的表观大小 = f·板宽/Z,只能定 f/Z 这个比值,f 和 Z 可同比例缩放。
- 主点 c_x/c_y 与板位置、焦距混在一起。
- 畸变:径向要大半径角点(正面居中板半径小,测不到),切向 p1/p2 本质描述镜头偏心,几乎必须有倾斜或不对称才能定。
倾斜板带来透视压缩(分开 f 和距离)、把角点扫过大半径并制造不对称(分开畸变),把这些方向重新撑开。第 1 步采样里 pitch/yaw ±30°、roll ±45°、九宫格位置、不同 fill 的混合遍历,正是为此——这也是为什么"位姿参数要独立多样"那条坑会致命。
退化的发现与定位
退化数据最阴险的地方在于:重投影误差往往很低。欠定的参数能把这批退化数据拟合得很好(拟合好 ≠ 参数对),正面拍 50 张图标完,RMS 可能还是 0.1px,看着很美,但 fx 其实可以是 2200 也可以是 40 万。所以 RMS 是必要不充分条件,绝不能只靠它判好坏。
发现方法(从省事到深入)
- 看 stdDeviations(OpenCV 白给)。
calibrateCamera返回的stdDeviations就是 √diag(I_obs⁻¹),即每个参数的不确定度。fx 的 stdDev 是 0.15px → 正常;是几万 px → 直接报警。比 RMS 靠谱得多。 - 子集复现性(不用任何理论)。把图分两半各标定一次,比内参;两次 fx 差很多 → 欠定。leave-one-out(每次抽掉一张重标)同理,fx 来回跳 → 约束脆弱。这是没真值也能查的穷人验证。
- 条件数 cond(I_β)。自己建信息矩阵(本流水线),cond 飙到 1e14+ 或 ∞ → 退化,直接刻画病态程度。
- 留出图测试。用正面图标定,去重投影一张倾斜的图,误差爆 → 模型根本没学到真畸变/真焦距。
定位步骤:先审输入,再特征分解
最该先做的是审输入位姿分布:每张图的板法向(从 rvec 算)、板在画面的位置、板大小,各自方差≈0 的那一维就是退化维。正面拍的法向全指向相机,一眼就出。
更精确的是特征分解 I_β。先把两个角色分清:
- 特征值(标量 λ):这条方向有多不可辨识,越小越退化,≈0 就是定不出来。
- 特征向量(9 维向量):活在参数空间里,第 k 个分量对应第 k 个参数(第 1/2 位 = fx/fy,第 3/4 位 = cx/cy,后 5 位 = 畸变)。读哪几个分量绝对值大,就是哪几个参数。
把"为什么"说透——信息矩阵是对数似然在最优点处的曲率,特征值就是沿该方向的曲率:
$$ \mathcal I_\beta=\mathbf V\boldsymbol\Lambda\mathbf V^\top,\qquad [\mathcal I_\beta^{-1}]_{kk}=\sum_j \frac{V_{kj}^{2}}{\lambda_j} $$λ≈0 的方向似然是平的(扰动参数、像素几乎不变)→ 不可辨识;上面第二个式子说明,任何近零 λ_j 都会让 1/λ_j 爆炸,把所有在 v_j 上有分量的参数的方差下界一起抬高——这就是退化会"连累"一堆参数 CRLB 飙升的根因。
用前面「只拍正面」那组退化数据,I_β 有 3 个近零特征值,特征向量这样读:
| 近零 λ | 大分量 | 读作 |
|---|---|---|
| 3.3×10⁻⁹ | cy=−0.88, cx=−0.48 | 主点 |
| 1.9×10⁻⁷ | cx=+0.87, cy=−0.47 | 主点(正交组合) |
| 3.6×10⁻⁷ | fx=+0.70, fy=+0.70 | 焦距 |
两条主点 + 一条焦距,即"正面拍 → 主点和焦距不可辨识",和直觉完全吻合。分量间的符号比值也有含义:fx、fy 同号等大表示二者同步缩放(典型的 f/距离尺度退化);cx、cy 的组合给出主点沿某朝向移动。
定位后的修复方法
特征向量告诉你缺哪一维多样性,对症补采集:
- 主点方向退化 → 多拍板偏离中心(含画面角落)的图;
- 焦距方向退化 → 多拍不同倾斜、不同距离的图;
- 畸变方向退化 → 多拍大半径(板占满画面)+ 倾斜的图。
补齐后 cond(I_β)、stdDeviations 会一起回落,就是修好了的信号。
采集前的准备
正经采集前,核心是把会改变内参的变量锁死、系统误差源提前排除——而且可以用仿真在拍之前就预估精度够不够。这份准备清单决定了整轮采集能不能用。
锁死相机的几何相关设置
内参是"这台相机在这个设置下"的属性,设置一变就作废:
- 手动对焦并锁定:标定中绝不能让自动对焦跑——焦距变了,每张图的内参都不同,标定无意义。在工作距离对实。
- 固定焦距(变焦别动 zoom),定焦最好。
- 固定光圈(中段 f/5.6~f/8 通常最锐、像差最小)。
- 关掉机内畸变/镜头校正(数字变焦、畸变校正 profile 都关),否则标定的是校正后的图,不是镜头本身。
- 用实际处理的分辨率拍:fx/fy/cx/cy 是像素单位、和分辨率绑定,别满分辨率标定再降采样。
- 固定 ISO/曝光选低噪声档,相机热稳定后再开拍。
标定板
- 平:翘曲是 CRLB 看不见的系统偏差。用铝板/玻璃背衬的刚性板,别用软纸或易翘覆膜,拿直尺验。
- 方格尺寸量准:数显卡尺量多个方格取平均。它决定物理尺度(fx 像素值不受影响,但外参和物理解读依赖它)。
- 大小匹配 FOV/工作距离:板要能在工作距离占画面约 1/3~1/2;广角要大板,长焦要小板。
- 清洁、无反光、黑白高对比。
环境与拍摄
匀光漫射光,避免反光和强阴影;防抖(三脚架或高速快门,运动模糊直接抬高 σ_a);每个曝光内板和相机静止。
先测噪声底,再预估精度(最值钱的一步)
整套理论最实在的落地——拍之前就能知道够不够:
- 静态重复测 σ_a:相机和板都不动,连拍约 100 帧,检测角点,算角点位置跨帧的 std → 得径向 σ_det,单轴 σ_a = σ_det/√2。这就是你的噪声底。
- 仿真预估 CRLB:把计划好的板、规划的位姿覆盖、测到的 σ_a 喂进本文流水线(第 1~5 步),算出预期 CRLB。满足下游精度 → 放心拍;不满足 → 先调(加图、加多样性、降噪声)再拍,省下一整轮废采集。
这一步把"实验设计"变成可计算的:拍前估、拍后验,是 Fisher 信息的核心用途,也是本文仿真流水线最实在的价值。
规划覆盖、流程就绪、记元数据
- 列位姿清单,确保倾斜全方向、位置含角落、远近多样、滚转都有(对应那张"多样性↔参数方向"表)。
- 管线先测通:先拍 1~2 张跑检测+标定,确认角点检测稳、代码能跑、现场能即时查 RMS/stdDeviations。
- 记元数据:相机型号、镜头、标称焦距、光圈、对焦、分辨率、工作距离、板型号与实测方格尺寸、光照。
可靠标定的标准流程
前面用仿真验证了方法、用退化实验看清了坑。把结论收拢成一条可照着做的标准流程:多样的采集 + 恰当的模型 + 自检诊断闭环,目标是让 I_β 良态、stdDeviations 小到够用、且无系统偏差。Fisher 信息把每一步都变成可量化、可验证的事。
采集:多样覆盖,让 I_β 良态
这是决定成败、也最难补救的一步。本质是在参数空间里给每个特征方向喂够信息——信息是各图累加、并按噪声缩放的:
$$ \mathcal I=\sum_{\text{图}}\frac{1}{\sigma_a^{2}}\,\mathbf J^\top\mathbf J $$所以"图越多越好"只在新方向上成立(雷同图在已覆盖方向上重复累加,是浪费),而 σ_a 越小(图越清晰)每个角点贡献越大。每维多样性对应它撑起的参数方向:
| 采集维度 | 撑起的参数方向 | 缺了会怎样 |
|---|---|---|
| 板多向倾斜(pitch/yaw 正负都覆盖) | 焦距(破 f↔Z 尺度)、主点、切向畸变 | 焦距/主点退化(实验里 ±30° 够,=0 崩) |
| 板偏离中心、拍到画面角落 | 主点、大半径径向畸变 | 主点和 k1/k2/k3 定不准 |
| 不同距离(板占画面 1/3~1/2) | 分开焦距与畸变(角点扫不同半径) | 焦距畸变耦合 |
| 板自身滚转 | 解耦面内参数 | 部分参数强相关 |
| 覆盖全视场(尤其边缘) | 径向畸变(要大半径角点) | 高阶畸变测不到 |
清单:倾斜全方向、位置铺满含角落、远近多样、滚转有;图清晰对焦光照足;板要平、方格尺寸量准(翘曲或错尺寸是 CRLB 看不见的系统偏差)。数量服从多样性——20 张充分错开胜过 100 张雷同正面;实际 15–30 张高质量全覆盖通常够。
检测与求解
findChessboardCorners + cornerSubPix 亚像素细化;检测差的图(模糊、反光、遮挡)剔除——它们抬高 σ 还可能引偏。求解用 cv2.calibrateCamera,畸变模型别过度参数化:镜头若 5 系数够用就别上 8/14,多出来的弱约束参数会变成新的近零特征方向,反而让 I_β 病态。按镜头实际畸变量级选阶数。
验证诊断(闭环,别跳过)
把"看着不错"变成"确实可靠",就是上一节的诊断套件,按代价从低到高:重投影 RMS 接近 σ_a(亚像素,好检测 < 0.3px,但必要不充分)→ 逐图误差看分布、剔除离群 → stdDeviations 每个内参都小到合理(fx 的不能是几万,最省事最该盯)→ 子集复现性 / leave-one-out 看稳定性 → cond(I_β) + 特征分解看退化与缺维 → 物理交叉校验(fx ≈ 焦距(mm)/像元(mm)、主点 ≈ 画面中心)挡系统偏差 → 留出图重投影测泛化。
精度预算与定稿
内参要连同 stdDeviations 一起报告,不能只给一个数。按下游任务反查:需要 fx 准到 0.1px 而 stdDev 是 0.15px → 这组不够,回去补数据;够用才定稿。Fisher 信息把"精度预算"变成拍前能估、拍后能验的量。
几条原理
- 多样性 > 数量:信息只在新的参数方向上累加。
- 条件数是命脉:目标是 I_β 良态,λ_min 远高于 ε·λ_max 地板。
- 噪声是地板:σ_a 决定单角点信息,清晰图 + 亚像素检测最划算。
- CRLB 只管随机误差:系统偏差靠物理控制和交叉校验。
- 闭环验证:每改采集或模型,都用 stdDev + 特征分解 + 物理校验重新确认。
最后记住:真实流程里 Fisher 信息是在估计 θ̂ 处算的(观测信息),渐近等价于真值处的金标准。本文那条仿真流水线的价值,正是先用真值验证了"观测信息法可靠"(std/CRLB≈1),才敢把它用在真实数据上——仿真不是玩具,是真实流程的计量校准。那套真实诊断的完整 10 步实操(含 k 折、独立测试、数据一致性补回"没有真值就无法验证"这步)见《Fisher 信息:相机标定真实数据实操》。
参考资料
- cv::calibrateCamera — OpenCV 文档
- Fisher information — Wikipedia
- Cramér–Rao bound — Wikipedia
- 理论推导:《Fisher 信息与精度下界》
- 真实数据实操:《Fisher 信息:相机标定真实数据实操》
文章链接:
https://www.zywvvd.com/notes/study/camera-imaging/fisher-information-calib/fisher-information-calib/
“觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭”
微信支付
支付宝支付