2. 基于RANSAC的平面分割

任务目标

使用RANSAC算法从LiDAR点云中拟合平面模型,分割出地面或墙面点云,并输出分割结果的可视化。本案例以PCL库为基础,采用最小二乘意义上的RANSAC平面拟合,通过距离阈值区分内点(平面点)与外点(非平面点)。

环境与依赖

  • Ubuntu 20.04 / 18.04,已安装PCL 1.10+(含VTK可视化模块)
  • 编译工具:CMake 3.10+,g++ 7.5+
  • 输入数据:PCD格式点云文件(本案例使用PCL官方示例点云table_scene_lms400.pcd,可从PCL测试数据仓库获取)

核心实现

以下代码完成从PCD文件读取点云、执行RANSAC平面分割、提取内点与外点,并启动可视化窗口展示分割结果。分割参数(距离阈值)设为0.01米,适用于桌面场景的平面提取。

#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/pcd_io.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/ModelCoefficients.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <thread>

int main(int argc, char** argv) {
    // 1. 加载点云
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    if (pcl::io::loadPCDFile<pcl::PointXYZ>("table_scene_lms400.pcd", *cloud) == -1) {
        PCL_ERROR("无法读取PCD文件\n");
        return -1;
    }
    std::cerr << "输入点云点数: " << cloud->size() << std::endl;

    // 2. RANSAC平面分割
    pcl::SACSegmentation<pcl::PointXYZ> seg;
    pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
    seg.setOptimizeCoefficients(true);
    seg.setModelType(pcl::SACMODEL_PLANE);
    seg.setMethodType(pcl::SAC_RANSAC);
    seg.setDistanceThreshold(0.01);
    seg.setInputCloud(cloud);
    seg.segment(*inliers, *coefficients);

    if (inliers->indices.empty()) {
        PCL_ERROR("未找到平面模型\n");
        return -1;
    }

    // 3. 提取内点(平面)与外点(非平面)
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_plane(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_rest(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::ExtractIndices<pcl::PointXYZ> extract;
    extract.setInputCloud(cloud);
    extract.setIndices(inliers);
    extract.setNegative(false);
    extract.filter(*cloud_plane);
    extract.setNegative(true);
    extract.filter(*cloud_rest);

    std::cerr << "平面内点: " << cloud_plane->size()
              << ", 外点: " << cloud_rest->size() << std::endl;
    std::cerr << "平面方程: " << coefficients->values[0] << "x + "
              << coefficients->values[1] << "y + "
              << coefficients->values[2] << "z + "
              << coefficients->values[3] << " = 0" << std::endl;

    // 4. 可视化
    pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("RANSAC平面分割"));
    viewer->setBackgroundColor(0.1, 0.1, 0.1);
    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> plane_color(cloud_plane, 0, 255, 0);
    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> rest_color(cloud_rest, 255, 0, 0);
    viewer->addPointCloud(cloud_plane, plane_color, "plane");
    viewer->addPointCloud(cloud_rest, rest_color, "rest");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "plane");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "rest");
    while (!viewer->wasStopped()) {
        viewer->spinOnce(100);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    return 0;
}

编译与运行

创建CMakeLists.txt并编译:

cmake_minimum_required(VERSION 3.10)
project(plane_segmentation)
find_package(PCL 1.10 REQUIRED COMPONENTS common io segmentation visualization)
add_executable(plane_seg plane_seg.cpp)
target_link_libraries(plane_seg ${PCL_LIBRARIES})
add_definitions(${PCL_DEFINITIONS})
mkdir build && cd build
cmake ..
make
./plane_seg

验证方法

  • 终端输出:程序打印输入点数、平面内点数、外点数及平面方程系数。若内点数量占输入点云比例合理(桌面场景通常为30%~60%),说明分割有效。
  • 可视化窗口:绿色点云为拟合平面(桌面),红色点云为其余物体(如桌上的杯子、背景)。若绿色点云清晰覆盖桌面区域且红色点云包含桌上物体,则分割正确。
  • 参数调整:若平面过厚(内点包含非平面点),增大setDistanceThreshold;若平面破碎(内点过少),减小阈值。典型范围0.005~0.05米。
RANSAC平面分割流程 输入点云 PCD文件 RANSAC平面拟合 随机采样3点 计算平面模型 迭代优化 距离阈值 d < 0.01m 内点/外点分类 内点 平面点云 外点 非平面点云 可视化分割结果 绿色=平面,红色=非平面 输出平面方程 ax + by + cz + d = 0 关键参数 • 距离阈值: 0.01m • 模型: SACMODEL_PLANE • 方法: SAC_RANSAC • 优化系数: 开启

常见问题

  • 编译错误:确认PCL版本与CMake配置一致,若使用PCL 1.8需将find_package版本号改为1.8。
  • 可视化窗口无响应:确保主线程未阻塞,使用spinOnce加延时循环而非spin()。
  • 分割结果不理想:点云密度不均时,可先执行体素滤波下采样;若场景包含多个平面,需循环执行分割并移除已提取内点。