点云数据
pcl::PointCloud中包含points , width , height 。points是一个vector容器包含点云中所有的点,可以通过下下标来访问所有的点。
一、点的数据类型
1、PointXYZ
pcl::PointCloudpcl::PointXYZ
成员变量: float x, y, z;
最常见的一个点数据类型,因为它只包含三维xyz坐标信息, 值得注意的是它多加了一个浮点数来满足内存对齐
//访问xyz方式一
float x = cloud.points[i].data[0];
float y = cloud.points[i].data[1];
float z = cloud.points[i].data[2];
//访问xyz方式二
float x = cloud.points[i].x;
float y = cloud.points[i].y;
float z = cloud.points[i].z;
2、PointXYZI
pcl::PointCloudpcl::PointXYZI
成员变量: float x, y, z, intensity; 表示XYZ信息加上强度信息的类型。
pcl::PointCloud<pcl::PointXYZI>::Ptr boundingbox(new pcl::PointCloud<pcl::PointXYZI>)
for (size_t i = 0; i < boundingbox->points.size(); ++i)
{
std::cout << "polygons:" + std::to_string(i) + ": "
<< surface_hull->points[i].x << ","
<< surface_hull->points[i].y << ","
<< surface_hull->points[i].z << ","
<< std::endl;
}
PCL:点云滤波
https://blog.csdn.net/weixin_46098577/article/details/114385690
一、获取固定区域点云
(一)通过凸包
凸包和凹包的概念: https://blog.csdn.net/com1098247427/article/details/120664905
凸包分割点云:https://blog.csdn.net/qq_45006390/article/details/119107777
使用方法:通过设置一系列点(boundingbox)来划定一个区域,获取输入点云在这个区域中的点。boundingbox这些点围成的可以是一个三维图形也可以是一个平面图形。
1.如果boundingbox设置为一个水平平面,setDimension和setDim函数设置为2 (两个函数维度必须设置相同),那么保留的点云为水平平面竖直向下扫过的立体点云。
2.如果boundingbox设置为一个三维立体图形,setDimension和setDim函数设置为3 (两个函数维度必须设置相同),那么保留的点云为设置的三维立体图形包含的点云。
void cropHull(pcl::PointCloud<pcl::PointXYZI>::Ptr cloud, pcl::PointCloud<pcl::PointXYZI>::Ptr boundingbox, pcl::PointCloud<pcl::PointXYZI>::Ptr objects)
{
pcl::PointCloud<pcl::PointXYZI>::Ptr surface_hull (new pcl::PointCloud<pcl::PointXYZI>);
//构造凸包
pcl::ConvexHull<pcl::PointXYZI> hull;//创建凸包对象
hull.setInputCloud(boundingbox); //设置输入点云:封闭区域顶点点云
hull.setDimension(3); //设置凸包维度
std::vector<pcl::Vertices> polygons;//设置Vertices类型的向量,用于保存凸包顶点
hull.reconstruct(*surface_hull, polygons);//计算凸包结果
std::cout << "polygons size: " << polygons.size() << std::endl;
//polygons中每个向量中保存的是每个顶点的索引,索引和surface_hull中的点对应
// for (size_t i = 0; i < boundingbox->points.size(); ++i)
// {
// std::cout << "polygons:" + std::to_string(i) + ": "
// << surface_hull->points[i].x << ","
// << surface_hull->points[i].y << ","
// << surface_hull->points[i].z << ","
// << std::endl;
// }
//CropHull滤波
pcl::CropHull<pcl::PointXYZI> CH;//创建CropHull滤波对象
CH.setDim(3); //设置维度,与凸包维度一致
CH.setInputCloud(cloud); //设置需要滤波的点云
CH.setHullIndices(polygons); //输入封闭区域的顶点
CH.setHullCloud(surface_hull); //输入封闭区域的形状
CH.filter(*objects); //执行CropHull滤波,存储结果于objects
}
//设置封闭范围顶点
pcl::PointCloud<pcl::PointXYZI>::Ptr boundingbox(new pcl::PointCloud<pcl::PointXYZI>);
pcl::PointXYZI point[8];
point[0].x = 1.8f;
point[0].y = 0.3f;
point[0].z = -1.0f;
point[0].intensity = 1.0f;
point[1].x = 1.8f;
point[1].y = 0.0f;
point[1].z = -1.0f;
point[1].intensity = 1.0f;
point[2].x = 2.0f;
point[2].y = 0.3f;
point[2].z = -1.0f;
point[2].intensity = 1.0f;
point[3].x = 2.0f;
point[3].y = 0.0f;
point[3].z = -1.0f;
point[3].intensity = 1.0f;
point[4].x = 2.0f;
point[4].y = 0.3f;
point[4].z = -0.5f;
point[4].intensity = 1.0f;
point[5].x = 2.0f;
point[5].y = 0.0f;
point[5].z = -0.5f;
point[5].intensity = 1.0f;
point[6].x = 1.8f;
point[6].y = 0.3f;
point[6].z = -0.5f;
point[6].intensity = 1.0f;
point[7].x = 1.8f;
point[7].y = 0.0f;
point[7].z = -0.5f;
point[7].intensity = 1.0f;
for(int i = 0; i < 8; i++)
{
std::cout << "i: " << std::to_string(i) << std::endl;
boundingbox->push_back(point[i]);
}
std::cout << "voxel_ptr size: " << voxel_ptr->size() << std::endl;
cropHull(voxel_ptr, boundingbox, crophull_ptr);
(二)通过pcl::CropBoxpcl::PointXYZI
//获取立方体内点
void CropBoxFilter(const PointCloudTypePtr& cloud_in, Eigen::Vector4f& min_point, Eigen::Vector4f& max_point,bool negative,PointCloudType& cloud_out)
{
cloud_out.clear();
if(cloud_in->empty())
return ;
pcl::CropBox<pcl::PointXYZI> clipper;
// min_point << bounds[0], bounds[2], bounds[4], 1.0;//cube的两个对角点
// max_point << bounds[1], bounds[3], bounds[5], 1.0;
clipper.setMin(min_point);
clipper.setMax(max_point);
clipper.setInputCloud(cloud_in);
clipper.setNegative(negative);//默认为false
clipper.filter(cloud_out);
pcl::PointIndices indexs;
clipper.getRemovedIndices (indexs);
}
min_point,max_point为cube的两个对角点
DBSCAN算法:
一、DBSCAN算法原理:
(1)DBSCAN通过检查数据集中每点的Eps邻域来搜索簇,如果点p的Eps邻域包含的点多于MinPts个,则创建一个以p为核心对象的簇;
(2)然后,DBSCAN迭代地聚集从这些核心对象直接密度可达的对象,这个过程可能涉及一些密度可达簇的合并;
(3)当没有新的点添加到任何簇时,该过程结束。
简单理解:在每个点的Eps邻域内拥有点的个数多于MinPts,那么认为p为核心对象簇的一员。即dbscan认为密度可达的点即为一个簇,这也是dbscan聚类的核心思想。根据密度可达的定义,在聚类过程中,除了第一个和最后一个点之外(可以为核心对象或者边界对象),其余点必须是核心对象才可以。
二、优缺点
优点:
(1)聚类速度快且能够有效处理噪声点和发现任意形状的空间聚类;
(2)与K-MEANS比较起来,不需要输入要划分的聚类个数;
(3)聚类簇的形状没有偏倚;
(4)可以在需要时输入过滤噪声的参数。
缺点:
(1)当数据量增大时,要求较大的内存支持I/O消耗也很大;
(2)当空间聚类的密度不均匀、聚类间距差相差很大时,聚类质量较差,因为这种情况下参数MinPts和Eps选取困难。
(3)算法聚类效果依赖与距离公式选取,实际应用中常用欧式距离,对于高维数据,存在“维数灾难”。
三、代码实现
// 依赖项:pcl1.9.1
//时间:2021.06.25
//功能:实现点云dbscan聚类
//
#pragma once
#include <iostream>
#include<string>
#include<vector>
#include<pcl/io/pcd_io.h>
#include<pcl/point_cloud.h>
#include<pcl/point_types.h>
#include<pcl/kdtree/kdtree_flann.h>
#include<pcl/visualization/cloud_viewer.h>
typedef pcl::PointXYZ pointT;
typedef pcl::PointCloud<pointT> cloud;
//const cloud::Ptr& cloud_in,输入点云
//std::vector<std::vector<int>> &clusters_index, 索引
//const double& r, Eps邻域半径,单位:m
//const int& size,Eps邻域包含的点的最小个数MinPts
bool dbscan(const cloud::Ptr& cloud_in, std::vector<std::vector<int>> &clusters_index, const double& r, const int& size)
{
if (!cloud_in->size())
return false;
//LOG()
pcl::KdTreeFLANN<pointT> tree;
tree.setInputCloud(cloud_in);
std::vector<bool> cloud_processed(cloud_in->size(), false);
for (size_t i = 0; i < cloud_in->points.size(); ++i)
{
if (cloud_processed[i])
{
continue;
}
std::vector<int>seed_queue;
//检查近邻数是否大于给定的size(判断是否是核心对象)
std::vector<int> indices_cloud;
std::vector<float> dists_cloud;
if (tree.radiusSearch(cloud_in->points[i], r, indices_cloud, dists_cloud) >= size)
{
seed_queue.push_back(i);
cloud_processed[i] = true;
}
else
{
//cloud_processed[i] = true;
continue;
}
int seed_index = 0;
while (seed_index < seed_queue.size())
{
std::vector<int> indices;
std::vector<float> dists;
if (tree.radiusSearch(cloud_in->points[seed_queue[seed_index]], r, indices, dists) < size)//函数返回值为近邻数量
{
//cloud_processed[i] = true;//不满足<size可能是边界点,也可能是簇的一部分,不能标记为已处理
++seed_index;
continue;
}
for (size_t j = 0; j < indices.size(); ++j)
{
if (cloud_processed[indices[j]])
{
continue;
}
seed_queue.push_back(indices[j]);
cloud_processed[indices[j]] = true;
}
++seed_index;
}
clusters_index.push_back(seed_queue);
}
// std::cout << clusters_index.size() << std::endl;
if (clusters_index.size())
return true;
else
return false;
}
int main()
{
cloud::Ptr cloud_in(new cloud);
std::vector<std::vector<int>> clusters_index;
pcl::io::loadPCDFile<pcl::PointXYZ>("2.pcd", *cloud_in);
dbscan(cloud_in, clusters_index, 0.1, 10);
std::cout << clusters_index.size() << std::endl;
pcl::PointCloud<pcl::PointXYZI> visual_cloud;
visual_cloud.width = cloud_in->size();
visual_cloud.height = 1;
visual_cloud.resize(cloud_in->size());
for (size_t i = 0; i < clusters_index.size(); ++i)
{
for (size_t j = 0; j < clusters_index[i].size(); ++j)
{
visual_cloud.points[clusters_index[i][j]].x = cloud_in->points[clusters_index[i][j]].x;
visual_cloud.points[clusters_index[i][j]].y = cloud_in->points[clusters_index[i][j]].y;
visual_cloud.points[clusters_index[i][j]].z = cloud_in->points[clusters_index[i][j]].z;
visual_cloud.points[clusters_index[i][j]].intensity = 20*(i+100);
//std::cout << clusters_index[i][j] << std::endl;
}
// std::cout << clusters_index[i].size() << std::endl;
}
pcl::visualization::CloudViewer viewer("DBSCAN cloud viewer.");
viewer.showCloud(visual_cloud.makeShared());
while (!viewer.wasStopped())
{
}
pcl::io::savePCDFile("dbscan.pcd", visual_cloud,true);
std::cout << "Hello World!\n";
}
参考:
1.https://blog.csdn.net/baidu_38172402/article/details/81915112
2.https://blog.csdn.net/xinxiangwangzhi_/article/details/118212814
自己用C++ 实现保存pcd、ply格式的点云文件
https://www.jianshu.com/p/10b96e52a996
pcd、ply格式都分别有ascii形式和二进制形式的保存方式。 ascii形式方便人阅读,但是文件体积稍微大一些。二进制形式文件体积小,但是人不易读
一、pcd
(一)ascii 形式
ascii 形式的PCD文件内容样式如下:
std::vector<PointXyzRgb<float>> points; // PointXyzRgb为自定义类,有x/y/z/r/g/b信息
points_len = points.size();
std::string pc_name = "./pointcloud" + std::to_string(cnt) + ".pcd";
std::ofstream fout_pc_name(pc_name);
fout_pc_name << "# .PCD v0.7 - Point Cloud Data file format" << std::endl;
fout_pc_name << "VERSION 0.7" << std::endl;
fout_pc_name << "FIELDS x y z" << std::endl;
fout_pc_name << "SIZE 4 4 4" << std::endl;
fout_pc_name << "TYPE F F F" << std::endl;
fout_pc_name << "COUNT 1 1 1" << std::endl;
fout_pc_name << "WIDTH " << points_len << std::endl;
fout_pc_name << "HEIGHT 1" << std::endl;
fout_pc_name << "VIEWPOINT 0 0 0 1 0 0 0" << std::endl;
fout_pc_name << "POINTS " << points_len << std::endl;
fout_pc_name << "DATA ascii" << std::endl;
for (int n = 0; n < points_len; n++) {
fout_pc_name << points[n].x << " " << points[n].y << " " << points[n].z << std::endl;
}
fout_pc_name.close();
如果需要颜色,则在文件头多增加几行,for循环里加上rgb的信息
(二)二进制
二进制形式的PCD文件内容样式如下:
参照这个内容样式可写C++代码如下:
std::vector<PointXyzRgb<float>> points; // 里面存有点云。 PointXyzRgb为自定义类,有x/y/z/r/g/b信息
points_len = points.size();
std::string pc_name = "./pointcloud" + std::to_string(cnt) + ".pcd";
FILE *fp = fopen(pc_name.c_str(), "wb");
fprintf(fp, "# .PCD v0.7 - Point Cloud Data file format\n");
fprintf(fp, "VERSION 0.7\n");
fprintf(fp, "FIELDS x y z\n");
fprintf(fp, "SIZE 4 4 4\n");
fprintf(fp, "TYPE F F F\n");
fprintf(fp, "COUNT 1 1 1\n");
fprintf(fp, "WIDTH %d\n", points_len);
fprintf(fp, "HEIGHT 1\n");
fprintf(fp, "VIEWPOINT 0 0 0 1 0 0 0\n");
fprintf(fp, "POINTS %d\n", points_len);
fprintf(fp, "DATA binary\n");
for (int n = 0; n < points_len; n++) {
float tmp2[3] = { points[n].x, points[n].y, points[n].z };
fwrite(tmp2, sizeof(float), 3, fp);
}
fclose(fp);
如果需要颜色,则在文件头多增加几行,for循环里加上rgb的信息
二、PLY
(一)ascii 形式
ascii 形式的PLY文件内容样式如下:
参照这个内容样式可写C++代码如下:
std::vector<PointXyzRgb<float>> points; // 里面存有点云。 PointXyzRgb为自定义类,有x/y/z/r/g/b信息
points_len = points.size();
std::string pc_name = "./pointcloud" + std::to_string(cnt) + ".ply";
std::ofstream fout_pc_name(pc_name);
fout_pc_name << "ply" << std::endl;
fout_pc_name << "format ascii 1.0" << std::endl;
fout_pc_name << "element vertex " << points_len << std::endl;
fout_pc_name << "property float x" << std::endl;
fout_pc_name << "property float y" << std::endl;
fout_pc_name << "property float z" << std::endl;
fout_pc_name << "element face 0" << std::endl;
fout_pc_name << "property list uchar int vertex_index" << std::endl;
fout_pc_name << "end_header" << std::endl;
for (int n = 0; n < points_len; n++) {
fout_pc_name << points[n].x << " " << points[n].y << " " << points[n].z << std::endl;
}
fout_pc_name.close();
需要RGB信息,则如下:
std::vector<PointXyzRgb<float>> points; // 里面存有点云。 PointXyzRgb为自定义类,有x/y/z/r/g/b信息
points_len = points.size();
std::string pc_name = "./pointcloud" + std::to_string(cnt) + ".ply";
std::ofstream fout_pc_name(pc_name);
fout_pc_name << "ply" << std::endl;
fout_pc_name << "format ascii 1.0" << std::endl;
fout_pc_name << "element vertex " << points_len << std::endl;
fout_pc_name << "property float x" << std::endl;
fout_pc_name << "property float y" << std::endl;
fout_pc_name << "property float z" << std::endl;
fout_pc_name << "property uchar red" << std::endl;
fout_pc_name << "property uchar green" << std::endl;
fout_pc_name << "property uchar blue" << std::endl;
fout_pc_name << "element face 0" << std::endl;
fout_pc_name << "property list uchar int vertex_index" << std::endl;
fout_pc_name << "end_header" << std::endl;
for (int n = 0; n < points_len; n++) {
fout_pc_name << points[n].x << " " << points[n].y << " " << points[n].z << " " <<
points[n].r << " " << points[n].g << " " << points[n].b << std::endl;
}
fout_pc_name.close();
(二)二进制
二进制形式的PLY文件内容样式如下:
参照这个内容样式可写C++代码如下:
void saveBinPts(std::vector<float> &Pts, char *fileName)
{
int elementNum = Pts.size() / 3;
FILE *fp = fopen(fileName, "wb");
fprintf(fp, "ply\n");
fprintf(fp, "format binary_little_endian 1.0\n");
fprintf(fp, "comment File generated\n");
fprintf(fp, "element vertex %d\n", elementNum);
fprintf(fp, "property float x\n");
fprintf(fp, "property float y\n");
fprintf(fp, "property float z\n");
fprintf(fp, "end_header\n");
for (int i = 0; i < elementNum; i++) {
float tmp2[3] = { Pts[3 * i + 0], Pts[3 * i + 1], Pts[3 * i + 2] };
fwrite(tmp2, sizeof(float), 3, fp);
}
fclose(fp);
}
ply各字段含义说明
http://paulbourke.net/dataformats/ply/
http://gamma.cs.unc.edu/POWERPLANT/papers/ply.pdf