几何尺寸与公差论坛

 找回密码
 注册
查看: 237|回复: 5

求固定圆半径的最小二乘圆

  [复制链接]
发表于 2024-10-21 10:16:44 | 显示全部楼层 |阅读模式
使用 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;
}
 楼主| 发表于 2024-10-21 10:17:34 | 显示全部楼层
继续,使用 Eigen库取代boost库优化上述代码,以显著提升运算速度
 楼主| 发表于 2024-10-21 10:17:46 | 显示全部楼层
#include <iostream>
#include <vector>
#include <cmath>
#include <Eigen/Dense>
#include <unsupported/Eigen/NonLinearOptimization>  // Eigen Levenberg-Marquardt 模块

using namespace std;
using namespace Eigen;

// 定义 2D 点结构
struct Point2D
{
    double x, y;
};

// 定义误差模型,继承 Eigen 的 Functor
struct CircleFitFunctor
{
    const std::vector<Point2D>& points;  // 输入的点
    double radius;  // 固定的圆半径

    CircleFitFunctor(const std::vector<Point2D>& pts, double r)
        : points(pts), radius(r) {}

    // 返回变量维度
    int inputs() const
    {
        return 2;  // 圆心有 2 个变量 (cx, cy)
    }

    // 返回误差维度(数据点的数量)
    int values() const
    {
        return points.size();
    }

    // 实现 functor 运算,计算误差 residuals
    int operator()(const VectorXd& parameters, VectorXd& residuals) const
    {
        double cx = parameters(0);  // 圆心 x
        double cy = parameters(1);  // 圆心 y

        for (int i = 0; i < points.size(); ++i)
        {
            double dx = points[i].x - cx;
            double dy = points[i].y - cy;
            double dist_squared = dx * dx + dy * dy;  // 计算平方距离,避免 sqrt
            residuals(i) = dist_squared - (radius * radius);  // 计算误差平方
        }
        return 0;  // 表示成功
    }
};

// 使用 Eigen::LevenbergMarquardt 进行最小二乘拟合
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 初始值

    // 定义圆拟合模型
    CircleFitFunctor functor(points, fixed_radius);

    // Levenberg-Marquardt 优化器
    Eigen::LevenbergMarquardt<CircleFitFunctor, double> lm(functor);

    // 设定优化的精度
    lm.parameters.maxfev = 50;  // 最大迭代次数
    lm.parameters.xtol = 1.0e-6;  // 误差阈值

    // 进行最小二乘拟合
    int status = lm.minimize(initial_guess);

    if (status <= 0)
    {
        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
    {
        // 使用 Eigen 的 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;
}
 楼主| 发表于 2024-10-21 10:20:45 | 显示全部楼层
优化点:
完全使用 Eigen:

通过 Eigen::LevenbergMarquardt 和 Eigen::VectorXd 实现了所有矩阵和向量运算,避免了对 Boost 的依赖。
平方距离的计算:

与之前一样,使用平方距离代替欧氏距离,避免了 sqrt 的计算开销,显著提高了性能。
减少不必要的计算和并行化:

使用 Eigen 高效的矩阵和向量运算,并通过 Levenberg-Marquardt 的迭代控制减少不必要的计算,显著提升了整体性能。
依赖项:
Eigen 库:

本次代码完全使用 Eigen 库,你可以在编译时确保包含 Eigen 库路径,并确保 unsupported/Eigen/NonLinearOptimization 模块可用。
 楼主| 发表于 2024-10-21 10:24:13 | 显示全部楼层
find_package(Eigen3 3.3 REQUIRED)
target_link_libraries(your_target Eigen3::Eigen)
 楼主| 发表于 2024-10-21 11:38:24 | 显示全部楼层
#include <iostream>
#include <vector>
#include <Eigen/Dense>
#include <unsupported/Eigen/NonLinearOptimization>  // Levenberg-Marquardt 模块
#include <unsupported/Eigen/NumericalDiff>          // 数值微分支持

using namespace std;
using namespace Eigen;

// 定义 2D 点结构(使用 Eigen::Vector2d)
struct Point2D
{
    Vector2d position;  // 使用Eigen的向量

    Point2D(double x = 0.0, double y = 0.0)
    {
        position << x, y;
    }
};

// MathHelper 类包含距离计算函数
class MathHelper
{
public:
    // 计算点到点的距离
    static double GetDistance(const Vector2d& p1, const Vector2d& p2)
    {
        return (p1 - p2).norm();  // 使用 Eigen 的 norm 计算欧氏距离
    }
};

// 定义用于 Levenberg-Marquardt 的 Functor,继承自 DenseFunctor
struct CircleFitFunctor : Eigen::DenseFunctor<double>
{
    const std::vector<Point2D>& points;  // 数据点集
    double fixedRadius;                  // 固定半径

    // 构造函数
    CircleFitFunctor(const std::vector<Point2D>& pts, double r)
        : Eigen::DenseFunctor<double>(2, pts.size()), points(pts), fixedRadius(r) {}

    // 计算误差
    int operator()(const VectorXd& x, VectorXd& fvec) const override
    {
        Vector2d center = x;  // 圆心
        for (int i = 0; i < points.size(); ++i)
        {
            double distance = MathHelper::GetDistance(points[i].position, center);
            fvec(i) = distance - fixedRadius;  // 误差是距离减去固定半径
        }
        return 0;
    }
};

// FitCircleToPointCloud 函数,使用 Levenberg-Marquardt 优化圆心
tuple<Vector2d, float> FitCircleToPointCloud(const vector<Point2D>& points, float fixedRadius)
{
    // 定义拟合函数的 Functor
    CircleFitFunctor functor(points, fixedRadius);

    // 初始猜测:点集质心
    Vector2d initialGuess = Vector2d::Zero();
    for (const auto& pt : points)
    {
        initialGuess += pt.position;
    }
    initialGuess /= points.size();  // 质心作为初始猜测

    // 使用 Levenberg-Marquardt 优化器
    Eigen::LevenbergMarquardt<CircleFitFunctor, double> lm(functor);
    lm.parameters.maxfev = 200;  // 设置最大迭代次数
    int info = lm.minimize(initialGuess);  // 执行优化

    // 返回优化后的圆心和固定半径
    return make_tuple(initialGuess, fixedRadius);
}

int main()
{
    // 定义一些测试点
    vector<Point2D> points = {
        Point2D(1, 1),
        Point2D(2, 2),
        Point2D(3, 3),
        Point2D(4, 5),
        Point2D(6, 5),
        Point2D(7, 4)
    };

    // 固定半径
    float fixedRadius = 3.0f;

    // 调用拟合函数
    auto [center, radius] = FitCircleToPointCloud(points, fixedRadius);

    // 输出结果
    cout << "拟合圆心: (" << center(0) << ", " << center(1) << "), 半径: " << radius << endl;

    return 0;
}
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|Archiver|小黑屋|几何尺寸与公差论坛

GMT+8, 2024-12-21 23:39 , Processed in 0.042013 second(s), 21 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表