一个参数块是相机参数(维度为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. 总结
将相机参数和三维点坐标分开成两个参数块的主要原因是:
- 利用稀疏性:Ceres可以识别问题结构,使用高效算法
- 参数性质不同:相机和点有不同的约束和参数化需求
- 计算效率:Schur消元法等算法可以大幅提升性能
- 内存优化:减少内存使用,改善缓存性能
- 可扩展性:便于处理大规模优化问题
对于大规模捆绑调整问题,这种参数块分离的设计可以带来几个数量级的性能提升,是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 方法中:
- 将输入参数指针转换为
Jet指针,并为每个参数块创建对应的Jet数组,初始化导数信息。 - 调用代价函子的
operator(),传入Jet类型的参数和Jet类型的残差数组。 - 从残差的
Jet数组中提取实部(残差值)和虚部(雅可比矩阵元素)
Ceres中残差与参数块设计解析
230

被折叠的 条评论
为什么被折叠?



