OpencvSharp 算子学习教案之 - Cv2.DecomposeProjectionMatrix 重载1
重载1:DecomposeProjectionMatrix(InputArray projMatrix, OutputArray cameraMatrix, OutputArray rotMatrix, OutputArray transVect, OutputArray? rotMatrixX = null, OutputArray? rotMatrixY = null, OutputArray? rotMatrixZ = null, OutputArray? eulerAngles = null)

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

Cv2.DecomposeProjectionMatrix

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

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

public static void DecomposeProjectionMatrix(
    InputArray projMatrix,
    OutputArray cameraMatrix,
    OutputArray rotMatrix,
    OutputArray transVect,
    OutputArray? rotMatrixX = null,
    OutputArray? rotMatrixY = null,
    OutputArray? rotMatrixZ = null,
    OutputArray? eulerAngles = null)

2. 函数用途

这个重载用于在 Mat / OutputArray 工作流中分解 3x4 投影矩阵,适合:

  1. 相机标定结果的后处理与验证。
  2. 位姿恢复、AR 投影链路和 3D 重建管线。
  3. 需要额外输出轴旋转矩阵和 Euler 角的教学/调试场景。

它会输出:

  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]

其中:

K∈R3×3,R∈R3×3,t∈R3 K \in \mathbb{R}^{3\times3},\quad R \in \mathbb{R}^{3\times3},\quad t \in \mathbb{R}^{3} KR3×3,RR3×3,tR3

3.2 左上 3x3 子矩阵分解

设投影矩阵左上 3x3 子矩阵为:

M=P0:3,0:3 M = P_{0:3,0:3} M=P0:3,0:3

函数会对 M 做 RQ 分解:

M=KR M = K R M=KR

其中 K 为上三角矩阵,R 为正交矩阵。

3.3 相机中心与平移向量

transVect 保存的是齐次相机中心向量,可写为:

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

再由相机中心恢复外参平移:

t=−RC t = -R C t=RC

3.4 Euler 角与轴矩阵

rotMatrixX / rotMatrixY / rotMatrixZeulerAngles 表示一种合法的欧拉分解结果。由于欧拉角表示并不唯一,同一姿态可能对应多组等价解。

4. 函数原理说明

该重载可以理解为“Mat 风格的投影矩阵分解入口”:

  1. InputArray 转换为内部可处理的 Mat
  2. 分离出左上 3x3 子矩阵,调用 RQDecomp3x3 获得 cameraMatrixrotMatrix
  3. 对 3x4 投影矩阵做齐次分解,提取相机中心并写入 transVect
  4. 若传入 rotMatrixX / rotMatrixY / rotMatrixZ / eulerAngles,则同步写出这些可选结果。

对于初学者,最重要的是记住两点:

  1. transVect 不是直接的三维平移向量,而是齐次相机中心。
  2. cameraMatrixrotMatrix 才是最常用的核心输出。

5. 参数含义解析

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

6. 应用场景列表

场景名 场景说明 价值
场景A:InputArray 完整分解 输出所有核心与可选结果 适合教学总览
场景B:相机中心与平移恢复 验证 transVect 的含义 帮助理解齐次坐标
场景C:轴矩阵与 Euler 角 对比 RQ 分解结果 解释姿态表示
场景D:扰动敏感性分析 输入轻微噪声后再分解 观察稳定性

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

说明:以下示例均为完整 Program.cs 的核心逻辑,注释采用教学风格。为突出 API 重点,省略了少量重复的打印辅助代码。

7.1 场景A:InputArray 完整分解

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 },
        };

        // Mat.FromPixelData 把托管二维数组包装成 OpenCV 可直接读取的 Mat。
        // 这里使用 3x4 的 CV_64FC1,是因为投影矩阵必须是双精度单通道。
        using var projMat = Mat.FromPixelData(3, 4, MatType.CV_64FC1, projMatrix);

        // InputArray 是 OpenCV 的通用输入包装器。
        // 这里再包一层,是为了命中 DecomposeProjectionMatrix 的 InputArray 重载。
        using var projInput = InputArray.Create((Mat)projMat);

        // 以下几个 Mat<double> 是输出容器,调用结束后会装着分解结果。
        using var cameraMat = new Mat<double>();
        using var rotMat = new Mat<double>();
        using var transMat = new Mat<double>();

        // 可选输出:三个轴向旋转矩阵和 Euler 角。
        // 即使某个场景暂时不展示,也建议在教学示例里把它们准备好,方便观察完整结果。
        using var rotXMat = new Mat<double>();
        using var rotYMat = new Mat<double>();
        using var rotZMat = new Mat<double>();
        using var eulerMat = new Mat<double>();

        // 调用目标函数:输入投影矩阵,输出相机内参、旋转矩阵、相机中心以及可选的轴矩阵/Euler 角。
        // OutputArray.Create(...) 的作用是把 Mat 包装成 OpenCV 需要的输出参数。
        Cv2.DecomposeProjectionMatrix(
            projInput,
            OutputArray.Create(cameraMat),
            OutputArray.Create(rotMat),
            OutputArray.Create(transMat),
            OutputArray.Create(rotXMat),
            OutputArray.Create(rotYMat),
            OutputArray.Create(rotZMat),
            OutputArray.Create(eulerMat));

        // ToRectangularArray() 把 Mat 转回 double[,],这样更适合初学者在控制台中观察。
        Console.WriteLine("cameraMatrix:");
        PrintMatrix(cameraMat.ToRectangularArray());
        Console.WriteLine();

        // 旋转矩阵也是同样方式打印,方便和教案公式逐项对照。
        Console.WriteLine("rotMatrix:");
        PrintMatrix(rotMat.ToRectangularArray());
    }

    private static void PrintMatrix(double[,] matrix)
    {
        // 这个小工具只是为了把矩阵打印得更整齐,便于肉眼检查每一行每一列。
        for (var row = 0; row < matrix.GetLength(0); row++)
        {
            Console.Write("[");
            for (var col = 0; col < matrix.GetLength(1); col++)
            {
                Console.Write(matrix[row, col].ToString("F6"));
                if (col < matrix.GetLength(1) - 1)
                {
                    Console.Write(", ");
                }
            }
            Console.WriteLine("]");
        }
    }
}

7.2 场景B:相机中心与平移恢复

using OpenCvSharp;

// 为了让这个示例可以单独复制运行,这里仍然先准备同一份投影矩阵样本。
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 },
};

// 把数组包装成 Mat,再交给 InputArray 版本的分解函数。
using var projMat = Mat.FromPixelData(3, 4, MatType.CV_64FC1, projMatrix);
using var projInput = InputArray.Create((Mat)projMat);
using var cameraMat = new Mat<double>();
using var rotMat = new Mat<double>();
using var transMat = new Mat<double>();

// 执行分解后,transMat 中保存的是齐次相机中心。
Cv2.DecomposeProjectionMatrix(projInput, OutputArray.Create(cameraMat), OutputArray.Create(rotMat), OutputArray.Create(transMat));

// transVect 是 4x1 齐次向量,最后一个分量是 w。
// 先除以 w,才能得到真正的三维相机中心 C。
var transVect = transMat.ToRectangularArray();
var cameraCenter = new[]
{
    transVect[0, 0] / transVect[3, 0],
    transVect[1, 0] / transVect[3, 0],
    transVect[2, 0] / transVect[3, 0],
};

// rotMatrix 是 3x3 旋转矩阵。
// 根据相机几何关系,平移向量可以由 t = -R * C 反推出来。
var rotMatrix = rotMat.ToRectangularArray();
var translation = new double[3];
for (var row = 0; row < 3; row++)
{
    // 这里显式展开矩阵乘法,目的是让初学者看清楚每一个分量是怎么来的。
    var sum = 0.0;
    for (var col = 0; col < 3; col++)
    {
        sum += rotMatrix[row, col] * cameraCenter[col];
    }

    // 结果前面加负号,就是外参平移向量 t。
    translation[row] = -sum;
}

7.3 场景C:轴矩阵与 Euler 角对比

using OpenCvSharp;

// 这里沿用 7.1 的 projMatrix 样本;如果单独复制,请先准备同样的 3x4 投影矩阵。
// 只取投影矩阵左上 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);

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

using OpenCvSharp;

// 这里继续沿用 7.1 / 7.2 中同一组投影矩阵样本,只是复制一份出来做扰动。
// 先复制原始矩阵,避免修改原始样本数据后影响后续对比。
var noisyProj = (double[,])projMatrix.Clone();

// 这里给投影矩阵加一点点噪声,用来模拟真实标定/测量过程中的浮点扰动。
noisyProj[0, 3] += 0.12;
noisyProj[1, 3] -= 0.08;

// 再次分解扰动后的投影矩阵,观察输出变化是否稳定。
Cv2.DecomposeProjectionMatrix(noisyProj, out var noisyK, out var noisyR, out var noisyT);

8. 函数使用注意事项

  1. projMatrix 必须是 3x4。
  2. transVect 是齐次向量,使用前要先按 w 归一化。
  3. eulerAnglesrotMatrixX/Y/Z 不是唯一解,不要硬性假设只有一组结果。
  4. 只看欧拉角很容易误判,建议同时看矩阵误差和重建误差。

9. 参数调优建议

  1. 输入投影矩阵尽量使用稳定尺度,避免极小或极大数值带来条件数问题。
  2. 优先检查三项指标:cameraMatrix 误差、rotMatrix 误差和 P 重建误差。
  3. 若要比较姿态结果,优先比较旋转矩阵,再看 Euler 角。

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

场景A(完整分解)

  1. 准备 3x4 投影矩阵。
  2. 创建 InputArrayOutputArray 容器。
  3. 调用函数并读取所有输出。

场景B(相机中心恢复)

  1. 取出 transVect
  2. 先做齐次归一化得到 cameraCenter
  3. 再用 t = -R * C 恢复平移向量。

场景C(轴矩阵对比)

  1. 对左上 3x3 子矩阵调用 RQDecomp3x3
  2. 对比 rotMatrixX/Y/ZeulerAngles
  3. 确认结果与 DecomposeProjectionMatrix 保持一致。

场景D(扰动敏感性)

  1. 对投影矩阵施加微扰。
  2. 再次调用分解函数。
  3. 对比前后结果变化。

11. 常见错误排查

  1. 错误:输入矩阵不是 3x4
    • 排查:确认投影矩阵维度是否正确。
  2. 错误:把 transVect 误认为普通平移向量
    • 排查:先除以 w 再恢复三维相机中心。
  3. 错误:Euler 角和自己的手工公式对不上
    • 排查:Euler 角存在多解,优先比较旋转矩阵。
  4. 错误:重建误差略大
    • 排查:确认 P = K [R | t] 里的 t = -R C 是否按正确顺序恢复。

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

  • Features/Cv2DecomposeProjectionMatrixInputArray/Cv2DecomposeProjectionMatrixInputArrayControl.xaml
  • Samples/Cv2DecomposeProjectionMatrix/DecomposeProjectionMatrixInputArrayDecomposeSample.cs
  • Samples/Cv2DecomposeProjectionMatrix/DecomposeProjectionMatrixInputArrayCameraCenterSample.cs
  • Samples/Cv2DecomposeProjectionMatrix/DecomposeProjectionMatrixInputArrayAxisSample.cs
  • Samples/Cv2DecomposeProjectionMatrix/DecomposeProjectionMatrixInputArrayPerturbationSample.cs
Logo

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

更多推荐