4. 点云预处理

任务目标

本案例基于PCL(Point Cloud Library)实现半径滤波(RadiusOutlierRemoval),对含稀疏离群点的点云数据进行去噪处理。通过设定搜索半径 radius 与最小邻域点数 min_neighbors,观察不同参数组合下去噪效果的差异,并输出滤波前后的点云数量对比。

环境与依赖

  • Ubuntu 20.04+ / Windows 10+,已安装 PCL 1.10+ 与 CMake 3.10+
  • 安装命令(Ubuntu):sudo apt install libpcl-dev
  • 输入数据:程序内生成合成点云(含球面点 + 随机稀疏噪声),无需外部文件

关键完整代码

#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <random>
#include <thread>

using PointT = pcl::PointXYZ;

// 生成球面点云 + 稀疏噪声
pcl::PointCloud<PointT>::Ptr generateCloud() {
    auto cloud = pcl::PointCloud<PointT>::Ptr(new pcl::PointCloud<PointT>);
    std::mt19937 gen(42);
    std::uniform_real_distribution<float> noise(-1.0f, 1.0f);

    // 球面点(半径0.5,中心原点)
    for (int i = 0; i < 2000; ++i) {
        float theta = 2 * M_PI * i / 2000;
        float phi = acos(1 - 2 * (i % 50) / 50.0f);
        PointT p;
        p.x = 0.5f * sin(phi) * cos(theta);
        p.y = 0.5f * sin(phi) * sin(theta);
        p.z = 0.5f * cos(phi);
        cloud->push_back(p);
    }
    // 稀疏噪声点(200个)
    for (int i = 0; i < 200; ++i) {
        PointT p;
        p.x = noise(gen) * 1.5f;
        p.y = noise(gen) * 1.5f;
        p.z = noise(gen) * 1.5f;
        cloud->push_back(p);
    }
    cloud->width = cloud->size();
    cloud->height = 1;
    cloud->is_dense = true;
    return cloud;
}

// 执行半径滤波
pcl::PointCloud<PointT>::Ptr radiusFilter(
    const pcl::PointCloud<PointT>::Ptr& input,
    float radius, int min_neighbors) {
    pcl::RadiusOutlierRemoval<PointT> filter;
    filter.setInputCloud(input);
    filter.setRadiusSearch(radius);
    filter.setMinNeighborsInRadius(min_neighbors);
    auto output = pcl::PointCloud<PointT>::Ptr(new pcl::PointCloud<PointT>);
    filter.filter(*output);
    return output;
}

// 可视化对比(原始 vs 滤波后)
void visualize(const pcl::PointCloud<PointT>::Ptr& original,
               const pcl::PointCloud<PointT>::Ptr& filtered) {
    pcl::visualization::PCLVisualizer viewer("Radius Filter Demo");
    int v1, v2;
    viewer.createViewPort(0, 0, 0.5, 1, v1);
    viewer.createViewPort(0.5, 0, 1, 1, v2);

    viewer.addPointCloud(original, "original", v1);
    viewer.addPointCloud(filtered, "filtered", v2);
    viewer.setBackgroundColor(0.1, 0.1, 0.1, v1);
    viewer.setBackgroundColor(0.1, 0.1, 0.1, v2);
    viewer.addText("Original", 10, 10, 14, 1, 1, 1, "text1", v1);
    viewer.addText("Filtered", 10, 10, 14, 1, 1, 1, "text2", v2);

    while (!viewer.wasStopped()) {
        viewer.spinOnce(100);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main() {
    auto cloud = generateCloud();
    std::cout << "原始点云点数: " << cloud->size() << std::endl;

    // 参数组合1:小半径 + 少邻域
    auto r1 = radiusFilter(cloud, 0.05f, 5);
    std::cout << "半径0.05, 邻域5 -> 剩余: " << r1->size() << std::endl;

    // 参数组合2:大半径 + 多邻域
    auto r2 = radiusFilter(cloud, 0.15f, 15);
    std::cout << "半径0.15, 邻域15 -> 剩余: " << r2->size() << std::endl;

    visualize(cloud, r2);
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(radius_filter_demo)
find_package(PCL 1.10 REQUIRED COMPONENTS common filters visualization)
add_executable(radius_filter_demo main.cpp)
target_link_libraries(radius_filter_demo ${PCL_LIBRARIES})
add_definitions(${PCL_DEFINITIONS})

运行与验证

  1. 编译:在源码目录执行 mkdir build && cd build && cmake .. && make
  2. 运行:./radius_filter_demo
  3. 观察输出:终端打印三行点数信息,可视化窗口左右分屏显示原始点云与滤波结果

判定标准

  • 原始点云点数应为 2200(2000球面点 + 200噪声点)
  • 参数组合1(半径0.05,邻域5)应去除大部分稀疏噪声,剩余点数约 2000~2100(球面点密度高,噪声点因邻域不足被剔除)
  • 参数组合2(半径0.15,邻域15)去噪更激进,剩余点数应明显少于组合1,约 1900~2000,且球面结构保持完整
  • 可视化窗口中,右侧滤波结果应无孤立散点,球面轮廓清晰

参数影响说明

radius 决定邻域搜索范围,min_neighbors 决定保留条件。半径过小或邻域数过大可能导致球面边缘点被误删;半径过大或邻域数过小则噪声残留增多。实际应用中需根据点云密度(单位体积点数)调整:密度高时用小半径,密度低时用大半径。

半径滤波参数影响示意 原始点云(含噪声) 球面点 + 稀疏离群点 小半径 + 少邻域 去除大部分噪声,边缘点保留 大半径 + 多邻域 噪声完全去除,球面完整