OpencvSharp 算子学习教案之 - Cv2.DecomposeProjectionMatrix 重载2
重载2:DecomposeProjectionMatrix(double[,] projMatrix, out double[,] cameraMatrix, out double[,] rotMatrix, out double[] transVect, out double[,] rotMatrixX, out double[,] rotMatrixY, out double[,] rotMatrixZ, out double[] eulerAngles)

大家好,Opencv在很多工程项目中都会用到,而OpencvSharp则是以C#开发与实现的Opencv操作库,对.NET开发人员友好,但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳,因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案,供大家参考学习。

Cv2.DecomposeProjectionMatrix

  • 教案版本:V1.0
  • 面向对象:OpenCvSharp 初学者
  • 所属模块:calib3d
  • 源码位置:OpenCvSharp/Cv2/Cv2_calib3d.cs:361

1. 函数名称(带参数签名)

public static void DecomposeProjectionMatrix(
    double[,] projMatrix,
    out double[,] cameraMatrix,
    out double[,] rotMatrix,
    out double[] transVect,
    out double[,] rotMatrixX,
    out double[,] rotMatrixY,
    out double[,] rotMatrixZ,
    out double[] eulerAngles)

2. 函数用途

这个重载是纯托管数组的全量输出版本,适合:

  1. double[,] / double[] 数据流。
  2. 需要同时拿到 cameraMatrix、rotMatrix、transVect、轴矩阵和 Euler 角的场景。
  3. 需要和 InputArray 版本做一致性对比的教学或测试场景。

它会返回:

  1. cameraMatrix:3x3 相机内参矩阵。
  2. rotMatrix:3x3 旋转矩阵。
  3. transVect:4x1 齐次相机中心向量。
  4. rotMatrixX / rotMatrixY / rotMatrixZ:三个轴向旋转矩阵。
  5. eulerAngles:三个欧拉角(度)。

3. 函数公式

3.1 投影矩阵模型

P=K[R∣t] P = K [R \mid t] P=K[Rt]

3.2 结构分解

M=P0:3,0:3=KR M = P_{0:3,0:3} = K R M=P0:3,0:3=KR

M=KR,K 为上三角,RTR=I M = K R,\quad K \text{ 为上三角},\quad R^T R = I M=KR,K 为上三角,RTR=I

3.3 相机中心

C~=[Cx,Cy,Cz,w]T \tilde C = [C_x, C_y, C_z, w]^T C~=[Cx,Cy,Cz,w]T

C=1w[Cx,Cy,Cz]T C = \frac{1}{w}[C_x, C_y, C_z]^T C=w1[Cx,Cy,Cz]T

3.4 Euler 角与轴矩阵

rotMatrixX / rotMatrixY / rotMatrixZeulerAngles 描述的是一组合法的姿态分解结果。由于欧拉角存在多解,OpenCV 只返回其中一种可用解。

3.5 常用质量指标

εorth=∥RTR−I∥F,εrecon=∥P−K[R∣−RC]∥F \varepsilon_{orth} = \|R^T R - I\|_F, \quad \varepsilon_{recon} = \|P - K[R \mid -RC]\|_F εorth=RTRIF,εrecon=PK[RRC]F

4. 函数原理说明

这个重载直接在托管数组层面完成矩阵分解,底层逻辑与其他版本一致:

  1. 对投影矩阵左上 3x3 子矩阵执行 RQ 分解。
  2. 从完整 3x4 矩阵中提取齐次相机中心。
  3. 写出轴矩阵与 Euler 角,供教学或调试使用。

它的优点是:

  1. 不需要手工创建 Mat
  2. 更适合单元测试和批处理。
  3. 输出结果完全可以直接喂给后续纯托管数组逻辑。

5. 参数含义解析

参数名 类型 必填 含义 典型形状
projMatrix double[,] 输入 3x4 投影矩阵 3x4
cameraMatrix out double[,] 输出相机内参矩阵 3x3
rotMatrix out double[,] 输出旋转矩阵 3x3
transVect out double[] 输出齐次相机中心向量 长度 4
rotMatrixX out double[,] 绕 X 轴的旋转矩阵 3x3
rotMatrixY out double[,] 绕 Y 轴的旋转矩阵 3x3
rotMatrixZ out double[,] 绕 Z 轴的旋转矩阵 3x3
eulerAngles out double[] 三个欧拉角(度) 长度 3

6. 应用场景列表

场景名 场景说明 价值
场景A:double[,] 全量分解 输出所有结果 适合完整教学
场景B:轴矩阵质量检查 验证 rotMatrixX/Y/Z 解释姿态分解
场景C:与 InputArray 重载对比 验证两类封装一致 交叉校验
场景D:扰动敏感性分析 观察噪声影响 评估稳定性

7. 函数使用示例(每个场景一个完整示例)

7.1 场景A:double[,] 全量分解

using System;
using OpenCvSharp;

internal static class Program
{
    private static void Main()
    {
        // 这里构造一个 3x4 投影矩阵,作为全量重载的输入样本。
        double[,] projMatrix =
        {
            { 1195.0, 0.0, 640.0, 1850.0 },
            { 0.0, 1185.0, 360.0, -720.0 },
            { 0.0, 0.0, 1.0, 4.20 },
        };

        // 全量重载一次性输出 cameraMatrix、rotMatrix、transVect、三个轴矩阵和 Euler 角。
        // 对于初学者来说,这个版本最适合观察“完整分解结果”到底长什么样。
        Cv2.DecomposeProjectionMatrix(
            projMatrix,
            out var cameraMatrix,
            out var rotMatrix,
            out var transVect,
            out var rotMatrixX,
            out var rotMatrixY,
            out var rotMatrixZ,
            out var eulerAngles);

        // transVect 是齐次相机中心,长度固定为 4。
        Console.WriteLine($"transVect length = {transVect.Length}");

        // Euler 角是度数制输出,便于阅读和教学讲解。
        Console.WriteLine($"eulerAngles = [{eulerAngles[0]:F6}, {eulerAngles[1]:F6}, {eulerAngles[2]:F6}]");
    }
}

7.2 场景B:轴矩阵质量检查

using OpenCvSharp;

// 为了让这个示例可以单独复制运行,这里先准备和 7.1 相同的投影矩阵。
double[,] projMatrix =
{
    { 1195.0, 0.0, 640.0, 1850.0 },
    { 0.0, 1185.0, 360.0, -720.0 },
    { 0.0, 0.0, 1.0, 4.20 },
};

// 先执行全量重载,拿到 rotMatrixX/Y/Z 和 Euler 角。
Cv2.DecomposeProjectionMatrix(
    projMatrix,
    out var cameraMatrix,
    out var rotMatrix,
    out var transVect,
    out var rotMatrixX,
    out var rotMatrixY,
    out var rotMatrixZ,
    out var eulerAngles);

// 再把左上 3x3 子矩阵提取出来,因为 RQDecomp3x3 只关心这个部分。
var left3x3 = new double[,]
{
    { projMatrix[0, 0], projMatrix[0, 1], projMatrix[0, 2] },
    { projMatrix[1, 0], projMatrix[1, 1], projMatrix[1, 2] },
    { projMatrix[2, 0], projMatrix[2, 1], projMatrix[2, 2] },
};

// 用 RQDecomp3x3 再做一次分解,方便和 DecomposeProjectionMatrix 的输出交叉验证。
var rqAngles = Cv2.RQDecomp3x3(left3x3, out var rqR, out var rqQ, out var rqX, out var rqY, out var rqZ);

// 下面用最直接的方式计算一个轴矩阵的正交误差:Qx * Qx^T 是否接近单位矩阵。
double orthErrorX = 0.0;
for (var row = 0; row < 3; row++)
{
    for (var col = 0; col < 3; col++)
    {
        // 这一步是在手写矩阵乘法,方便理解“行乘列”到底在算什么。
        double sum = 0.0;
        for (var k = 0; k < 3; k++)
        {
            sum += rotMatrixX[row, k] * rotMatrixX[col, k];
        }

        // 对角线期望是 1,非对角线期望是 0。
        var expected = row == col ? 1.0 : 0.0;
        var diff = sum - expected;
        orthErrorX += diff * diff;
    }
}

7.3 场景C:与 InputArray 重载对比

using OpenCvSharp;

// 这里沿用 7.1 中的 projMatrix 样本;如果单独复制这一段,请先补上同样的 3x4 矩阵定义。
// 先把同一份输入包装成 Mat,再转成 InputArray,以便调用 InputArray 版本。
using var projMat = Mat.FromPixelData(3, 4, MatType.CV_64FC1, projMatrix);
using var projInput = InputArray.Create((Mat)projMat);

// 这些 Mat<double> 容器用于接收 InputArray 版本输出。
using var kMat = new Mat<double>();
using var rMat = new Mat<double>();
using var tMat = new Mat<double>();
using var xMat = new Mat<double>();
using var yMat = new Mat<double>();
using var zMat = new Mat<double>();
using var eMat = new Mat<double>();

// 先跑 InputArray 版本,再跑 double[,] 版本,最后比较核心结果。
Cv2.DecomposeProjectionMatrix(projInput, OutputArray.Create(kMat), OutputArray.Create(rMat), OutputArray.Create(tMat), OutputArray.Create(xMat), OutputArray.Create(yMat), OutputArray.Create(zMat), OutputArray.Create(eMat));
Cv2.DecomposeProjectionMatrix(projMatrix, out var basicK, out var basicR, out var basicT, out var basicX, out var basicY, out var basicZ, out var basicE);

7.4 场景D:扰动敏感性分析

using OpenCvSharp;

// 这里继续沿用 7.1 中的 projMatrix 样本。
// 复制原始矩阵,避免修改基础样本。
var noisyProj = (double[,])projMatrix.Clone();

// 这里故意加一点微小扰动,模拟真实测量中的噪声或数值误差。
noisyProj[0, 3] += 0.12;
noisyProj[1, 3] -= 0.08;

// 再次执行全量分解,观察 K/R/角度是否出现明显波动。
Cv2.DecomposeProjectionMatrix(noisyProj, out var noisyK, out var noisyR, out var noisyT, out var noisyX, out var noisyY, out var noisyZ, out var noisyE);

8. 函数使用注意事项

  1. transVect 仍然需要按 w 归一化后再使用。
  2. eulerAngles 并不是唯一表示,不能和别的实现做“逐元素绝对等值”判断。
  3. 对比姿态时,优先比较 rotMatrixcameraMatrix,再看 Euler 角。
  4. 若输入矩阵包含噪声,欧拉角比矩阵更容易出现跳变。

9. 参数调优建议

  1. 如果你只关心核心结果,基础重载更简洁;如果你要教学/调试姿态,则优先全量重载。
  2. 做自动化验证时,建议同时检查 rotMatrix 的正交误差和重建误差。
  3. 如果要和其他库对比,请先统一矩阵约定和欧拉角顺序。

10. 示例代码运行说明(按场景关键逻辑)

场景A(全量分解)

  1. 准备 3x4 投影矩阵。
  2. 调用全量重载。
  3. 输出所有矩阵和 Euler 角。

场景B(轴矩阵质量检查)

  1. 对左上 3x3 子矩阵调用 RQDecomp3x3
  2. 对比轴矩阵和欧拉角。
  3. 验证结果一致性。

场景C(与 InputArray 对比)

  1. 用同一输入分别调用两种封装。
  2. 对比所有核心输出。
  3. 确认 wrapper 不改变数值行为。

场景D(扰动敏感性)

  1. 对输入矩阵施加小扰动。
  2. 重新分解。
  3. 观察输出变化。

11. 常见错误排查

  1. 错误:把 Euler 角当作唯一姿态表示
    • 排查:欧拉角存在多解,优先比较旋转矩阵。
  2. 错误:忽略 transVect 的齐次归一化
    • 排查:先除以 w
  3. 错误:认为全量重载和 InputArray 重载的结果可能不同
    • 排查:两者底层数学过程相同,差异通常来自输入不一致或数据类型转换。
  4. 错误:只看一个矩阵指标
    • 排查:至少同时检查 cameraMatrixrotMatrix 和重建误差。

对应 WPF 演示控件与样例代码:

  • Features/Cv2DecomposeProjectionMatrixDoubleArrayFull/Cv2DecomposeProjectionMatrixDoubleArrayFullControl.xaml
  • Samples/Cv2DecomposeProjectionMatrix/DecomposeProjectionMatrixDoubleFullDecomposeSample.cs
  • Samples/Cv2DecomposeProjectionMatrix/DecomposeProjectionMatrixDoubleFullAxisSample.cs
  • Samples/Cv2DecomposeProjectionMatrix/DecomposeProjectionMatrixDoubleFullCompareInputArraySample.cs
  • Samples/Cv2DecomposeProjectionMatrix/DecomposeProjectionMatrixDoubleFullPerturbationSample.cs
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐