关于ceres中的残差块、参数块

Ceres中残差与参数块设计解析

一个参数块是相机参数(维度为9),第二个参数块是三维点坐标(维度为3),为什么不写成12个维度的一个参数块?

1. 问题结构利用(最重要原因)

稀疏性模式

在捆绑调整(Bundle Adjustment)问题中:

  • 每个残差只依赖于一个相机一个三维点
  • 不同相机-点对之间的参数是相互独立

这种结构导致雅可比矩阵具有特殊的块对角结构

残差1: [相机1雅可比] [点1雅可比] [0] [0] ...
残差2: [相机1雅可比] [0] [点2雅可比] [0] ...
残差3: [0] [相机2雅可比] [点3雅可比] [0] ...

计算效率

Ceres可以利用这种稀疏性使用Schur消元法等高效算法,将计算复杂度从O(n³)降低到接近O(n)。

2. 参数类型和约束不同

不同的参数性质

// 相机参数(9维) - 通常有特殊约束
struct CameraParams {
    double rotation[3];    // 旋转(可能需要归一化)
    double translation[3]; // 平移
    double intrinsics[3];  // 内参(焦距、主点等)
};

// 三维点坐标(3维) - 普通欧几里得空间
struct Point3D {
    double x, y, z;
};

不同的参数化方式

// 相机旋转可能需要特殊参数化(四元数、旋转向量等)
problem.SetParameterization(camera_parameters, new ceres::QuaternionParameterization());

// 三维点使用普通欧几里得参数化
problem.SetParameterization(point_parameters, new ceres::IdentityParameterization(3));

3. 内存和计算优化

内存访问模式

分开的参数块允许更高效的缓存利用

// 不好的方式:所有参数混合存储
double all_parameters[12] = {cam1, cam2, ..., point1, point2, ...};

// 好的方式:参数按类型分组
double cameras[num_cameras * 9];  // 连续存储所有相机参数
double points[num_points * 3];    // 连续存储所有三维点

并行计算优化

// Ceres可以并行处理不同的参数块
// 线程1:更新相机参数
// 线程2:更新三维点参数

4. 实际代码对比

方式1:错误的方式(混合参数块)

// 将所有参数合并成一个12维参数块 - 不推荐!
struct BadReprojectionError {
    template<typename T>
    bool operator()(const T* const all_params, T* residual) const {
        const T* camera = &all_params[0];  // 前9个是相机参数
        const T* point = &all_params[9];   // 后3个是点参数
        
        // 计算重投影误差...
        residual[0] = ...;
        residual[1] = ...;
        return true;
    }
};

// 使用方式 - 效率低下
ceres::CostFunction* bad_cost_function = 
    new ceres::AutoDiffCostFunction<BadReprojectionError, 2, 12>(...);

方式2:正确的方式(分开参数块)

// 将相机和点分开为两个参数块 - 推荐!
struct GoodReprojectionError {
    template<typename T>
    bool operator()(const T* const camera, const T* const point, T* residual) const {
        // 计算重投影误差...
        residual[0] = ...;
        residual[1] = ...;
        return true;
    }
};

// 使用方式 - 高效
ceres::CostFunction* good_cost_function = 
    new ceres::AutoDiffCostFunction<GoodReprojectionError, 2, 9, 3>(...);

// 添加到问题中
problem.AddResidualBlock(good_cost_function, nullptr, camera_params, point_params);

5. Schur消元法的优势

当使用分开的参数块时,Ceres可以应用Schur消元法

// 设置求解器选项以利用稀疏性
ceres::Solver::Options options;
options.linear_solver_type = ceres::SPARSE_SCHUR;  // 使用Schur消元
options.minimizer_progress_to_stdout = true;

ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);

Schur消元法的优势

  • 将大问题分解为相机参数和点参数两个子系统
  • 先求解相机参数,再回代求解点参数
  • 计算复杂度从O((9M+3N)³)降低到O((9M)³ + 3N×(9M)²)

6. 实际问题规模的影响

考虑一个典型的捆绑调整问题:

  • 100个相机(9×100 = 900参数)
  • 10,000个三维点(3×10,000 = 30,000参数)
  • 总参数:30,900个

如果合并成一个参数块:

  • 雅可比矩阵:30,900 × 30,900 → 需要处理9.5亿个元素

如果分开参数块:

  • 利用稀疏性,实际只需要存储非零块
  • 内存使用量减少几个数量级

7. 总结

将相机参数和三维点坐标分开成两个参数块的主要原因是:

  1. 利用稀疏性:Ceres可以识别问题结构,使用高效算法
  2. 参数性质不同:相机和点有不同的约束和参数化需求
  3. 计算效率:Schur消元法等算法可以大幅提升性能
  4. 内存优化:减少内存使用,改善缓存性能
  5. 可扩展性:便于处理大规模优化问题

对于大规模捆绑调整问题,这种参数块分离的设计可以带来几个数量级的性能提升,是Ceres能够高效处理计算机视觉问题的关键设计之一。

AutoDiffCostFunction到底做了什么?

AutoDiffCostFunction 的模板参数

ceres::AutoDiffCostFunction<CostFunctor, kNumResiduals, kParameterBlock1Size, kParameterBlock2Size, ...>
  • CostFunctor:代价函子类型,必须是一个具有 template<typename T> bool operator()(const T* const parameters..., T* residuals) const 方法的类。
  • kNumResiduals:残差的数量。
  • kParameterBlock1Size:第一个参数块的维度。
  • kParameterBlock2Size:第二个参数块的维度(如果有的话),以此类推。

AutoDiffCostFunction 继承自 CostFunction,它重写了 Evaluate 方法。在 Evaluate 方法中:

  1. 将输入参数指针转换为 Jet 指针,并为每个参数块创建对应的 Jet 数组,初始化导数信息。
  2. 调用代价函子的 operator(),传入 Jet 类型的参数和 Jet 类型的残差数组。
  3. 从残差的 Jet 数组中提取实部(残差值)和虚部(雅可比矩阵元素)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值