前言
小白一枚,进行实验之前,首先要明确一下思路:
1、为何要进行拟合,首先搞清楚你的需求、你要实现的目标。
2、二维数据拟合成线,首先要做的就是滤波、去除离散点,否则对拟合的效果影响很大。
3、如何拟合单条【本文以直线为例】、拿到它的方程式,并进行可视化展示。
4、若要拟合多条线【本文以直线为例】是否可行?实现思路是怎样的。
一、滤波、去除离散点方法
我比较常用的是条件滤波,去除离散点常用的是StatisticalOutlierRemoval 和 RadiusOutlierRemoval
下面写几个例子,并给出了完整代码:
1、条件滤波:
条件滤波是拿到你给定x\y\z区间内的点云。
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/extract_indices.h>
#include <iostream>
#include <vector>
#include <cmath>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
using namespace std;
int main() {
// 读取点云文件 20230906_103819.pcd
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("20230906_103603.pcd", *cloud) == -1) {
std::cerr << "Failed to load the PCD file." << std::endl;
return (-1);
}
std::cout << "Loaded "
<< cloud->width * cloud->height
<< " data points from the PCD file." << std::endl;
//创建容器保存过滤后的点
pcl::PointCloud<pcl::PointXYZ>::Ptr conditionCloud(new pcl::PointCloud<pcl::PointXYZ>);
//创建条件滤波,设置滤波条件
pcl::ConditionAnd<pcl::PointXYZ>::Ptr rangeCondOrigin(new pcl::ConditionAnd<pcl::PointXYZ>());
//这里我设置的范围是x从-2到1,y从-0.5到10.0,关于开闭区间,可以自行修改
rangeCondOrigin->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::GT, -0.5)));
rangeCondOrigin->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::LT, 10.0)));
rangeCondOrigin->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::GT, -2)));
rangeCondOrigin->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::LT, 1)));
// 执行点云移除处理,并将剩下的点添加到 conditionCloud(刚刚创建的容器,现在得到的就是过滤后的点云)
pcl::ConditionalRemoval<pcl::PointXYZ> condremOrigin(rangeCondOrigin, false);
condremOrigin.setInputCloud(cloud);
condremOrigin.setKeepOrganized(false);
condremOrigin.filter(*conditionCloud);
//可视化显示过滤后的点云
pcl::visualization::PCLVisualizer viewer("Point Cloud Viewer");
viewer.addPointCloud<pcl::PointXYZ>(conditionCloud, "conditionCloud");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "conditionCloud");
viewer.setBackgroundColor(0, 0, 0); // 设置背景色为黑色
while (!viewer.wasStopped()) {
viewer.spinOnce(100);
}
return 0;
}
2、RadiusOutlierRemoval去除离散点
过滤掉不符合搜索半径内邻居点数目的将被过滤掉。
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/extract_indices.h>
#include <iostream>
#include <vector>
#include <cmath>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
using namespace std;
int main() {
// 读取点云文件
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("20230906_103603.pcd", *cloud) == -1) {
std::cerr << "Failed to load the PCD file." << std::endl;
return (-1);
}
pcl::RadiusOutlierRemoval<pcl::PointXYZ> sorCloud; //创建滤波器对象
sorCloud.setInputCloud(cloud); //设置待滤波的点云
sorCloud.setRadiusSearch(0.01); // 设置搜索半径
sorCloud.setMinNeighborsInRadius(3); // 设置一个内点最少的邻居数目
// 创建一个容器对象来保存滤波后的点云
pcl::PointCloud<pcl::PointXYZ>::Ptr radiusCloud(new pcl::PointCloud<pcl::PointXYZ>);
sorCloud.filter(*radiusCloud);
//可视化窗口
pcl::visualization::PCLVisualizer viewer("Point Cloud Viewer");
viewer.addPointCloud<pcl::PointXYZ>(radiusCloud, "radiusCloud");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "radiusCloud");
viewer.setBackgroundColor(0, 0, 0); // 设置背景色为黑色
while (!viewer.wasStopped()) {
viewer.spinOnce(100);
}
return 0;
}
二、拟合成线
1、要想拟合效果好,必须滤干净杂点、离散点,我依次使用了半径滤波、条件滤波
2、选择想要拟合的函数的最高阶次数,下面我写的1,目的是拟合一次直线,你也可以根据你的实验需求改成2、3、4、5…
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/extract_indices.h>
#include <iostream>
#include <vector>
#include <cmath>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
using namespace std;
// 计算给定点集的多项式拟合
Eigen::VectorXd fitPolynomial(const vector<pcl::PointXYZ>& points, int degree)
{
int n = points.size();
Eigen::MatrixXd A(n, degree + 1);
Eigen::VectorXd b(n);
for (int i = 0; i < n; ++i)
{
for (int j = 0; j <= degree; ++j)
{
A(i, j) = pow(points[i].x, j);
}
b(i) = points[i].y;
}
Eigen::VectorXd coeffs = A.colPivHouseholderQr().solve(b);
return coeffs;
}
int main() {
// 读取点云文件
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("20230906_103603.pcd", *cloud) == -1) {
std::cerr << "Failed to load the PCD file." << std::endl;
return (-1);
}
std::cout << "Loaded "
<< cloud->width * cloud->height
<< " data points from the PCD file." << std::endl;
//对cloud滤波
//半径内去除离散点
pcl::RadiusOutlierRemoval<pcl::PointXYZ> sor; //创建滤波器对象
sor.setInputCloud(cloud); //设置待滤波的点云
sor.setRadiusSearch(0.008); // 设置搜索半径
sor.setMinNeighborsInRadius(3); // 设置一个内点最少的邻居数目
// 创建一个容器对象来保存滤波后的点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered_last(new pcl::PointCloud<pcl::PointXYZ>);
sor.filter(*cloud_filtered_last);
// 创建条件滤波器
pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZ>());
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::GT, -0.18)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::LT, 0.23)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::GT, 0.63)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::LT, 1.13)));
// 执行点云移除处理,并将剩下的点添加到 filtered(过滤后的点云)
pcl::ConditionalRemoval<pcl::PointXYZ> condrem(range_cond, false);
condrem.setInputCloud(cloud_filtered_last);
condrem.setKeepOrganized(false);
condrem.filter(*cloud_filtered_last);
// 拟合曲线 可以根据需要调整多项式的阶数或其他参数
vector<pcl::PointXYZ> filtered_points(cloud_filtered_last->begin(), cloud_filtered_last->end());
Eigen::VectorXd coeffs = fitPolynomial(filtered_points, 1); // 使用一次多项式拟合,根据需求设置,如果你要拟合2次曲线就改成2
// 输出拟合的直线系数
std::cout << "Model coefficients: " << coeffs.transpose() << std::endl;
double a = coeffs(1); // 斜率
double b = coeffs(0); // 截距
// 显示拟合的直线方程
std::cout << "拟合方程为 y = " << coeffs(1) << "x + " << coeffs(0) << std::endl;
//可视化过滤后的原始点云
pcl::visualization::PCLVisualizer viewer("Point Cloud Viewer");
viewer.addPointCloud<pcl::PointXYZ>(cloud, "cloud");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud");
viewer.setBackgroundColor(0, 0, 0); // 设置背景色为黑色
// 获取点云的范围
float minX = cloud_filtered_last->points.front().x;
float maxX = cloud_filtered_last->points.front().x;
for (const auto& point : cloud_filtered_last->points) {
minX = std::min(minX, point.x-1);
maxX = std::max(maxX, point.x+1);
}
// 添加拟合的直线
pcl::PointXYZ start_point(minX, coeffs(1) * minX + coeffs(0), 0);
pcl::PointXYZ end_point(maxX, coeffs(1) * maxX + coeffs(0), 0);
viewer.addLine<pcl::PointXYZ>(start_point, end_point, 1.0, 0.0, 0.0, "fitted_line");
while (!viewer.wasStopped()) {
viewer.spinOnce(100);
}
return 0;
}
效果如下:
三、拟合多条线
线是一条一条拟合的,如果说有人说有一种方法说可以一劳永逸、一次性拟合点云中所有的线,那肯定是不靠谱的。
拟合多条线的思路应该是:
1、先把杂点、离散点过滤干净,再设置条件滤波滤出第一条线,拿到这条线后通过代码把点云文件中这条线附近的所有点都给滤掉,对剩余的点进行拟合。
2、对剩余的点去除离散点后进行条件滤波,通过设定条件,对条件内的点进行第二条线的拟合,拟合后再把这条线周围所有的点滤掉,对剩余的点进行拟合。
3、以此类推。。。直到拟合出你想要的效果。
下面我以已经拿到第一条线为前提、如何拟合第二条线为例,写代码如下:
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/extract_indices.h>
#include <iostream>
#include <vector>
#include <cmath>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
using namespace std;
// 计算给定点集的多项式拟合
Eigen::VectorXd fitPolynomial(const vector<pcl::PointXYZ>& points, int degree)
{
int n = points.size();
Eigen::MatrixXd A(n, degree + 1);
Eigen::VectorXd b(n);
for (int i = 0; i < n; ++i)
{
for (int j = 0; j <= degree; ++j)
{
A(i, j) = pow(points[i].x, j);
}
b(i) = points[i].y;
}
Eigen::VectorXd coeffs = A.colPivHouseholderQr().solve(b);
return coeffs;
}
// 定义一个函数来检查点是否靠近给定的直线
bool isNearLine(const pcl::PointXYZ& point, double slope, double intercept, double threshold) {
// 计算点到直线的距离
double distance = std::abs(point.y - (slope * point.x + intercept)) / std::sqrt(slope * slope + 1);
return distance <= threshold;
}
int main() {
// 读取点云文件 20230906_103819.pcd
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("20230906_103603.pcd", *cloud) == -1) {
std::cerr << "Failed to load the PCD file." << std::endl;
return (-1);
}
std::cout << "Loaded "
<< cloud->width * cloud->height
<< " data points from the PCD file." << std::endl;
// 直线参数
double slope = 1.0968; // 斜率
double intercept = -1.452976; // 截距
double threshold = 0.1; // 阈值
// 创建一个向量存储要移除的索引
std::vector<int> indices_to_remove;
for (size_t i = 0; i < cloud->points.size(); ++i) {
if (isNearLine(cloud->points[i], slope, intercept, threshold)) {
indices_to_remove.push_back(i);
}
}
// 创建过滤器对象
pcl::ExtractIndices<pcl::PointXYZ> extract;
extract.setInputCloud(cloud);
extract.setIndices(pcl::IndicesPtr(new std::vector<int>(indices_to_remove)));
extract.setNegative(true); // 设置为true保留不在索引列表中的点
pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_cloud(new pcl::PointCloud<pcl::PointXYZ>);
extract.filter(*filtered_cloud);
// 保存过滤后的点云
pcl::io::savePCDFileASCII("filtered_20230906_103819.pcd", *filtered_cloud);
std::cout << "钢轴过滤后的点云已保存" << std::endl;
//对filtered_cloud滤波
//半径内去除离散点
pcl::RadiusOutlierRemoval<pcl::PointXYZ> sor; //创建滤波器对象
sor.setInputCloud(filtered_cloud); //设置待滤波的点云
sor.setRadiusSearch(0.008); // 设置搜索半径
sor.setMinNeighborsInRadius(3); // 设置一个内点最少的邻居数目
// 创建一个容器对象来保存滤波后的点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered_last(new pcl::PointCloud<pcl::PointXYZ>);
sor.filter(*cloud_filtered_last);
// 创建条件滤波器
pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZ>());
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::GT, -0.18)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::LT, 0.23)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::GT, 0.63)));
range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::LT, 1.13)));
// 执行点云移除处理,并将剩下的点添加到 filtered(过滤后的点云)
pcl::ConditionalRemoval<pcl::PointXYZ> condrem(range_cond, false);
condrem.setInputCloud(cloud_filtered_last);
condrem.setKeepOrganized(false);
condrem.filter(*cloud_filtered_last);
// 拟合曲线 可以根据需要调整多项式的阶数或其他参数
vector<pcl::PointXYZ> filtered_points(cloud_filtered_last->begin(), cloud_filtered_last->end());
Eigen::VectorXd coeffs = fitPolynomial(filtered_points, 1); // 使用一次多项式拟合
// 输出拟合的直线系数
std::cout << "Model coefficients: " << coeffs.transpose() << std::endl;
double a = coeffs(1); // 斜率
double b = coeffs(0); // 截距
// 显示拟合的直线方程
std::cout << "固定方程为:y = 1.0968x - 1.452976" << std::endl;
std::cout << "拟合方程为 y = " << coeffs(1) << "x + " << coeffs(0) << std::endl;
//拟合直线方程为y=coeffs(1)x+coeffs(0)
//可视化过滤后的原始点云
pcl::visualization::PCLVisualizer viewer("Point Cloud Viewer");
viewer.addPointCloud<pcl::PointXYZ>(cloud, "cloud");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud");
viewer.setBackgroundColor(0, 0, 0); // 设置背景色为黑色
// 获取点云的范围
float minX = cloud_filtered_last->points.front().x;
float maxX = cloud_filtered_last->points.front().x;
for (const auto& point : cloud_filtered_last->points) {
minX = std::min(minX, point.x-1);
maxX = std::max(maxX, point.x+1);
}
// 添加拟合的直线
pcl::PointXYZ start_point(minX, coeffs(1) * minX + coeffs(0), 0);
pcl::PointXYZ end_point(maxX, coeffs(1) * maxX + coeffs(0), 0);
viewer.addLine<pcl::PointXYZ>(start_point, end_point, 1.0, 0.0, 0.0, "fitted_line");
// 添加另一条直线 y = 1.0968x - 1.452976
pcl::PointXYZ second_start_point(minX, 1.0968 * minX - 1.452976, 0);
pcl::PointXYZ second_end_point(maxX, 1.0968 * maxX - 1.452976, 0);
viewer.addLine<pcl::PointXYZ>(second_start_point, second_end_point, 0.0, 1.0, 0.0, "second_line");
while (!viewer.wasStopped()) {
viewer.spinOnce(100);
}
return 0;
}
效果如下: