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