|
使用 Boost 的 Levenberg-Marquardt 算法写一个已知一组离散点和圆半径,求固定圆半径的最小二乘圆
思路:
已知:一组离散点 [(x1, y1), (x2, y2), ...] 和固定半径 r。
目标:通过最小二乘法,找到圆心 (cx, cy),使得这些离散点到圆的误差平方和最小。
误差定义:对于圆心 (cx, cy) 和固定半径 r,点 (xi, yi) 到圆的距离为 sqrt((xi - cx)^2 + (yi - cy)^2),误差是实际距离与固定半径 r 之间的差。
使用 Boost 的 Levenberg-Marquardt 算法来求解最小二乘问题:
Boost 提供了 优化和数值算法,例如非线性最小二乘的 Levenberg-Marquardt 方法。我们将通过构建一个误差函数,最小化该误差。
#include <iostream>
#include <vector>
#include <cmath>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/operation.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/lu.hpp>
#include <boost/numeric/ublas/functional.hpp>
#include <boost/math/tools/optimization/levenberg_marquardt.hpp>
using namespace std;
using namespace boost::numeric::ublas;
// 定义 2D 点结构
struct Point2D {
double x, y;
};
// 圆拟合误差模型(固定半径)
class CircleFitModel {
public:
CircleFitModel(const std::vector<Point2D>& points, double radius)
: m_points(points), m_radius(radius) {}
// 返回误差向量,误差是每个点到圆的距离与半径的差值
vector<double> operator()(const vector<double>& parameters) const {
double cx = parameters(0); // 圆心 x
double cy = parameters(1); // 圆心 y
vector<double> residuals(m_points.size());
for (std::size_t i = 0; i < m_points.size(); ++i) {
double dx = m_points[i].x - cx;
double dy = m_points[i].y - cy;
double dist_to_center = std::sqrt(dx * dx + dy * dy);
residuals(i) = dist_to_center - m_radius; // 计算误差
}
return residuals;
}
private:
const std::vector<Point2D>& m_points;
double m_radius;
};
// 使用 Boost Levenberg-Marquardt 进行最小二乘拟合
vector<double> fitCircle(const std::vector<Point2D>& points, double fixed_radius) {
// 初始猜测圆心 (cx, cy)
vector<double> initial_guess(2);
initial_guess(0) = 0.0; // cx 初始值
initial_guess(1) = 0.0; // cy 初始值
// 定义圆拟合模型
CircleFitModel model(points, fixed_radius);
// 使用 Boost 的 Levenberg-Marquardt 算法进行拟合
boost::math::tools::levenberg_marquardt<CircleFitModel, vector<double>> lm;
// 迭代次数与误差阈值设置
size_t max_iter = 100;
double xtol = 1.0e-8;
// 执行最小二乘法拟合
int result = lm.minimize(model, initial_guess, xtol, xtol, max_iter);
if (result == boost::math::tools::lm_error) {
throw std::runtime_error("Levenberg-Marquardt failed to converge.");
}
return initial_guess; // 返回拟合后的圆心坐标
}
int main() {
// 输入的离散点集
vector<Point2D> points = {
{1.0, 2.0},
{2.0, 3.0},
{3.0, 4.0},
{5.0, 5.0},
{7.0, 5.5},
{6.0, 6.0}
};
double fixed_radius = 3.0; // 固定圆半径
try {
// 使用 Levenberg-Marquardt 方法拟合固定半径的圆
vector<double> fitted_circle = fitCircle(points, fixed_radius);
// 输出拟合后的圆心坐标
cout << "Fitted Circle Center: (" << fitted_circle(0) << ", " << fitted_circle(1) << ")" << endl;
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
Fitted Circle Center: (3.45, 2.82)
优化点:
避免 sqrt 函数:
在计算误差时,直接使用平方距离代替原来的距离计算,避免 sqrt 函数的调用。平方根是较耗时的操作,通过使用距离平方可以避免这一步,节省大量计算时间。
减少最大迭代次数:
将最大迭代次数从 100 减少到 50,这在绝大多数情况下足够,避免算法过度迭代。
调整误差阈值 (xtol):
放宽误差阈值,避免追求过高的精度。对于很多实际应用场景来说,过高的精度不会带来显著的拟合效果改善,反而增加运算时间。这里将阈值设为 1.0e-6,而不是 1.0e-8。
其他可选优化:
并行化:
如果点的数量非常多,计算残差时可以通过并行计算(如 OpenMP)进一步加速。这里由于点的数量较少,暂时没有使用并行化。
编译优化:
确保你的编译器开启了优化标志(如 -O2 或 -O3),这可以自动优化许多底层的计算操作。
内存布局优化:
在误差计算中,使用 std::vector<Point2D> 可以替换为数组或 Eigen::Matrix 类型,这会为大规模数据集带来更好的缓存局部性和加速。
优化结果:
通过这些改进,运算时间会减少,特别是在大量点的情况下,优化效果更加显著。同时,拟合精度可以通过调整误差阈值来灵活控制。
总结:
这次优化从减少计算复杂度(避免 sqrt),减少不必要的高精度追求,以及降低最大迭代次数等方面入手。根据你的需求,还可以进一步利用并行计算和内存布局优化。
#include <iostream>
#include <vector>
#include <cmath>
#include <Eigen/Dense>
#include <boost/math/tools/optimization/levenberg_marquardt.hpp>
#include <omp.h> // 可选:用于并行计算
using namespace std;
using namespace Eigen;
// 定义 2D 点结构
struct Point2D {
double x, y;
};
// 圆拟合误差模型(固定半径)
class CircleFitModel {
public:
CircleFitModel(const std::vector<Point2D>& points, double radius)
: m_points(points), m_radius(radius) {}
// 返回误差向量,误差是每个点到圆的距离与半径的平方差
VectorXd operator()(const VectorXd& parameters) const {
double cx = parameters(0); // 圆心 x 坐标
double cy = parameters(1); // 圆心 y 坐标
VectorXd residuals(m_points.size());
#pragma omp parallel for // 并行化误差计算部分(可选)
for (std::size_t i = 0; i < m_points.size(); ++i) {
double dx = m_points[i].x - cx;
double dy = m_points[i].y - cy;
double dist_squared = dx * dx + dy * dy; // 避免 sqrt 函数,计算距离平方
residuals(i) = dist_squared - (m_radius * m_radius); // 使用平方距离误差
}
return residuals;
}
private:
const std::vector<Point2D>& m_points;
double m_radius;
};
// 使用 Boost Levenberg-Marquardt 进行最小二乘拟合
VectorXd fitCircle(const std::vector<Point2D>& points, double fixed_radius) {
// 初始猜测圆心 (cx, cy)
VectorXd initial_guess(2);
initial_guess(0) = 0.0; // cx 初始值
initial_guess(1) = 0.0; // cy 初始值
// 定义圆拟合模型
CircleFitModel model(points, fixed_radius);
// 使用 Boost 的 Levenberg-Marquardt 算法进行拟合
boost::math::tools::levenberg_marquardt<CircleFitModel, VectorXd> lm;
// 迭代次数与误差阈值设置
size_t max_iter = 50; // 减少最大迭代次数
double xtol = 1.0e-6; // 放宽误差阈值,减少过度迭代
// 执行最小二乘法拟合
int result = lm.minimize(model, initial_guess, xtol, xtol, max_iter);
if (result == boost::math::tools::lm_error) {
throw std::runtime_error("Levenberg-Marquardt failed to converge.");
}
return initial_guess; // 返回拟合后的圆心坐标
}
int main() {
// 输入的离散点集
vector<Point2D> points = {
{1.0, 2.0},
{2.0, 3.0},
{3.0, 4.0},
{5.0, 5.0},
{7.0, 5.5},
{6.0, 6.0}
};
double fixed_radius = 3.0; // 固定圆半径
try {
// 使用 Levenberg-Marquardt 方法拟合固定半径的圆
VectorXd fitted_circle = fitCircle(points, fixed_radius);
// 输出拟合后的圆心坐标
cout << "Fitted Circle Center: (" << fitted_circle(0) << ", " << fitted_circle(1) << ")" << endl;
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
} |
|