【C++】用Ceres从三维点中拟合三维空间中的圆

这篇具有很好参考价值的文章主要介绍了【C++】用Ceres从三维点中拟合三维空间中的圆。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

任务描述

在三维空间中有N个点,需要得到过这N个点的最优圆,需要估计圆的圆心、半径和法向量,本文提供了一种方法和代码示例,利用Ceres进行非线性拟合,在cpp上开发。

圆心为三维,半径一维,法向量三维,总共是7维的拟合参数。三个点确定一个圆,所以需要大于等于3的点数。优化器需要初值,这里由N个点中前三个点解算出的圆心、半径、法向量作为初值。当然前三个点可能带有噪声或者离群点,可以读者可以根据实际需要选择稳定的初始化参考点。
完整代码附在文章末尾

实现过程

定义损失函数结构体

损失函数中需要计算残差,是待拟合点到当前圆最短距离之和,而不是简单的到圆心的距离,且必须考虑圆的法向量。设待拟合点为P,圆心为C,半径为R,残差距离的计算公式为:
【C++】用Ceres从三维点中拟合三维空间中的圆,c++,算法
C A → = n ⃗ × C P → × n ⃗ ∣ n ⃗ × C P → × n ⃗ ∣ ⋅ R   A P → = C P → − C A →   r e s i d u a l = ∣ A P → ∣ \overrightarrow{CA}=\frac{\vec n \times \overrightarrow{CP} \times \vec n}{|\vec n \times \overrightarrow{CP} \times \vec n|} \cdot R \\ \ \\ \overrightarrow{AP} = \overrightarrow{CP} - \overrightarrow{CA} \\ \ \\ residual = |\overrightarrow{AP}| CA =n ×CP ×n n ×CP ×n R AP =CP CA  residual=AP
其中A点是 C P → \overrightarrow{CP} CP 向量在圆平面上的投影与圆的交点,通过叉积运算得到 C A → \overrightarrow{CA} CA 的方向,再归一化后乘以半径得到向量 C A → \overrightarrow{CA} CA


这里的损失函数结构是根据ceres::AutoDiffCostFunction模板的要求设计的,调用的示例如下:

// 构造Cost函数,计算当前点到外接圆的距离
// 尖括号中的参数分别是struct, 输出的维度,第n个参数块的维度
ceres::CostFunction *cost_function = 
			 newceres::AutoDiffCostFunction<CircleFittingCost, 1, 3, 3, 1>(
			     new CircleFittingCost(points[i]));

所以需要设计一个结构体CircleFittingCost作为损失函数

// 定义计算点到外接圆的距离的Cost函数
struct CircleFittingCost
{
    // 构造函数,传入当前点的坐标
    CircleFittingCost(const Eigen::Vector3d &_point) : m_point(_point) {}

    // 模板函数,计算当前点到外接圆的距离
    template <typename T>
    bool operator()(const T *const center, const T *const norm_vector,
                    const T *const radius, T *residual) const
    {
        Eigen::Matrix<T, 3, 1> C_P(m_point.x() - center[0],
                                   m_point.y() - center[1],
                                   m_point.z() - center[2]);
        Eigen::Matrix<T, 3, 1> cross_result1;
        cross_result1(0) = norm_vector[1] * C_P(2) - norm_vector[2] * C_P(1);
        cross_result1(1) = norm_vector[2] * C_P(0) - norm_vector[0] * C_P(2);
        cross_result1(2) = norm_vector[0] * C_P(1) - norm_vector[1] * C_P(0);
        Eigen::Matrix<T, 3, 1> cross_result2;
        cross_result2(0) = cross_result1(1) * norm_vector[2] - cross_result1(2) * norm_vector[1];
        cross_result2(1) = cross_result1(2) * norm_vector[0] - cross_result1(0) * norm_vector[2];
        cross_result2(2) = cross_result1(0) * norm_vector[1] - cross_result1(1) * norm_vector[0];
        Eigen::Matrix<T, 3, 1> C_A = cross_result2.normalized() *
                                     *radius;

        // Eigen::Vector3d C_A = norm_vector.cross(C_P)
        //                           .cross(norm_vector)
        //                           .normalized() *
        //                       *radius;
        residual[0] = (C_P - C_A).norm();
        // residual[0] = *radius;

        return true;
    }
    Eigen::Vector3d m_point; // 当前点的坐标
};

C点是圆心,P点是传入的待拟合三维点。这里要注意, problem.AddResidualBlock()函数的后面几个传给CircleFittingCost的参数(圆心半径法向量那些)只接受double[]数组和double指针

double C_data[] = {C.x(), C.y(), C.z()};
double norm_vector_data[] = {norm_vector.x(), norm_vector.y(), norm_vector.z()};
 // 构造Cost函数,计算当前点到外接圆的距离
ceres::CostFunction *cost_function = // 尖括号中的参数分别是struct, 输出的维度,第n个参数块的维度
      new ceres::AutoDiffCostFunction<CircleFittingCost, 1, 3, 3, 1>(
          new CircleFittingCost(points[i]));
  // 将Cost函数添加到优化问题中
  problem.AddResidualBlock(cost_function, NULL, C_data, norm_vector_data, &radius);

而传入损失函数结构体CircleFittingCost之后需要都转换成Eigen::Matrix,Ceres才能进行自动求导运算,所以有了下面这些定义,同时Eigen::Matrix不支持叉积运算,所以只能手动计算叉积。Eigen::Vector3d可以进行叉积运算,但是不满足Ceres自动求导的数据结构要求:

// 这段代码在上面出现过,只是复制下来解释

// 定义向量CP,由圆心C指向待拟合点P
Eigen::Matrix<T, 3, 1> C_P(m_point.x() - center[0],
                                   m_point.y() - center[1],
                                   m_point.z() - center[2]);
Eigen::Matrix<T, 3, 1> cross_result1;
cross_result1(0) = norm_vector[1] * C_P(2) - norm_vector[2] * C_P(1);
cross_result1(1) = norm_vector[2] * C_P(0) - norm_vector[0] * C_P(2);
cross_result1(2) = norm_vector[0] * C_P(1) - norm_vector[1] * C_P(0);
Eigen::Matrix<T, 3, 1> cross_result2;
cross_result2(0) = cross_result1(1) * norm_vector[2] - cross_result1(2) * norm_vector[1];
cross_result2(1) = cross_result1(2) * norm_vector[0] - cross_result1(0) * norm_vector[2];
cross_result2(2) = cross_result1(0) * norm_vector[1] - cross_result1(1) * norm_vector[0];
Eigen::Matrix<T, 3, 1> C_A = cross_result2.normalized() * *radius;

计算初始值

取前三个点直接计算初始的圆心、半径、法向量

Eigen::Vector3d v1 = points[1] - points[0];
Eigen::Vector3d v2 = points[2] - points[0];
Eigen::Vector3d v3 = points[2] - points[1];

double sin_A = v1.cross(v2).norm() / (v1.norm() * v2.norm());
double cos_A = v1.dot(v2) / (v1.norm() * v2.norm());
double sin_2A = 2 * sin_A * cos_A;

double sin_B = v1.cross(v3).norm() / (v1.norm() * v3.norm());
double cos_B = -v1.dot(v3) / (v1.norm() * v3.norm());
double sin_2B = 2 * sin_B * cos_B;

double sin_C = v2.cross(v3).norm() / (v2.norm() * v3.norm());
double cos_C = v2.dot(v3) / (v2.norm() * v3.norm());
double sin_2C = 2 * sin_C * cos_C;

Eigen::Vector3d AC = cos_B / (2 * sin_A * sin_C) * v1 + cos_C / (2 * sin_A * sin_B) * v2; // W为圆心点
C = points[0] + AC;
radius = AC.norm();
norm_vector = v1.cross(v2).normalized();

调用优化器进行非线性优化

优化的流程是:文章来源地址https://www.toymoban.com/news/detail-530559.html

  1. 定义优化问题
  2. 把所有的点构建成cost_functionAddResidualBlock()添加到优化问题中
  3. 添加优化器设置并调用Solve()函数进行优化
  4. 取出数据
ceres::Problem problem; // 定义优化问题
// 添加观测,即待优化点points
for (size_t i = 0; i < points.size(); ++i)
{
    // 构造Cost函数,计算当前点到外接圆的距离
    ceres::CostFunction *cost_function = // 尖括号中的参数分别是struct, 输出的维度,第n个参数块的维度
        new ceres::AutoDiffCostFunction<CircleFittingCost, 1, 3, 3, 1>(
            new CircleFittingCost(points[i]));
    // 将Cost函数添加到优化问题中
    problem.AddResidualBlock(cost_function, NULL, C_data, norm_vector_data, &radius);
}

ceres::Solver::Options options;               // 定义优化器的配置项
options.max_num_iterations = 100;             // 最大迭代次数
options.linear_solver_type = ceres::DENSE_QR; // 线性求解器类型
options.minimizer_progress_to_stdout = true;  // 输出优化过程到终端

ceres::Solver::Summary summary;            	  // 定义优化结果的汇总信息
ceres::Solve(options, &problem, &summary);    // 使用LM算法求解优化问题

// 取出数据,传入优化器的都是double数组和指针,转变成需要的数据
 center.x() = C_data[0];
 center.y() = C_data[1];
 center.z() = C_data[2];
 std::cout << "center_x:  " << center.x() << std::endl;
 std::cout << "center_y:  " << center.y() << std::endl;
 std::cout << "center_z:  " << center.z() << std::endl;
 std::cout << "radius:  " << radius << std::endl;
 norm_vector.x() = norm_vector_data[0];
 norm_vector.y() = norm_vector_data[1];
 norm_vector.z() = norm_vector_data[2];

完整代码

/**
 * @description: 求解外接圆的曲率半径、法向量和圆心位置
 * @param {vector<Eigen::Vector3d>} &points 采样点的坐标
 * @param {Vector3d} &center 拟合圆心
 * @param {double} &radius 拟合圆半径
 * @param {Vector3d} &norm_vector 拟合圆法向量
 * @return {*}
 */
bool fitCircle(const std::vector<Eigen::Vector3d> &points, Eigen::Vector3d &center,
               double &radius, Eigen::Vector3d &norm_vector)
{
    // 初始化圆心和曲率半径
    Eigen::Vector3d C; // 圆心坐标向量
    if (points.size() < 3)
    {
        std::cout << "采样点的数量小于3,计算失败!!" << std::endl;
        return false;
    }
    else
    {
        // 如果采样点数大于3,用前三个点初始化圆法向量、圆心和半径
        // 1.计算法向量
        Eigen::Vector3d v1 = points[1] - points[0];
        Eigen::Vector3d v2 = points[2] - points[0];
        Eigen::Vector3d v3 = points[2] - points[1];

        double sin_A = v1.cross(v2).norm() / (v1.norm() * v2.norm());
        double cos_A = v1.dot(v2) / (v1.norm() * v2.norm());
        double sin_2A = 2 * sin_A * cos_A;

        double sin_B = v1.cross(v3).norm() / (v1.norm() * v3.norm());
        double cos_B = -v1.dot(v3) / (v1.norm() * v3.norm());
        double sin_2B = 2 * sin_B * cos_B;

        double sin_C = v2.cross(v3).norm() / (v2.norm() * v3.norm());
        double cos_C = v2.dot(v3) / (v2.norm() * v3.norm());
        double sin_2C = 2 * sin_C * cos_C;

        Eigen::Vector3d AC = cos_B / (2 * sin_A * sin_C) * v1 + cos_C / (2 * sin_A * sin_B) * v2; // W为圆心点
        C = points[0] + AC;
        radius = AC.norm();
        norm_vector = v1.cross(v2).normalized();
    }
    double C_data[] = {C.x(), C.y(), C.z()};
    double norm_vector_data[] = {norm_vector.x(), norm_vector.y(), norm_vector.z()};

    ceres::Problem problem; // 定义优化问题
    for (size_t i = 0; i < points.size(); ++i)
    {
        // 构造Cost函数,计算当前点到外接圆的距离
        ceres::CostFunction *cost_function = // 尖括号中的参数分别是struct, 输出的维度,第n个参数块的维度
            new ceres::AutoDiffCostFunction<CircleFittingCost, 1, 3, 3, 1>(
                new CircleFittingCost(points[i]));
        // 将Cost函数添加到优化问题中
        problem.AddResidualBlock(cost_function, NULL, C_data, norm_vector_data, &radius);
    }

    ceres::Solver::Options options;               // 定义优化器的配置项
    options.max_num_iterations = 100;             // 最大迭代次数
    options.linear_solver_type = ceres::DENSE_QR; // 线性求解器类型
    options.minimizer_progress_to_stdout = true;  // 输出优化过程到终端

    ceres::Solver::Summary summary;            // 定义优化结果的汇总信息
    ceres::Solve(options, &problem, &summary); // 使用LM算法求解优化问题

    // 更新圆心和法向量,半径直接用传入的radius进行优化就不用更新了
    center.x() = C_data[0];
    center.y() = C_data[1];
    center.z() = C_data[2];
    std::cout << "center_x:  " << center.x() << std::endl;
    std::cout << "center_y:  " << center.y() << std::endl;
    std::cout << "center_z:  " << center.z() << std::endl;
    std::cout << "radius:  " << radius << std::endl;
    norm_vector.x() = norm_vector_data[0];
    norm_vector.y() = norm_vector_data[1];
    norm_vector.z() = norm_vector_data[2];
    return true;
}

// 定义计算点到外接圆的距离的Cost函数
struct CircleFittingCost
{
    // 构造函数,传入当前点的坐标
    CircleFittingCost(const Eigen::Vector3d &_point) : m_point(_point) {}

    // 模板函数,计算当前点到外接圆的距离
    template <typename T>
    bool operator()(const T *const center, const T *const norm_vector,
                    const T *const radius, T *residual) const
    {
        Eigen::Matrix<T, 3, 1> C_P(m_point.x() - center[0],
                                   m_point.y() - center[1],
                                   m_point.z() - center[2]);
        Eigen::Matrix<T, 3, 1> cross_result1;
        cross_result1(0) = norm_vector[1] * C_P(2) - norm_vector[2] * C_P(1);
        cross_result1(1) = norm_vector[2] * C_P(0) - norm_vector[0] * C_P(2);
        cross_result1(2) = norm_vector[0] * C_P(1) - norm_vector[1] * C_P(0);
        Eigen::Matrix<T, 3, 1> cross_result2;
        cross_result2(0) = cross_result1(1) * norm_vector[2] - cross_result1(2) * norm_vector[1];
        cross_result2(1) = cross_result1(2) * norm_vector[0] - cross_result1(0) * norm_vector[2];
        cross_result2(2) = cross_result1(0) * norm_vector[1] - cross_result1(1) * norm_vector[0];
        Eigen::Matrix<T, 3, 1> C_A = cross_result2.normalized() *
                                     *radius;

        // Eigen::Vector3d C_A = norm_vector.cross(C_P)
        //                           .cross(norm_vector)
        //                           .normalized() *
        //                       *radius;
        residual[0] = (C_P - C_A).norm();
        // residual[0] = *radius;

        return true;
    }
    Eigen::Vector3d m_point; // 当前点的坐标
};

到了这里,关于【C++】用Ceres从三维点中拟合三维空间中的圆的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【必备知识】 三维空间/坐标转换/相机知识

    以下内容包含了2D坐标与3D坐标系之间的转换以及关于相机场的基础知识,理解这部分内容可以更快入门SLAM相关、多视角合成、三维空间变换等内容。 照相机制造过程中的一些涉及到透镜精密以及组装工艺等原因需要对图像进行相应的矫正。如下所示: 需要建立世界坐标系到

    2024年02月03日
    浏览(46)
  • 三维空间刚体运动之旋转矩阵与变换矩阵

    点: 点是空间中的基本元素,没有长度,没有体积; 向量: 把两个点连接起来,就构成了向量,向量可以看成从某点指向另一点的一个箭头;只有当我们指定这个三维空间中的某个坐标系时,才可以谈论该向量在此坐标系下的坐标;默认向量就是列向量; 坐标系: 三根不

    2024年02月11日
    浏览(60)
  • PCL 判断两条线段的平行性(三维空间)

    这里使用一种比较有趣的方式来判断三维空间中两条线段的平行性,我们都知道两条线段所代表的矢量进行叉乘计算所得数值,代表了由这两条线段组成的平行四边形的面积值,如下图所示: ok,那么如果将此结论推广到三维空间呢?可以得到下面的形式: 其中, i ⃗

    2024年02月10日
    浏览(43)
  • 视觉SLAM14讲笔记-第3讲-三维空间刚体运动

    空间向量之间的运算包括: 数乘、加法、减法、内积、外积。 内积 :可以描述向量间的投影关系,结果是一个标量。 a ⃗ ⋅ b ⃗ = ∑ i = 1 3 a i b i = ∤ a ∤ ∤ b ∤ c o s ⟨ a , b ⟩ vec{a} cdot vec{b}=sum_{i=1}^3{{a _i}{b_i}} =nmid a nmid nmid b nmid cos langle a,b rangle a ⋅ b = i = 1 ∑ 3 ​

    2024年02月11日
    浏览(48)
  • 【数理知识】求两个三维空间点的坐标矩阵之间,任意两两点之间的空间距离,matlab 实现

    假设有两个包含了三维空间点坐标的,三维向量集 A A A 和 B B B ,两集合中分别有 m m m 个和 n n n 个三维空间坐标点,可以用矩阵表示为 A = [ a 1 x a 2 x a 3 x ⋯ a m x a 1 y a 2 y a 3 y ⋯ a m y a 1 z a 2 z a 3 z ⋯ a m z ] 3 × m , B = [ b 1 x b 2 x b 3 x ⋯ b n x b 1 y b 2 y b 3 y ⋯ b n y b 1 z b 2 z b 3 z ⋯

    2024年02月11日
    浏览(51)
  • 双目视觉离线测量空间三维坐标带详细注释

    直接上代码: 代码中的示例图片和参数详见链接。

    2024年02月11日
    浏览(39)
  • 【线性代数-3Blue1Brown】- 5 三维空间的线性变换

    飞书原文档:Docs  

    2024年02月11日
    浏览(40)
  • ArcGIS Pro实践技术应用、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合

    GIS是利用电子计算机及其外部设备,采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲,它是在一定的地域内,将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来,达到对地理和属性信息的综合管理。GIS的研究对象是整个地理空间,而地

    2024年02月09日
    浏览(49)
  • 三维重建_体素重建_空间雕刻法/体素着色法

    目录 1. 三角化和体素重建的区别 2. 空间雕刻法  空间雕刻法的一致性定义  空间雕刻法具体实现  基于八叉树的空间雕刻法具体实现​编辑  空间雕刻法效果展示  3. 体素着色法  体素着色法的缺点:不唯一性​编辑 体素着色法不唯一性解决措施​编辑  体素着色发实验

    2024年02月11日
    浏览(76)
  • 空间三维模型的编码结构光方法实现:基于EinScan-S软件

      本文介绍基于 EinScan-S 软件,实现 编码结构光 方法的空间三维模型重建的具体操作。 目录 1 相关原理 1.1 编码结构光成像原理 1.2 编码结构光编码方式 1.3 编码结构光与侧影轮廓方法比较 1.4 编码结构光方法流程 2 三维模型制作 2.1 防晒霜罐三维模型制作 2.1.1 前期准备工作

    2024年02月05日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包