[done, 3349.09 ms : 19553780 points]
Available dimensions: x y z intensity
源点云 Statues_4.pcd
不同条件函数output.pcd 【按5切换到强度通道可视化】
终端输出:
Loading...
>> Done: 1200.46 ms, 19553780 points
Downsampling...
>> Done: 411.366 ms, 202529 points
Computing normals...
>> Done: 717.815 ms
Segmenting to clusters...
>> Done: 1616.51 ms
Saving...
>> Done: 590.647 ms
点云下载:
https://sourceforge.net/projects/pointclouds/files/PCD%20datasets/Trimble/Outdoor1/
源码解析:
#include <pcl/point_types.h> // 包含PCL库中点云类型的头文件
#include <pcl/io/pcd_io.h> // 包含PCL库中用于点云读写的头文件
#include <pcl/console/time.h> // 包含PCL库中用于计时的头文件
#include <pcl/filters/voxel_grid.h> // 包含PCL库中体素栅格滤波器的头文件
#include <pcl/features/normal_3d.h> // 包含PCL库中用于计算点云中点的法向量的头文件
#include <pcl/segmentation/conditional_euclidean_clustering.h> // 包含PCL库中条件欧几里得聚类算法的头文件
typedef pcl::PointXYZI PointTypeIO; // 定义带有强度信息的3D点类型
typedef pcl::PointXYZINormal PointTypeFull; // 定义带有强度和法线信息的3D点类型
bool
enforceIntensitySimilarity (const PointTypeFull& point_a, const PointTypeFull& point_b, float /*squared_distance*/)
{
if (std::abs (point_a.intensity - point_b.intensity) < 5.0f)
return (true); // 如果两个点的强度相差小于5,认为它们相似
else
return (false);
}
bool
enforceNormalOrIntensitySimilarity2(const PointTypeFull& point_a,
const PointTypeFull& point_b,
float squared_distance) // 曲率强度相似性
{
Eigen::Map<const Eigen::Vector3f> point_a_normal = point_a.getNormalVector3fMap(),
point_b_normal = point_b.getNormalVector3fMap();
if (fabs(point_a.intensity - point_b.intensity) < 5.0f) // 如果a点的密度-b点的密度<5
return (true);
if (fabs(point_a_normal.dot(point_b_normal)) < 0.05) // 如果a点法线估计
return (true);
return (false);
}
bool
enforceNormalOrIntensitySimilarity (const PointTypeFull& point_a, const PointTypeFull& point_b, float /*squared_distance*/)
{
Eigen::Map<const Eigen::Vector3f> point_a_normal = point_a.getNormalVector3fMap (), point_b_normal = point_b.getNormalVector3fMap ();
if (std::abs (point_a.intensity - point_b.intensity) < 5.0f)
return (true); // 如果强度相差小于5,认为相似
if (std::abs (point_a_normal.dot (point_b_normal)) > std::cos (30.0f / 180.0f * static_cast<float> (M_PI)))
return (true); // 如果法线方向的夹角小于30度,也认为相似
return (false);
}
bool
customRegionGrowing (const PointTypeFull& point_a, const PointTypeFull& point_b, float squared_distance)
{
Eigen::Map<const Eigen::Vector3f> point_a_normal = point_a.getNormalVector3fMap (), point_b_normal = point_b.getNormalVector3fMap ();
if (squared_distance < 10000) // 距离小于10000
{
if (std::abs (point_a.intensity - point_b.intensity) < 8.0f) // 强度相差小于8
return (true);
if (std::abs (point_a_normal.dot (point_b_normal)) > std::cos (30.0f / 180.0f * static_cast<float> (M_PI))) // 法线夹角小于30度
return (true);
}
else
{
if (std::abs (point_a.intensity - point_b.intensity) < 3.0f) // 距离大于10000时,强度相差小于3
return (true);
}
return (false);
}
int
main ()
{
// 用于存储点云的数据容器
pcl::PointCloud<PointTypeIO>::Ptr cloud_in (new pcl::PointCloud<PointTypeIO>), cloud_out (new pcl::PointCloud<PointTypeIO>);
pcl::PointCloud<PointTypeFull>::Ptr cloud_with_normals (new pcl::PointCloud<PointTypeFull>);
// 用于存储聚类结果的数据容器
pcl::IndicesClustersPtr clusters (new pcl::IndicesClusters), small_clusters (new pcl::IndicesClusters), large_clusters (new pcl::IndicesClusters);
pcl::search::KdTree<PointTypeIO>::Ptr search_tree (new pcl::search::KdTree<PointTypeIO>); // Kd树搜索方法
pcl::console::TicToc tt; // 用于计时
// 加载输入的点云
std::cerr << "Loading...\n", tt.tic ();
pcl::io::loadPCDFile ("Statues_4.pcd", *cloud_in);
std::cerr << ">> Done: " << tt.toc () << " ms, " << cloud_in->size () << " points\n";
// 使用体素栅格类下采样点云
std::cerr << "Downsampling...\n", tt.tic ();
pcl::VoxelGrid<PointTypeIO> vg;
vg.setInputCloud (cloud_in);
vg.setLeafSize (80.0, 80.0, 80.0); // 设置体素大小
vg.setDownsampleAllData (true); // 设置下采样时是否处理所有数据
vg.filter (*cloud_out);
std::cerr << ">> Done: " << tt.toc () << " ms, " << cloud_out->size () << " points\n";
// 设置法线估计类并合并数据到包含法线的点云中
std::cerr << "Computing normals...\n", tt.tic ();
pcl::copyPointCloud (*cloud_out, *cloud_with_normals); // 将经过下采样的点云数据复制到带有法线信息的点云容器中
pcl::NormalEstimation<PointTypeIO, PointTypeFull> ne; // 创建一个法线估计对象
ne.setInputCloud (cloud_out); // 设置该对象的输入点云为下采样之后的点云
ne.setSearchMethod (search_tree); // 设置搜索方法为Kd树
ne.setRadiusSearch (300.0); // 设置法线估计时的搜索半径为300.0
ne.compute (*cloud_with_normals); // 计算输入点云的每个点的法线,并将结果存储到带有法线信息的点云容器中
std::cerr << ">> Done: " << tt.toc () << " ms\n";
// 设置条件欧几里得聚类类
std::cerr << "Segmenting to clusters...\n", tt.tic ();
pcl::ConditionalEuclideanClustering<PointTypeFull> cec (true);
cec.setInputCloud (cloud_with_normals);
cec.setConditionFunction (&customRegionGrowing); // 设置条件函数
cec.setClusterTolerance (500.0); // 设置聚类容忍度
cec.setMinClusterSize (cloud_with_normals->size () / 1000); // 设置最小聚类大小
cec.setMaxClusterSize (cloud_with_normals->size () / 5); // 设置最大聚类大小
cec.segment (*clusters); // 执行聚类
cec.getRemovedClusters (small_clusters, large_clusters); // 获取移除的聚类结果
std::cerr << ">> Done: " << tt.toc () << " ms\n";
// 使用强度通道进行简单的可视化输出
for (const auto& small_cluster : (*small_clusters))
for (const auto& j : small_cluster.indices)
(*cloud_out)[j].intensity = -2.0; // 较小的聚类强度设置为-2
for (const auto& large_cluster : (*large_clusters))
for (const auto& j : large_cluster.indices)
(*cloud_out)[j].intensity = +10.0; // 较大的聚类强度设置为+10
for (const auto& cluster : (*clusters))
{
int label = rand () % 8; // 为每个聚类随机分配一个标签
for (const auto& j : cluster.indices)
(*cloud_out)[j].intensity = label; // 设置对应的强度值
}
// 保存输出的点云
std::cerr << "Saving...\n", tt.tic ();
pcl::io::savePCDFile ("output.pcd", *cloud_out);
std::cerr << ">> Done: " << tt.toc () << " ms\n";
return (0);
}
这段代码使用了PCL(Point Cloud Library)库,其主要的功能是对输入的点云数据进行条件欧式聚类分析。
主要步骤如下:
使用
pcl::VoxelGrid
类进行下采样:将输入点云的密度降低,用于提高后续操作的效率。这通过指定立方体体素的边长(80.0),将点云中处于同一立方体体素内的所有点替换为他们的质心。计算点云中每一个点的法线:使用
pcl::NormalEstimation
类计算每一个点的法线,这将作为后续操作的一部分使用。setRadiusSearch
用于指定查找近邻的半径。使用
pcl::ConditionalEuclideanClustering
类进行条件欧式聚类:在这个过程中,每一个点都会被放入一个特定的群组(也就是聚类)。算法会根据定义的距离容忍度(在本代码中为500)和自定义的函数customRegionGrowing
来确定点是否应属于当前的聚类。所定义的函数中,需要注意的是,据点之间的强度差异(小于8)和点法线之间的角度(小于或等于30度),或者强度差异小于3的情况下,两点可以被视为同一聚类。聚类的最大和最小大小也被定义为输入点的数量的五分之一和千分之一。对每个生成的聚类(条件欧式聚类)进行可视化处理:通过将不同聚类的点置为不同强度来区分它们。此处,较小的聚类将被设置为强度-2,较大的聚类将被设置为强度+10。
保存处理后的点云数据,以便于后续的分析和处理。
在这段代码中,定义了四个自定义函数(enforceIntensitySimilarity
,enforceNormalOrIntensitySimilarity
,customRegionGrowing
),这些函数被用作聚类过程中的条件函数,以便在决定如何聚类点时,可以根据强度和法线等属性进行更精细控制。
踩坑笔记
预处理器添加:PCL_NO_PRECOMPILE
添加flann.lib
pcl::console::TicToc
pcl::NormalEstimation<PointTypeIO, PointTypeFull> ne;
#include <pcl/point_types.h>
#include <pcl/features/normal_estimation.h>
#include <pcl/io/pcd_io.h>
#include <pcl/kdtree/kdtree_flann.h>
int main() {
// 加载点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("inputCloud.pcd", *cloud);
// 创建法线估计对象
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud(cloud);
// 创建一个空的kdtree表示,并将其设置为法线估计对象的搜索方法
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
ne.setSearchMethod(tree);
// 设置邻居搜索的参数
ne.setKSearch(20);
// 计算法线
pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>);
ne.compute(*cloud_normals);
// cloud_normals 现在包含输入点云的法线
}
计算结果存储在之前复制的带有法线信息的点云数据结构中时,不会覆盖点坐标吗?
pcl::ConditionalEuclideanClustering<PointTypeFull>
cec.getRemovedClusters (small_clusters, large_clusters);
点云类型