激光雷达定位与建图-拟合问题

ops/2024/11/30 9:12:11/

本篇文章介绍如何在点云中提取线段和平面。

一、平面拟合

1. 问题提出

给定一组由N个点组成的点云 X = { x 1 , ⋯ , x n } X =\left \{x_{1}, \cdots , x_{n} \right \} X={x1,,xn} ,其中每个点取三维欧式坐标 x k x_{k} xk,寻找一组平面参数n,d,使得:
n T x k + d = 0 n^{T}x_{k} + d = 0 nTxk+d=0
其中:n为法向量,d为截距。
在给出的一组点云数据,满足上述平面方程,且误差最小,于是可直接用最小二乘求解。
误差最小化:

在这里插入图片描述
使用齐次坐标,将上试简化:
在这里插入图片描述
其中: x ~ = [ x T 1 ] \tilde{x} = \begin{bmatrix} x^T & 1 \end{bmatrix} x~=[xT1], n ~ = [ n T d ] T \tilde{n} = \begin{bmatrix} n^T & d \end{bmatrix}^{T} n~=[nTd]T
x ~ k \tilde{x}_{k} x~k看做矩阵A,进行SVD分解找到最小奇异值对应的右奇异向量即是方程的解。

2. 实现

/*** @brief 测试平面拟合功能* * 该函数生成带有噪声的平面点,然后使用FitPlane函数进行平面拟合,* 最后比较估计的平面参数与真实参数。*/
void PlaneFittingTest() {// 定义真实平面参数Vec4d true_plane_coeffs(0.1, 0.2, 0.3, 0.4);true_plane_coeffs.normalize();std::vector<Vec3d> points;// 随机生成仿真平面点cv::RNG rng;for (int i = 0; i < FLAGS_num_tested_points_plane; ++i) {// 生成随机点并投影到平面上Vec3d p(rng.uniform(0.0, 1.0), rng.uniform(0.0, 1.0), rng.uniform(0.0, 1.0));double n4 = -p.dot(true_plane_coeffs.head<3>()) / true_plane_coeffs[3];p = p / (n4 + std::numeric_limits<double>::min());  // 防止除零// 添加高斯噪声p += Vec3d(rng.gaussian(FLAGS_noise_sigma), rng.gaussian(FLAGS_noise_sigma), rng.gaussian(FLAGS_noise_sigma));points.emplace_back(p);// 验证点是否在平面上(考虑到噪声,结果应接近但不完全等于0)LOG(INFO) << "res of p: " << p.dot(true_plane_coeffs.head<3>()) + true_plane_coeffs[3];}// 使用FitPlane函数进行平面拟合Vec4d estimated_plane_coeffs;if (sad::math::FitPlane(points, estimated_plane_coeffs)) {// 拟合成功,输出估计的平面参数和真实参数LOG(INFO) << "estimated coeffs: " << estimated_plane_coeffs.transpose()<< ", true: " << true_plane_coeffs.transpose();} else {// 拟合失败LOG(INFO) << "plane fitting failed";}
}/*** @brief 拟合平面* * @tparam S 数据类型(通常为float或double)* @param data 输入的3D点集* @param plane_coeffs 输出的平面方程系数 (ax + by + cz + d = 0)* @param eps 拟合误差阈值* @return bool 拟合是否成功*/
template <typename S>
bool FitPlane(std::vector<Eigen::Matrix<S, 3, 1>>& data, Eigen::Matrix<S, 4, 1>& plane_coeffs, double eps = 1e-2) {// 确保至少有3个点才能拟合平面if (data.size() < 3) {return false;}// 构建系数矩阵AEigen::MatrixXd A(data.size(), 4);for (int i = 0; i < data.size(); ++i) {A.row(i).head<3>() = data[i].transpose();A.row(i)[3] = 1.0;}// 使用SVD求解最小二乘问题Eigen::JacobiSVD svd(A, Eigen::ComputeThinV);plane_coeffs = svd.matrixV().col(3);// 检查拟合误差是否在阈值范围内for (int i = 0; i < data.size(); ++i) {double err = plane_coeffs.template head<3>().dot(data[i]) + plane_coeffs[3];if (err * err > eps) {return false;}}return true;
}

二、线性拟合

1. 问题提出

给定一组由N个点组成的点云 X = { x 1 , ⋯ , x n } X =\left \{x_{1}, \cdots , x_{n} \right \} X={x1,,xn} ,其中每个点取三维欧式坐标 x k x_{k} xk,寻找一组直线参数d,p使得:
x = d t + p x = dt +p x=dt+p
其中:d为直线方向,满足\left | d \right | = 1,p为直线上一点;t为直线参数。
构造最小二乘,对于任意一个不在直线上的点 x k x_{k} xk,利用勾股定理,计算与直线的垂直距离的平方,如下:
在这里插入图片描述
然后构造最小二乘问题,求解d 和 p。
在这里插入图片描述
继续:
在这里插入图片描述
故p可以取点云的中心。于是可以先确定p,再求d。
y k = x k − p y_{k} = x_{k} - p yk=xkp,对上述构造的最小二乘进行化简,如下:
在这里插入图片描述
由于第一项不包含d,而求第二项最小值,只需取 d T y k y k T d d^{T}y_{k}y^{T}_{k}d dTykykTd的最大值即可。
y k T y^{T}_{k} ykT看做矩阵A,进行SVD分解找到最大奇异值对应的右奇异向量即是方程的解。

2. 实现

/*** @brief 拟合三维空间中的直线* * @tparam S 数据类型(通常为float或double)* @param data 输入的3D点集* @param origin 输出的直线上的一点(通常为点集的质心)* @param dir 输出的直线方向向量* @param eps 拟合误差阈值* @return bool 拟合是否成功*/
template <typename S>
bool FitLine(std::vector<Eigen::Matrix<S, 3, 1>>& data, Eigen::Matrix<S, 3, 1>& origin, Eigen::Matrix<S, 3, 1>& dir,double eps = 0.2) {// 确保至少有2个点才能拟合直线if (data.size() < 2) {return false;}// 计算点集的质心作为直线上的一点origin = std::accumulate(data.begin(), data.end(), Eigen::Matrix<S, 3, 1>::Zero().eval()) / data.size();// 构建矩阵Y,每行是点到质心的向量Eigen::MatrixXd Y(data.size(), 3);for (int i = 0; i < data.size(); ++i) {Y.row(i) = (data[i] - origin).transpose();}// 使用SVD求解主方向Eigen::JacobiSVD svd(Y, Eigen::ComputeFullV);dir = svd.matrixV().col(0);// 检查每个点到拟合直线的距离是否在阈值范围内for (const auto& d : data) {if (dir.template cross(d - origin).template squaredNorm() > eps) {return false;}}return true;
}

三、最小二乘的解法

SVD_167">1. 线性最小二乘SVD

SVD相关介绍请看参考文献1
在这里插入图片描述

2.非线性最小二乘

(1)迭代最小二乘法

在这里插入图片描述

(2)优化方法
a. 高斯牛顿法

在这里插入图片描述

b. LM

在这里插入图片描述

四、参考

1.https://blog.csdn.net/qq_34213260/article/details/115345986
2.<<自动驾驶与机器人中的SLAM技术从理论到实践>>


http://www.ppmy.cn/ops/137887.html

相关文章

人工智能如何改变你的生活?

在我们所处的这个快节奏的世界里&#xff0c;科技融入日常生活已然成为司空见惯的事&#xff0c;并且切实成为了我们生活的一部分。在这场科技变革中&#xff0c;最具变革性的角色之一便是人工智能&#xff08;AI&#xff09;。从我们清晨醒来直至夜晚入睡&#xff0c;人工智能…

原型模式

功能&#xff1a;复制一个运行时的对象&#xff0c;包括对象各个成员当前的值。并且能够通过父类的指针来克隆出子类的对象 主要解决&#xff1a;在运行期建立原型 优点&#xff1a;性能提高、避免了构造函数的约束 步骤&#xff1a; 1、定义抽象原型&#xff0c;声明纯虚接…

C# 可空类型

文章目录 前言一、单问号&#xff08;?&#xff09;二、双问号&#xff08;??&#xff09; 前言 可空类型&#xff08;Nullable&#xff09;是一个极具实用性的特性&#xff0c;它为我们处理那些可能出现未赋值情况的数据提供了便捷且合理的方式。而其中&#xff0c;单问号&…

如何使用 Codegen 加速 React Native 开发?

写在前面 在 React Native 开发中&#xff0c;经常需要编写大量的样板代码&#xff0c;例如组件、屏幕、API 等。这些重复性的工作不仅浪费时间&#xff0c;还容易出错。为了解决这个问题&#xff0c;Facebook 推出了一个名为 Codegen 的工具&#xff0c;它可以根据模板和配置…

海盗王用golang重写的AccountServer功能

自从用golang重写了海盗王的网关gateserver以来&#xff0c;一直想把accountserver也重写了&#xff0c;但是一直没有进行。 趁上次刚写好那个golang版的更新器&#xff0c;还有些熟悉&#xff0c;于是把原来AccountServer的C代码重写读了个大概。它原版的写得太过于复杂&#…

【目标跟踪】Anti-UAV数据集详细介绍

Anti-UAV数据集是在2021年公开的专用于无人机跟踪的数据集&#xff0c;该数据集采用RGB-T图像对的形式来克服单个类型视频的缺点&#xff0c;包含了318个视频对&#xff0c;并提出了相应的评估标准&#xff08;the state accurancy, SA)。 文章链接&#xff1a;https://arxiv.…

宝塔Linux面板上传PHP文件或者修改PHP文件,总是转圈圈,其他文件正常,解决办法

目录 问题描述 寻找解决方案 1.重启宝塔面板 2.清理宝塔缓存 3.升级面板 4.ssh远程 5.清空回收站 6.换网络 7. IDE远程编辑 总结&#xff1a; 问题描述 一直用宝塔linux面板&#xff0c;感觉非常好用&#xff0c;点点就能搞定&#xff0c;环境也很好配置。 公司搬家&…

娱乐API:快速生成藏头诗、藏尾诗和藏中诗

引言 诗歌是中国传统文化的重要组成部分&#xff0c;其中藏头诗、藏尾诗和藏中诗因其独特的形式而备受喜爱。为了满足广大文学爱好者的需求&#xff0c;我们推出了一款娱乐API&#xff0c;支持快速生成藏头诗、藏尾诗和藏中诗。本文将详细介绍该API的功能、使用方法以及如何将…