共计 3073 个字符,预计需要花费 8 分钟才能阅读完成。
引言
在解决非线性优化问题时,选择合适的损失函数对 Ceres Solver 的性能和收敛性至关重要。特别是在 SLAM、Bundle Adjustment 等任务中,噪声和异常值的存在使得鲁棒核函数(Robust Kernel)的选择变得尤为重要。本文将深入探讨 Ceres 内置损失函数的数学原理,对比不同函数的适用场景,并通过实际代码示例展示如何在工程实践中应用这些函数来提升优化效果。

数学背景:鲁棒核函数的作用
鲁棒核函数的主要作用是抑制异常值对优化结果的影响。在最小二乘问题中,传统的平方损失函数对异常值非常敏感,因为其导数会随着残差的增大而线性增长。鲁棒核函数通过引入非线性变换来减小大残差对整体损失函数的贡献。
数学上,鲁棒核函数可以表示为:
$$
\rho(s) = \rho\left(|f(x)|^2\right)
$$
其中,(f(x) ) 是残差函数,(\rho) 是鲁棒核函数。核函数的选择直接影响优化问题的凸性和收敛速度。
常见损失函数对比
Ceres Solver 提供了多种内置的鲁棒核函数,以下是几种常用的函数及其特性:
| 损失函数 | 数学形式 | 适用场景 |
|---|---|---|
| HuberLoss | (\rho(s) = \begin{cases} s & \text{if} s \leq 1 \ 2\sqrt{s} – 1 & \text{otherwise} \end{cases} ) | 适用于中等强度的异常值 |
| CauchyLoss | (\rho(s) = \log(1 + s) ) | 对极端异常值有较强的鲁棒性 |
| SoftLOneLoss | (\rho(s) = 2(\sqrt{1 + s} – 1) ) | 适用于重尾分布噪声 |
代码实战:Bundle Adjustment 中的损失函数应用
以下是一个完整的 Ceres Problem 构建示例,展示了如何在 Bundle Adjustment 中使用不同的损失函数来提升特征点匹配的鲁棒性。
#include <ceres/ceres.h>
#include <gflags/gflags.h>
#include <Eigen/Dense>
DEFINE_string(loss_function, "huber", "Type of loss function (huber, cauchy, softlone)");
DEFINE_double(loss_scale, 1.0, "Scale parameter for the loss function");
struct BundleAdjustmentCostFunctor {BundleAdjustmentCostFunctor(const Eigen::Vector2d& observed_point)
: observed_point_(observed_point) {}
template <typename T>
bool operator()(const T* const camera, const T* const point, T* residuals) const {
// Project 3D point to 2D using camera parameters
// ... (implementation omitted for brevity)
return true;
}
static ceres::CostFunction* Create(const Eigen::Vector2d& observed_point) {
return new ceres::AutoDiffCostFunction<BundleAdjustmentCostFunctor, 2, 9, 3>(new BundleAdjustmentCostFunctor(observed_point));
}
Eigen::Vector2d observed_point_;
};
int main(int argc, char** argv) {google::ParseCommandLineFlags(&argc, &argv, true);
ceres::Problem problem;
ceres::LossFunction* loss_function = nullptr;
if (FLAGS_loss_function == "huber") {loss_function = new ceres::HuberLoss(FLAGS_loss_scale);
} else if (FLAGS_loss_function == "cauchy") {loss_function = new ceres::CauchyLoss(FLAGS_loss_scale);
} else if (FLAGS_loss_function == "softlone") {loss_function = new ceres::SoftLOneLoss(FLAGS_loss_scale);
}
// Add residual blocks to the problem
// ... (implementation omitted for brevity)
ceres::Solver::Options options;
options.minimizer_progress_to_stdout = true;
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
std::cout << summary.FullReport() << std::endl;
return 0;
}
性能调优与避坑指南
缩放因子的选择
缩放因子(scale parameter)是鲁棒核函数中的一个重要参数,它决定了函数对异常值的敏感程度。设置不当会导致数值不稳定或优化效果不佳。一般来说,可以通过以下方式选择合适的缩放因子:
- 对于 HuberLoss,缩放因子通常设置为测量噪声的标准差。
- 对于 CauchyLoss,缩放因子可以稍大一些,以增强对极端异常值的鲁棒性。
AutoDiff 与 NumericDiff 的选择
Ceres Solver 提供了自动微分(AutoDiff)和数值微分(NumericDiff)两种方式来计算雅可比矩阵。推荐使用 AutoDiff,因为它的计算精度更高,性能也更好。但在以下情况下,可能需要使用 NumericDiff:
- 残差函数的实现无法模板化。
- 残差函数的计算涉及复杂的控制流(如循环、条件语句)。
延伸思考:自定义损失函数与 LocalParameterization
Ceres Solver 允许用户自定义损失函数,这为特定场景下的优化问题提供了极大的灵活性。例如,可以将自定义损失函数与 LocalParameterization 结合使用,以处理具有特殊约束的参数空间。以下是一个简单的自定义损失函数示例:
class CustomLossFunction : public ceres::LossFunction {
public:
virtual void Evaluate(double s, double out[3]) const {out[0] = s / (1.0 + s);
out[1] = 1.0 / ((1.0 + s) * (1.0 + s));
out[2] = -2.0 / ((1.0 + s) * (1.0 + s) * (1.0 + s));
}
};
总结
选择合适的损失函数是使用 Ceres Solver 解决非线性优化问题的关键步骤。本文通过数学原理、对比分析、代码示例和性能调优建议,全面介绍了 Ceres 内置损失函数的使用方法。希望读者能够根据实际问题的需求,灵活选择和应用这些函数,从而提升优化效果和鲁棒性。
对于更复杂的场景,可以尝试结合自定义损失函数和 LocalParameterization,进一步扩展 Ceres Solver 的应用范围。
