激光雷达进行障碍物检测

1. 背景

在农业机器人的正前方安装激光雷达,进行障碍物检测,激光雷达的数据向上发送的数据是pointcloud2类型,即xyzi(x、y、z表示点的空间位置,i可能表示反射强度等信息),下面介绍几种基于xyzi数据进行障碍物检测的方案

2. 基于阈值的简单聚类方法

2.1 原理

对于激光雷达数据中的xyzi(x、y、z表示点的空间位置,i可能表示反射强度等信息),可以首先根据z轴(高度)信息来设置阈值。例如,如果地面是相对平整的,且已知激光雷达安装高度和地面大致高度范围,将z值低于地面高度一定范围的点视为障碍物点的一部分。
然后,对于x和y方向,可以通过计算点之间的距离来进行聚类。如果两个点之间的距离小于某个设定的阈值(例如根据障碍物的最小尺寸估计),则将它们归为同一类。

2.2 步骤示例

首先,读取激光雷达数据文件,解析出xyzi信息存储在合适的数据结构中,如数组或自定义的点云类。
遍历点云数据,根据z轴阈值筛选出可能的障碍物点,将这些点存储在一个新的集合中。
对于新集合中的点,使用距离计算公式(如欧几里得距离)在这里插入图片描述
来判断点之间的距离。如果距离小于设定的聚类阈值,将它们标记为同一聚类。
根据聚类后的结果,可以确定障碍物的大致位置和范围。

2.3 代码

#include <iostream>
#include <vector>
#include <fstream>
#include <cmath>

// 定义点结构体,对应xyzi数据
struct Point {
    float x;
    float y;
    float z;
    float intensity;
};

// 计算两点之间的欧几里得距离
float distance(const Point& p1, const Point& p2) {
    return std::sqrt((p1.x - p2.x) * (p2.x - p1.x) + (p1.y - p2.y) * (p2.y - p1.y));
}

int main() {
    // 假设从文件中读取激光雷达数据,这里文件名可替换
    std::ifstream infile("lidar_data.xyz");
    if (!infile) {
        std::cerr << "无法打开文件" << std::endl;
        return -1;
    }

    std::vector<Point> points;
    float x, y, z, intensity;
    // 读取文件中的点数据
    while (infile >> x >> y >> z >> intensity) {
        Point p = {x, y, z, intensity};
        points.push_back(p);
    }
    infile.close();

    // 假设地面高度范围,这里示例值可根据实际调整
    float ground_height_min = -0.2;
    float ground_height_max = 0.2;
    // 聚类距离阈值
    float cluster_distance_threshold = 0.5; 

    // 存储可能的障碍物点
    std::vector<Point> obstacle_points;
    // 筛选出可能的障碍物点(根据高度)
    for (const auto& p : points) {
        if (p.z > ground_height_max || p.z < ground_height_min) {
            obstacle_points.push_back(p);
        }
    }

    // 聚类标记数组,初始为-1表示未分类
    std::vector<int> labels(obstacle_points.size(), -1);
    int current_label = 0;
    // 进行聚类
    for (size_t i = 0; i < obstacle_points.size(); ++i) {
        if (labels[i] == -1) {
            labels[i] = current_label;
            for (size_t j = i + 1; j < obstacle_points.size(); ++j) {
                if (distance(obstacle_points[i], obstacle_points[j]) < cluster_distance_threshold) {
                    labels[j] = current_label;
                }
            }
            current_label++;
        }
    }

    // 输出聚类结果(简单示例,可根据实际进一步处理)
    std::cout << "共检测到 " << current_label << " 个障碍物聚类" << std::endl;
    for (int label = 0; label < current_label; ++label) {
        std::cout << "聚类 " << label << " 中的点数量: ";
        int count = 0;
        for (size_t i = 0; i < labels.size(); ++i) {
            if (labels[i] == label) {
                count++;
            }
        }
        std::cout << count << std::endl;
    }

    return 0;
}

3. 基于空间划分的方法(如体素网格)

3.1 原理

将三维空间划分为规则的小立方体(体素)。对于每个体素,可以统计其中包含的点的数量或者其他属性(如最大z值、平均反射强度等)。如果一个体素中的点数量超过一定阈值,或者其属性符合障碍物的特征(例如最大z值高于地面高度很多),则可以认为这个体素包含障碍物。

3.2 步骤示例

确定体素的大小,这需要根据激光雷达的精度和要检测的障碍物大小来选择。例如,对于高精度激光雷达和较小障碍物,可以选择较小的体素尺寸(如 0.1m×0.1m×0.1m)。
遍历激光雷达数据中的点,将每个点分配到对应的体素中。可以通过计算点的x、y、z坐标除以体素尺寸的整数部分来确定体素索引。
统计每个体素中的点数量等属性。如果点数量超过阈值(如 5 个点以上),标记该体素为可能包含障碍物。
最后,根据标记的体素位置和范围来确定障碍物的位置。

3.3 代码

#include <iostream>
#include <vector>
#include <fstream>
#include <cmath>
#include <unordered_map>

// 定义点结构体,对应xyzi数据
struct Point {
    float x;
    float y;
    float z;
    float intensity;
};

int main() {
    // 假设从文件中读取激光雷达数据,这里文件名可替换
    std::ifstream infile("lidar_data.xyz");
    if (!infile) {
        std::cerr << "无法打开文件" << std::endl;
        return -1;
    }

    std::vector<Point> points;
    float x, y, z, intensity;
    // 读取文件中的点数据
    while (infile >> x >> y >> z >> intensity) {
        Point p = {x, y, z, intensity};
        points.push_back(p);
    }
    infile.close();

    // 体素尺寸(示例值,可根据实际调整)
    float voxel_size = 0.1;
    // 体素内点数量阈值
    int point_count_threshold = 5; 

    // 使用unordered_map来存储体素及其中的点索引
    std::unordered_map<int, std::vector<int>> voxels;
    // 将点分配到体素中并统计点数量
    for (size_t i = 0; i < points.size(); ++i) {
        int voxel_x = std::floor(points[i].x / voxel_size);
        int voxel_y = std::floor(points[i].y / voxel_size);
        int voxel_z = std::floor(points[i].z / voxel_size);
        int voxel_index = voxel_x + voxel_y * 100 + voxel_z * 10000;  // 简单的索引计算示例

        voxels[voxel_index].push_back(i);
    }

    // 检测包含障碍物的体素
    std::vector<int> obstacle_voxels;
    for (const auto& voxel : voxels) {
        if (voxel.second.size() >= point_count_threshold) {
            obstacle_voxels.push_back(voxel.first);
        }
    }

    // 输出包含障碍物的体素信息(简单示例,可根据实际进一步处理)
    std::cout << "检测到 " << obstacle_voxels.size() << " 个体素包含障碍物" << std::endl;
    for (int voxel_index : obstacle_voxels) {
        std::cout << "体素索引: " << voxel_index << ", 包含点数量: " << voxels[voxel_index].size() << std::endl;
    }

    return 0;
}

4. 聚类分割方法(例如欧几里得聚类)

4.1 原理

PCL 库提供了高效的点云处理算法。欧几里得聚类算法基于点之间的空间距离来划分点云。它从一个未标记的点开始,寻找与其距离小于给定阈值的所有点,将这些点标记为同一聚类。然后,继续寻找未标记的点,重复这个过程,直到所有点都被标记。

4.2 步骤示例(使用 PCL 库)

首先,将xyzi数据转换为 PCL 中的点云数据类型(如pcl::PointCloudpcl::PointXYZI)。
创建一个欧几里得聚类提取对象(如pcl::EuclideanClusterExtractionpcl::PointXYZI),设置距离阈值(例如 0.5 米)和最小聚类点数(例如 10 个点)等参数。
调用聚类算法对转换后的点云数据进行处理,得到聚类后的结果。
可以通过遍历聚类结果来获取每个聚类的点集,进而确定障碍物的位置和形状。

4.3 代码

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/segmentation/euclidean_cluster_extraction.h>

int main() {
    // 读取PCD格式的点云文件(这里假设数据已保存为PCD格式,也可从其他格式转换过来)
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);
    if (pcl::io::loadPCDFile<pcl::PointXYZI>("lidar_data.pcd", *cloud) == -1) {
        PCL_ERROR("无法读取点云文件\n");
        return (-1);
    }

    // 创建欧几里得聚类提取对象
    pcl::EuclideanClusterExtraction<pcl::PointXYZI> ec;
    ec.setInputCloud(cloud);
    // 设置距离阈值(单位:米,示例值可调整)
    ec.setClusterTolerance(0.5);
    // 设置最小聚类点数
    ec.setMinClusterSize(10);
    // 设置最大聚类点数(可选,这里示例设一个较大值)
    ec.setMaxClusterSize(1000000);

    std::vector<pcl::PointIndices> cluster_indices;
    // 执行聚类算法
    ec.extract(cluster_indices);

    // 输出聚类结果
    std::cout << "共检测到 " << cluster_indices.size() << " 个障碍物聚类" << std::endl;
    for (size_t i = 0; i < cluster_indices.size(); ++i) {
        std::cout << "聚类 " << i << " 中的点数量: " << cluster_indices[i].indices.size() << std::endl;
    }

    return 0;
}

5. 基于模型拟合的方法(如平面拟合用于地面去除后检测障碍物)

5.1 原理

先使用 PCL 库中的平面拟合算法(如随机抽样一致性算法 - RANSAC)来拟合地面平面。RANSAC 算法可以从点云中随机选择一些点来拟合模型(平面方程)在这里插入图片描述
,然后计算其他点到该平面的距离。通过多次迭代,找到最佳拟合平面。去除地面点后,剩余的点可以通过聚类等方法来检测障碍物。

5.2 步骤示例

将xyzi数据转换为 PCL 点云类型。
创建一个平面拟合对象(如pcl::SACSegmentationpcl::PointXYZI),设置模型类型为平面(pcl::SACMODEL_PLANE),距离阈值(例如 0.05 米,表示到拟合平面距离超过这个值的点被视为非平面点)和 RANSAC 迭代次数(例如 100 次)等参数。
运行平面拟合算法,得到地面平面的参数和地面点的索引。
从原始点云中去除地面点,然后使用聚类等障碍物检测方法对剩余点进行处理。

5.3 代码

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/ModelCoefficients.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/filters/extract_indices.h>

int main() {
    // 读取PCD格式的点云文件(这里假设数据已保存为PCD格式,也可从其他格式转换过来)
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);
    if (pcl::io::loadPCDFile<pcl::PointXYZI>("lidar_data.pcd", *cloud) == -1) {
        PCL_ERROR("无法读取点云文件\n");
        return (-1);
    }

    // 创建平面拟合对象(基于随机抽样一致性算法 - RANSAC)
    pcl::SACSegmentation<pcl::PointXYZI> seg;
    seg.setOptimizeCoefficients(true);
    // 设置输入点云
    seg.setInputCloud(cloud);
    // 设置模型类型为平面
    seg.setModelType(pcl::SACMODEL_PLANE);
    // 设置距离阈值(单位:米,示例值可调整)
    seg.setDistanceThreshold(0.05);
    // 设置RANSAC算法的迭代次数
    seg.setMaxIterations(100);

    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
    pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
    // 执行平面拟合算法,获取地面平面参数和地面点索引
    seg.segment(*inliers, *coefficients);
    if (inliers->indices.size() == 0) {
        PCL_ERROR("未能找到合适的地面平面\n");
        return (-1);
    }

    // 创建点云提取对象,用于去除地面点
    pcl::ExtractIndices<pcl::PointXYZI> extract;
    extract.setInputCloud(cloud);
    extract.setIndices(inliers);
    extract.setNegative(true);  // 设置为true表示提取非地面点
    pcl::PointCloud<pcl::PointXYZI>::Ptr obstacle_cloud(new pcl::PointCloud<pcl::PointXYZI>);
    extract.filter(*obstacle_cloud);

    // 这里可以对去除地面后的点云(obstacle_cloud)进一步使用聚类等方法检测障碍物
    // 例如下面简单输出去除地面后点云的点数作为示例
    std::cout << "去除地面后,剩余点云点数: " << obstacle_cloud->size() << std::endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值