一. 环境介绍
1.Visual Studio 2019
2.PCL、Boost
二. 点云数据处理
创建点云数据集,读取点云到pcl的点云结构体中:
pcl::PCDReader reader;
reader.read("1.pcd", *cloud);
对点云进行显示。为了同时显示原始点云,直通滤波器滤波,体素滤波器下采样,统计滤波器滤波,法向量计算,点云分割的结果,建立了三个PCLVisualizer渲染窗口,并将其中一个窗口分成四格同时显示不同滤波器的效果:
int v1(0);
viewer->createViewPort(0.0, 0.5, 0.5, 1.0, v1);
viewer->setBackgroundColor(0, 0, 0, v1);
viewer->addText("sample cloud", 10, 10, "sample cloud", v1);
viewer->addPointCloud<pcl::PointXYZ>(cloud, "cloud", v1);
int v2(0);
viewer->createViewPort(0.5, 0.5, 1.0, 1.0, v2);
viewer->setBackgroundColor(0.3, 0.3, 0.3, v2);
viewer->addText("cloud after PassThrough", 10, 10, "cloud after PassThrough", v2);
viewer->addPointCloud<pcl::PointXYZ>(cloud_after_PassThrough, "cloud_after_PassThrough", v2);
int v3(0);
viewer->createViewPort(0.0, 0.0, 0.5, 0.5, v3);
viewer->setBackgroundColor(0.3, 0.3, 0.3, v3);
viewer->addText("cloud after voxelgrid", 10, 10, "cloud after voxelgrid", v3);
viewer->addPointCloud<pcl::PointXYZ>(cloud_after_voxelgrid, "cloud_after_voxelgrid", v3);
int v4(0);
viewer->createViewPort(0.5, 0.0, 1.0, 0.5, v4);
viewer->setBackgroundColor(0.3, 0.3, 0.3, v4);
viewer->addText("cloud after StatisticalRemoval", 10, 10, "cloud after StatisticalRemoval", v4);
viewer->addPointCloud<pcl::PointXYZ>(cloud_after_StatisticalRemoval, "cloud_after_StatisticalRemoval", v4);
对点云进行滤波。通过直通滤波器滤波过滤z方向上过近或者过远的点云,通过体素滤波器下采样降低点云稠密度,通过统计滤波器滤波过滤离群点。通过上述流水线的串行点云滤波操作,得到更接近期望的点云:
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_PassThrough(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PassThrough<pcl::PointXYZ> passthrough;
passthrough.setInputCloud(cloud);
passthrough.setFilterFieldName("z");
passthrough.setFilterLimits(500.0, 3000.0);
passthrough.filter(*cloud_after_PassThrough);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_voxelgrid(new pcl::PointCloud<pcl::PointXYZ>);
pcl::VoxelGrid<pcl::PointXYZ> voxelgrid;
voxelgrid.setInputCloud(cloud_after_PassThrough);
voxelgrid.setLeafSize(10.0f, 10.0f, 10.0f);
voxelgrid.filter(*cloud_after_voxelgrid);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_StatisticalRemoval(new pcl::PointCloud<pcl::PointXYZ>);
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> Statistical;
Statistical.setInputCloud(cloud_after_voxelgrid);
Statistical.setMeanK(100);
Statistical.setStddevMulThresh(0.15);
Statistical.filter(*cloud_after_StatisticalRemoval);
对滤波后的点云进行法向量计算并显示法向量和点云:
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normalEstimation;
normalEstimation.setInputCloud(cloud_after_StatisticalRemoval);
normalEstimation.setRadiusSearch(100);
pcl::search::KdTree<pcl::PointXYZ>::Ptr kdtree(new pcl::search::KdTree<pcl::PointXYZ>);
normalEstimation.setSearchMethod(kdtree);
normalEstimation.compute(*normals);
viewer1->addPointCloud<pcl::PointXYZ>(cloud_after_StatisticalRemoval, "cloud_after_StatisticalRemoval");
viewer1->addPointCloudNormals<pcl::PointXYZ, pcl::Normal>(cloud_after_StatisticalRemoval, normals, 2, 100, "normals");
viewer1->addCoordinateSystem(1.0);
对点云进行分割。点云分割是根据空间、几何和纹理等特征对点云进行划分,使得同一划分区域内的点云拥有相似的特征 。从点云中提取簇(集群),并将点云索引保存在 cluster_indices 中,通过设置临近搜索的搜索半径(搜索容差)、每个簇(集群)的最小大小、每个簇(集群)的最大大小、点云搜索算法,进行点云分割。
需要注意的是:设置一个合适的聚类搜索半径 ClusterTolerance,如果搜索半径取一个非常小的值,那么一个实际独立的对象就会被分割为多个聚类;如果将值设置得太高,那么多个对象就会被分割为一个聚类,所以需要进行测试找出最合适的ClusterTolerance。
std::vector<pcl::PointIndices> cluster_indices;
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance(20.0);
ec.setMinClusterSize(2000);
ec.setMaxClusterSize(250000);
ec.setSearchMethod(tree);
ec.setInputCloud(cloud_after_StatisticalRemoval);
ec.extract(cluster_indices);
三. 代码
main.cpp:
#include <iostream>
#include <fstream>
#include <algorithm>
#include <stdio.h>
#include<pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/cloud_viewer.h>
#include<pcl/filters/passthrough.h>
#include<pcl/filters/voxel_grid.h>
#include<pcl/filters/statistical_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/features/normal_3d.h>
#include <pcl/ModelCoefficients.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <Eigen/Core>
#include <pcl/common/transforms.h>
using namespace std;
int main(int argc, char** argv)
{
vtkObject::GlobalWarningDisplayOff();
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PCDReader reader;
reader.read("1.pcd", *cloud);
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D Viewer:pre-process"));
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer1(new pcl::visualization::PCLVisualizer("3D Viewer:normals"));
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer2(new pcl::visualization::PCLVisualizer("3D Viewer:segmentation"));
viewer->initCameraParameters();
int v1(0);
viewer->createViewPort(0.0, 0.5, 0.5, 1.0, v1);
viewer->setBackgroundColor(0, 0, 0, v1);
viewer->addText("sample cloud", 10, 10, "sample cloud", v1);
viewer->addPointCloud<pcl::PointXYZ>(cloud, "cloud", v1);
std::cout << "原始点云数据点数:" << cloud->points.size() << std::endl;
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_PassThrough(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PassThrough<pcl::PointXYZ> passthrough;
passthrough.setInputCloud(cloud);
passthrough.setFilterFieldName("z");
passthrough.setFilterLimits(500.0, 3000.0);
passthrough.filter(*cloud_after_PassThrough);
int v2(0);
viewer->createViewPort(0.5, 0.5, 1.0, 1.0, v2);
viewer->setBackgroundColor(0.3, 0.3, 0.3, v2);
viewer->addText("cloud after PassThrough", 10, 10, "cloud after PassThrough", v2);
viewer->addPointCloud<pcl::PointXYZ>(cloud_after_PassThrough, "cloud_after_PassThrough", v2);
std::cout << "直通滤波后点云数据点数:" << cloud_after_PassThrough->points.size() << std::endl;
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_voxelgrid(new pcl::PointCloud<pcl::PointXYZ>);
pcl::VoxelGrid<pcl::PointXYZ> voxelgrid;
voxelgrid.setInputCloud(cloud_after_PassThrough);
voxelgrid.setLeafSize(10.0f, 10.0f, 10.0f);
voxelgrid.filter(*cloud_after_voxelgrid);
int v3(0);
viewer->createViewPort(0.0, 0.0, 0.5, 0.5, v3);
viewer->setBackgroundColor(0.3, 0.3, 0.3, v3);
viewer->addText("cloud after voxelgrid", 10, 10, "cloud after voxelgrid", v3);
viewer->addPointCloud<pcl::PointXYZ>(cloud_after_voxelgrid, "cloud_after_voxelgrid", v3);
std::cout << "体素化网格方法后点云数据点数:" << cloud_after_voxelgrid->points.size() << std::endl;
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_after_StatisticalRemoval(new pcl::PointCloud<pcl::PointXYZ>);
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> Statistical;
Statistical.setInputCloud(cloud_after_voxelgrid);
Statistical.setMeanK(100);
Statistical.setStddevMulThresh(0.15);
Statistical.filter(*cloud_after_StatisticalRemoval);
int v4(0);
viewer->createViewPort(0.5, 0.0, 1.0, 0.5, v4);
viewer->setBackgroundColor(0.3, 0.3, 0.3, v4);
viewer->addText("cloud after StatisticalRemoval", 10, 10, "cloud after StatisticalRemoval", v4);
viewer->addPointCloud<pcl::PointXYZ>(cloud_after_StatisticalRemoval, "cloud_after_StatisticalRemoval", v4);
std::cout << "统计分析滤波后点云数据点数:" << cloud_after_StatisticalRemoval->points.size() << std::endl;
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normalEstimation;
normalEstimation.setInputCloud(cloud_after_StatisticalRemoval);
normalEstimation.setRadiusSearch(100);
pcl::search::KdTree<pcl::PointXYZ>::Ptr kdtree(new pcl::search::KdTree<pcl::PointXYZ>);
normalEstimation.setSearchMethod(kdtree);
normalEstimation.compute(*normals);
viewer1->addPointCloud<pcl::PointXYZ>(cloud_after_StatisticalRemoval, "cloud_after_StatisticalRemoval");
viewer1->addPointCloudNormals<pcl::PointXYZ, pcl::Normal>(cloud_after_StatisticalRemoval, normals, 2, 100, "normals");
viewer1->addCoordinateSystem(1.0);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(cloud_after_StatisticalRemoval);
std::vector<pcl::PointIndices> cluster_indices;
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance(20.0);
ec.setMinClusterSize(2000);
ec.setMaxClusterSize(250000);
ec.setSearchMethod(tree);
ec.setInputCloud(cloud_after_StatisticalRemoval);
ec.extract(cluster_indices);
int j = 0;
for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin();it != cluster_indices.end(); ++it) {
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster(new pcl::PointCloud<pcl::PointXYZ>);
const std::vector<int>& indices = it->indices;
for (std::vector<int>::const_iterator pit = indices.begin(); pit != indices.end(); ++pit)
cloud_cluster->points.push_back(cloud_after_StatisticalRemoval->points[*pit]);
cloud_cluster->width = cloud_cluster->points.size();
cloud_cluster->height = 1;
cloud_cluster->is_dense = true;
std::cout << "PointCloud representing the Cluster: " << cloud_cluster->points.size() << " data points."
<< std::endl;
std::stringstream ss;
ss << "cloud_cluster_" << j;
pcl::visualization::PointCloudColorHandlerRandom<pcl::PointXYZ> single_color(cloud_cluster);
viewer2->addPointCloud<pcl::PointXYZ>(cloud_cluster, single_color, ss.str());
viewer2->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, ss.str());
j++;
}
std::cout << "cloud size: " << cluster_indices.size() << std::endl;
while (!viewer->wasStopped()) {
viewer->spinOnce(1000);
}
return (0);
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.9)
set(CMAKE_CXX_STANDARD 11)
project(pointscloudprocessing)
set(BOOST_ROOT "C:/PCL 1.12.1/3rdParty/Boost")
find_package(Boost REQUIRED
COMPONENTS system filesystem thread)
find_package(PCL 1.3 REQUIRED)
find_package(VTK REQUIRED)
set(SOURCE_FILES main.cpp)
include_directories(Include/)
include_directories(common/include)
include_directories(
${PCL_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS}
${VTK_INCLUDE_DIRS}
)
link_directories(Lib/Win64)
link_directories(common/lib/Win64)
link_directories(
${PCL_LIBRARY_DIRS}
${BOOST_LIBRARY_DIRS}
${VTK_LIBRARY_DIRS}
)
add_definitions(
${PCL_DEFINITIONS}
)
add_compile_definitions(NOMINMAX)
add_executable(pointscloudprocessing ${SOURCE_FILES})
target_link_libraries(pointscloudprocessing
${PCL_LIBRARIES}
${Boost_LIBRARIES}
${VTK_LIBRARIES}
)
四. 运行结果
可以看到第一个渲染窗口的原始点云,直通滤波器滤波,体素滤波器下采样,统计滤波器滤波结果:

法向量计算结果:

点云分割结果:
