二维点云数据拟合直线或曲线方程【sick二维激光雷达测距仪】


前言

小白一枚,进行实验之前,首先要明确一下思路:

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;
}

效果如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值