本篇博客,记录自己学习处理3D-Lidar点云数据目前遇到的算法及本工程使用的算法,作为记录有不足的地方,烦请大家批评指正、指导。
参考Being_young大佬文章:https://blog.csdn.net/u013019296/article/details/70052319
对于原始点的滤波,觉得目前PCL库已经做了很好的处理,PCL库中给出几种需要进行点云滤波处理情况:
对应解决方案:
PCL库直通滤波例子
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);
/**
* 1、创建直通滤波器的对象,设立参数,滤波字段名被设置为Z轴方向
* 2、通过函数setFilterLimitsNegative,过滤所有点的Z轴坐标不在该范围内的点过滤掉(false)或保留(true)
*/
// 设置滤波器对象
pcl::PassThrough<pcl::PointXYZ> pass; // 声明滤波器对象
pass.setInputCloud (cloud); // 设置输入点云(ptr指针)
pass.setFilterFieldName ("z"); // 设置过滤x,y,z哪个方向
pass.setFilterLimits (-1.8, 0.5); // 设置在过滤方向的范围
pass.setFilterLimitsNegative (false); // 设置保留范围内还是过滤掉范围内
pass.filter (*cloud_filtered); // 执行滤波,保存过滤结果到cloud_filtered(ptr指针)
PCL库体素下采样例子
本工程使用的滤波器
/**
* 创建pcl::VoxelGrid滤波器对象,并设置叶素大小为1cm
*/
pcl::VoxelGrid<pcl::PCLPointCloud2> vg; // 创建滤波对象
vg.setInputCloud (cloud); // 输入点云(ptr指针)
vg.setLeafSize (0.01f, 0.01f, 0.01f); // 设置体素体积为1cm的立方体
vg.filter (*cloud_filtered); // 执行滤波处理,存储输出
PCL库统计滤波器例子
/**
* 1、创建统计滤波器statisticalOutlierRemoval对象
* 2、设置在进行统计时考虑查询点临近点数
*/
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor; // 创建滤波器对象
sor.setInputCloud(cloud); // 输入点云(ptr指针)
sor.setMeanK(50); // 设置在进行统计时考虑查询点临近点数
sor.setStddevMulThresh(1.0); // 设置判断是否为离群点的阀值
sor.filter(*cloud_filtered); // 执行滤波处理,存储输出
本工程采用快速分割
1、根据Lidar旋转角度和角分辨率分割扇形区域,sector_num = 360° / 0.18°;0.18是水平角分辨率;
2、根据object的x,y值计算 θ \theta θ角,通过向下取整的方式把点运数据投影到不同扇形区域内,然后每个扇形区域内的点根据半径距离也向下取整并从小到大排列;
3、设置点与点之间最大坡度 point2point_max_slope = 5° 和地面坡度 ground_max_slope = 8°,根据实际情况标定;
4、计算点与点之间的高度阈值 point_height_threshold 和 ground_height_threshold 以及 point_distance;
计算开始之前,需要将原有点云数据转类型,新类型除包含原有数据内容,还添加了投影在xy平面的半径,X轴方向上的夹角 θ \theta θ,扇形分割的序列索引,所在扇形根据半径从小到大重排列的序列索引,以及原始点云序列索引
struct PointXYZRTCO
{
pcl::PointXYZ point; // 保存原始点云数据
float radius; // cylindric coords on XY Plane
float theta; // angle deg on XY plane
size_t radial_div; // index = 保存点云数据属于哪个扇形
size_t concentric_div; // index = 保存该扇形点云的重排列序列
size_t original_index; // index = 保存原始点云序列
};
对LIDAR的扫描区域进行扇形分割,每个扇形区域的点云将组成一条射线
对扇形区域内的点云,进行重排列:
1、根据 floor( θ \theta θ / 0.18°),对点云的射线所以归属进行取值;
2、然后,根据每个点云数据的x,y坐标值计算每个点云所在圆弧的半径r,根据半径r,将原有射线上的点云进行从小到大的顺序排列;
根据每条射线的点云分布,对其进行地面分割:
1、首先,对点与点之间坡度以及地面总体坡度进行估计,该估计值属于经验值,对于不同雷达,在分割数据时,需要进行标定调参;
2、从射线的最小端开始,计算点与点之间之间的距离,和高度差的阈值;
当前点高度属于前一个点高度 ± 点与点高度阈值情况,即
current_point.z ∈ (pre_point.z - height_threshold , pre_point.z + height_threshold)
:
1、情况一,前个点属于非地面的情况:
- 如果,当前点的高度也属于负的Lidar高度 ± 地面高度通用阈值,即
current_point.z ∈ (- sensor_height - general_height_threshold , - sensor_height + general_height_threshold)
:得到当前点为地面点;- 否则,也属于非地面点。
当前点高度属于前一个点高度 ± 高度阈值情况,即
current_point.z ∈ (pre_point.z - height_threshold , pre_point.z + height_threshold)
:
2、情况二,前个点属于地面的情况:
- 此时,当前点也为地面点。
当前点高度不属于前一个点高度 ± 高度阈值情况,即
current_point.z ∉ (pre_point.z - height_threshold , pre_point.z + height_threshold)
:
1、情况一,当前点的高度属于负的Lidar高度 ± 地面高度通用阈值,即current_point.z ∈ (- sensor_height - general_height_threshold , - sensor_height + general_height_threshold)
&& 点与点之间的距离很大p2p_distance > reclass_distance_threshold
:得到当前点为地面点;
2、其他情况,当前点都为非地面点。
分割后,地面点云:
分割后,非地面点云:
本工程基于非地面分割点云数据进行欧式聚类
对于2/3的方式,下一步可以尝试优化3D检测跟踪
PCL库使用kd-tree加快欧式聚类算法进度
Kd-tree详解:(1)
欧式聚类算法流程详解: (1) (2)
由于点云数据在距离增加的时候,离散度在不断增加,所以考虑这种情况,在下面传参的时候根据距离判断传入不同的容忍度:
std::vector<pcl::PointIndices> local_indices;
pcl::EuclideanClusterExtraction<PointT> euclid;
euclid.setInputCloud(in_ground_cloud_2d);
euclid.setClusterTolerance(in_max_cluster_distance[i]);
euclid.setMinClusterSize(50);
euclid.setMaxClusterSize(10000);
euclid.setSearchMethod(tree);
euclid.extract(local_indices);
KD-Tree 搜索过程关键点:
k = 3情况下,x,y,z三个方向的超平面选择方法,根据三个方向上数值方差最大值判断超平面选择顺序,根据均值选择二分点;
方差越大,数据分布越离散,选择最大的可以很好的将数据分割开来。
KD搜索流程,二分速度就是快:
图中对于给定的查询数据点m,须从KD-Tree的根结点开始进行比较,其中m(k)为当前结点划分维度k上数据点m对应的值,d为当前结点划分的阈值。若 m(k)小于d,则访问左子树;否则访问右子树,直至到达叶子结点Q。此时Q就是当m前最近邻点,而m与Q之间的距离就是当前最小距离Dmin 。随后沿着原搜索路径回退至根结点,若此过程中发现和m之间距离小于 Dmin的点,则须将未曾访问过的子节点均纳入搜索范畴,并及时更新最近邻点,直至所有的搜索路径都为空,整个基于KD-Tree结构的最近邻点查询过程便告完成。
欧式聚类流程:
对于欧式聚类来说,距离判断准则为欧氏距离 E u = s q r t ( ( x k − x i ) 2 + ( y k − y i ) 2 + ( z k − z i ) 2 + . . . ) Eu = sqrt((xk-xi)^2 + (yk-yi)^2 + (zk-zi)^2 + ...) Eu=sqrt((xk−xi)2+(yk−yi)2+(zk−zi)2+...)。对于空间某点P,通过KD-Tree近邻搜索算法找到k个离p点最近的点,这些点中距离小于设定阈值的便聚类到集合Q中。如果Q中元素的数目不在增加(或者小于最小搜索数目,或者大于最大搜索数目),整个聚类过程便结束;否则须在集合Q中选取p点以外的点,重复上述过程,直到Q中元素的数目不在增加为止。
对于Bounding Boxes的生成可以使用PCA主成分分析法生成更小的方框, 实现更高的预测结果精准性. 具体PCL实现可以查看这个链接:PCL-PCA.
Doing
在使用深度学习目标检测算法(PointRCNN)后,该3D多目标追踪系统不需要使用GPU且取得了最快处理速度(214.7FPS)。
本工程尝试使用欧式聚类的方式获取bbox,然后根据该目标检测结果进行多目标跟踪。
用最简单的过程模型来描述目标运动——恒定速度模型
从1D-IoU到2D-IoU推导
匹配前后帧的对象,中心思想为二分图查找 实现最大匹配数
中心思想就是使用每次检测结果的去估计