3D点云的噪声滤波在激光雷达领域往往是最常见也同样是最容易忽略的地方,在实际应用场景下往往很容易产生噪声点,比如点云灰尘、雨水、雪雾等等。而常见的3D点云去噪方法往往无法根除,因此需要结合场景及激光雷达的特点来设计不同的噪声滤波器,从而满足实际需求,废话不多说,本文主要采用PCL来实现噪声过滤。
首先向各位引荐一片关于点云去噪的论文:《Design of Dust-Filtering Algorithms for LiDAR Sensors Using Intensity and Range Information in Off-Road Vehicles》,该论文简单分析了各个噪声过滤方式的特点。
注意:点云PCL内已经封装好的滤波器就不再赘述了。
首先是 D R O R \ DROR DROR 滤波器,该滤波器本质上依然是由半径滤波构成,所以原理就不赘述了,其中的特点就是将半径滤波器中的半径参数动态化,通过计算点到原点的距离来约束搜索半径的参数,而其 α 、 Ф \ α、Ф α、Ф 系数可以通过激光雷达的分辨率以及手动设计来确定。
基本流程就不多介绍了,相信很容易看懂,直接上代码了、
/**
* @brief DROR_filter
* @param input_point_cloud Enter the point cloud to filter
* @param output_point_cloud Output filtered point cloud
* @param raise_dust_point_cloud Output noise point and dust point cloud
* @param min_search_radius Minimum search radius
* @param point_num_threshold Minimum number of adjacent points
* @param search_factor Coefficient factor of search radius
*/
void NoiseFilter::DROR_filter(const PointCloudPtr& input_point_cloud,
PointCloudPtr& output_point_cloud,
PointCloudPtr& raise_dust_point_cloud,
const double & min_search_radius,
const int & point_num_threshold,
const double & search_factor){
if (input_point_cloud->empty()) return;
/*Create Kdtree Searcher*/
pcl::KdTreeFLANN<Point>::Ptr kd_tree(new pcl::KdTreeFLANN<Point>());
kd_tree->setInputCloud(input_point_cloud);
std::vector<int> noise_cloud_indices(input_point_cloud->size());
std::vector<int> filter_cloud_indices(input_point_cloud->size());
#pragma omp parallel for num_threads(4)
for (size_t i = 0; i < input_point_cloud->points.size(); ++i){
Point point = input_point_cloud->points[i];
double search_radius_dynamic = std::sqrt(std::pow(point.x, 2) + std::pow(point.y, 2));
if (search_radius_dynamic < min_search_radius) search_radius_dynamic = min_search_radius;
else search_radius_dynamic *= search_factor ;
/*Radius Search Around Points*/
std::vector<int> point_idx_radius_search;
std::vector<float> point_radius_squared_distance;
int point_neighbors = kd_tree->radiusSearch(point,
search_radius_dynamic,
point_idx_radius_search,
point_radius_squared_distance);
if (point_neighbors < point_num_threshold)
noise_cloud_indices[i] = i;
else filter_cloud_indices[i] = i;
}
noise_cloud_indices.erase(remove(noise_cloud_indices.begin(),
noise_cloud_indices.end(),0),
noise_cloud_indices.end());
filter_cloud_indices.erase(remove(filter_cloud_indices.begin(),
filter_cloud_indices.end(),0),
filter_cloud_indices.end());
pcl::copyPointCloud(*input_point_cloud, noise_cloud_indices,*raise_dust_point_cloud);
pcl::copyPointCloud(*input_point_cloud, filter_cloud_indices,*output_point_cloud);
return;
}
上述的 D R O R \ DROR DROR 滤波器是通过利用点云的几何信息来进行滤波处理的,在实际状态下,灰尘、雨水、雪雾等点云的反光强度往往都会低于其他场景的反光强度,因此有根据反光强度来设计滤波器的,也就是Low Intensity Outlier Removal。
该滤波器适用于带有轻度灰尘、雨水等场景。
/**
* @brief LIOR_filter
* @param input_point_cloud Enter the point cloud to filter
* @param output_point_cloud Output filtered point cloud
* @param raise_dust_point_cloud Output noise point and dust point cloud
* @param search_radius search radius
* @param point_num_threshold Minimum number of adjacent points
* @param threshold_intensity Intensity threshold of point cloud
*/
void NoiseFilter::LIOR_filter(const PointCloudPtr& input_point_cloud,
PointCloudPtr& output_point_cloud,
PointCloudPtr& raise_dust_point_cloud,
const double & search_radius,
const int & point_num_threshold,
const int & threshold_intensity){
if (input_point_cloud->empty()) return;
/*Create Kdtree Searcher*/
pcl::KdTreeFLANN<Point>::Ptr kd_tree(new pcl::KdTreeFLANN<Point>());
kd_tree->setInputCloud(input_point_cloud);
std::vector<int> noise_cloud_indices(input_point_cloud->size());
std::vector<int> filter_cloud_indices(input_point_cloud->size());
#pragma omp parallel for num_threads(4)
for (size_t i = 0; i < input_point_cloud->points.size(); ++i){
Point point = input_point_cloud->points[i];
if (point.intensity >threshold_intensity){
filter_cloud_indices[i] = i;
continue;
}
/*Radius Search Around Points*/
std::vector<int> point_idx_radius_search;
std::vector<float> point_radius_squared_distance;
int point_neighbors = kd_tree->radiusSearch(point, search_radius,
point_idx_radius_search,
point_radius_squared_distance);
if (point_neighbors < point_num_threshold) noise_cloud_indices[i] = i;
else filter_cloud_indices[i] = i;
}
noise_cloud_indices.erase(remove(noise_cloud_indices.begin(),
noise_cloud_indices.end(),0),
noise_cloud_indices.end());
filter_cloud_indices.erase(remove(filter_cloud_indices.begin(),
filter_cloud_indices.end(),0),
filter_cloud_indices.end());
pcl::copyPointCloud(*input_point_cloud, noise_cloud_indices,*raise_dust_point_cloud);
pcl::copyPointCloud(*input_point_cloud, filter_cloud_indices,*output_point_cloud);
return;
}
这个滤波器是 L I O R \ LIOR LIOR 滤波器的升级版,也就是加入动态半径参数的设定,从而提高搜索效率。该滤波器同样仅适用于轻度灰尘等场景。
/**
* @brief LIOR_filter Low-Intensity Dynamic Radius Outlier Removal
* @param input_point_cloud Enter the point cloud to filter
* @param output_point_cloud Output filtered point cloud
* @param raise_dust_point_cloud Output noise point and dust point cloud
* @param min_search_radius Minimum search radius
* @param search_factor Coefficient factor of search radius
* @param point_num_threshold Minimum number of adjacent points
* @param threshold_intensity Intensity threshold of point cloud
*/
void NoiseFilter::LIDROR_filter(const PointCloudPtr& input_point_cloud,
PointCloudPtr& output_point_cloud,
PointCloudPtr& raise_dust_point_cloud,
const double & min_search_radius,
const double & search_factor,
const int & point_num_threshold,
const int & threshold_intensity){
if (input_point_cloud->empty()) return;
/*Create Kdtree Searcher*/
pcl::KdTreeFLANN<Point>::Ptr kd_tree(new pcl::KdTreeFLANN<Point>());
kd_tree->setInputCloud(input_point_cloud);
std::vector<int> noise_cloud_indices(input_point_cloud->size());
std::vector<int> filter_cloud_indices(input_point_cloud->size());
#pragma omp parallel for num_threads(4)
for (size_t i = 0; i < input_point_cloud->points.size(); ++i){
Point point = input_point_cloud->points[i];
if (point.intensity > threshold_intensity){
filter_cloud_indices[i] = i;
continue;
}
/*Set Dynamic Search Radius*/
double search_radius_dynamic = std::sqrt(std::pow(point.x, 2) + std::pow(point.y, 2));
if (search_radius_dynamic < min_search_radius) search_radius_dynamic = min_search_radius;
else search_radius_dynamic *= search_factor ;
/*Radius Search Around Points*/
std::vector<int> point_idx_radius_search;
std::vector<float> point_radius_squared_distance;
int point_neighbors = kd_tree->radiusSearch(point, search_radius_dynamic,
point_idx_radius_search,
point_radius_squared_distance);
if (point_neighbors < point_num_threshold) noise_cloud_indices[i] = i;
else filter_cloud_indices[i] = i;
}
noise_cloud_indices.erase(remove(noise_cloud_indices.begin(),
noise_cloud_indices.end(),0),
noise_cloud_indices.end());
filter_cloud_indices.erase(remove(filter_cloud_indices.begin(),
filter_cloud_indices.end(),0),
filter_cloud_indices.end());
pcl::copyPointCloud(*input_point_cloud, noise_cloud_indices,*raise_dust_point_cloud);
pcl::copyPointCloud(*input_point_cloud, filter_cloud_indices,*output_point_cloud);
return;
}
上述滤波方法都是通过遍历整个点云并通过半径搜索来进行过滤,这种方式在处理大型数据时,计算量非常大,因此我们可以结合局部分块的方法来设计新的滤波器,也就是借助PCL内的八叉树Octree的数据结构来提高过滤的效率。设计出Low Intensity Filtering Based on Octree Local Statistics过滤方式。
整个设计思路如下:
判定的条件可以参考 L I O R \ LIOR LIOR 滤波器。其中统计的方式可以简单设定为: s c a l e l o w I n t e n s i t y = n u m l o w I n t e n s i t y / n u m o c t r e e \ scale_{low Intensity} = num_{low Intensity} / num_{octree} scalelowIntensity=numlowIntensity/numoctree
那么可以很容易设计出该算法代码:
/**
- @brief LIOLS_filter Low Intensity Filtering Based on Octree Local Statistics
- @param input_point_cloud Enter the point cloud to filter
- @param output_point_cloud Output filtered point cloud
- @param raise_dust_point_cloud Output noise point and dust point cloud
- @param octree_size Octree size
- @param min_low_Intensity_scale Statistical coefficient of low intensity information
- @param point_num_threshold Minimum number of adjacent points
- @param threshold_intensity Intensity threshold of point cloud
*/
void NoiseFilter::LIOLS_filter(const PointCloudPtr& input_point_cloud,
PointCloudPtr& output_point_cloud,
PointCloudPtr& raise_dust_point_cloud,
const double & octree_size,
const double & min_low_Intensity_scale,
const int & point_num_threshold,
const int & threshold_intensity){
if (input_point_cloud->empty()) return;
/*Grid blocking*/
pcl::octree::OctreePointCloud<Point> octree(octree_size);
octree.setInputCloud(input_point_cloud);
octree.addPointsFromInputCloud(); // Constructing octree
pcl::PointIndices::Ptr outliners(new pcl::PointIndices());
for (auto iter = octree.leaf_begin(); iter != octree.leaf_end(); ++iter){
std::vector<int> vec_point_index = iter.getLeafContainer().getPointIndicesVector();
/*Noise point*/
if(vec_point_index.size() < point_num_threshold){
outliners->indices.insert( outliners->indices.end(), vec_point_index.begin(), vec_point_index.end());
continue;
}
/*Statistical octree internal strength*/
int low_intensity_point_num = 0;
for (size_t i = 0; i < vec_point_index.size(); ++i){
if (input_point_cloud->points[vec_point_index[i]].intensity <= threshold_intensity){
low_intensity_point_num++;
}
}
double low_Intensity_scale = low_intensity_point_num / vec_point_index.size();
if (low_Intensity_scale > min_low_Intensity_scale){
outliners->indices.insert( outliners->indices.end(), vec_point_index.begin(), vec_point_index.end());
continue;
}
}
pcl::ExtractIndices<Point> extract;
extract.setInputCloud(input_point_cloud);
extract.setIndices(outliners);
extract.setNegative(false);
extract.filter(*raise_dust_point_cloud);
extract.setNegative(true);
extract.filter(*output_point_cloud);
return;
}
可以参考 L I D R O R \ LIDROR LIDROR 滤波器的设计思路,那么可以不可以将 L I O L S \ LIOLS LIOLS 滤波器与 D R O R \ DROR DROR 滤波器结合起来呢,答案当然显而易见。那么怎么设计出合理的基于八叉树下的动态滤波器,其中重点需要考虑判定条件。由于是自己的一些设计想法,大家可以根据自己的场景来参考一下。本文的设计思路如下:
与 L I O L S \ LIOLS LIOLS 滤波器不相同的是 T h r e s h o l d i n t e n s i t y \ Threshold_{intensity} Thresholdintensity并不是人为设定的固定值,而是通过计算设定的动态阈值。其中计算的方式为:
T h i n t e n s i t y = m a x i − ( m a x i − m i n i ) r a n g e m a x × P t x 2 + P t y 2 \ Th_{intensity} = max_{i} -\tfrac{(max_{i}-min_{i})}{range_{max}} \times\sqrt{Pt_{x}^2 + Pt_{y}^2} Thintensity=maxi−rangemax(maxi−mini)×Ptx2+Pty2
其中 m a x i 、 m i n i \ max_{i} 、min_{i} maxi、mini分别标定人为设定的噪声点云最大反光强度与最小反光强度。 d i s t a n c e m a x \ distance_{max} distancemax则表示为设定的反光强度的最大过滤范围。
struct OLIDRORParam
{
int min_point_cloud_num = 5;
double octree_depth = 0.5;
double min_low_Intensity_scale = 0.3;
double min_search_radius = 0.1;
double search_factor = 0.014;
}olidror_param_;
enum OctreeState { free_space, hight_intensity, low_intensity };
/**
* @brief OLIDROR_filter Low-Intensity Dynamic Radius Outlier Removal base on Octree
* @param input_point_cloud Enter the point cloud to filter
* @param output_point_cloud Output filtered point cloud
* @param raise_dust_point_cloud Output noise point and dust point cloud
* @param filter_param Corresponding filtering OLIDROR parameters
*/
float NoiseFilter::GetIntensityThreshold(const Point& point,
const int & max_intensity_threshold,
const int & min_intensity_threshold,
const int & high_intensity_filter_range) {
double distance = std::sqrt(std::pow(point.x, 2) + std::pow(point.y, 2));
int high_intensity_filter_threshold = max_intensity_threshold;
if (distance <= high_intensity_filter_range) {
high_intensity_filter_threshold = (max_intensity_threshold - min_intensity_threshold)
* - 1.0 / high_intensity_filter_range * distance + max_intensity_threshold;
}
return std::max(high_intensity_filter_threshold,min_intensity_threshold);
}
void NoiseFilter::OLIDROR_filter(const PointCloudPtr& input_point_cloud,
PointCloudPtr& output_point_cloud,
PointCloudPtr& raise_dust_point_cloud,
const OLIDRORParam & filter_param){
if (input_point_cloud->empty()) return;
/*Octree blocking processing*/
AlignedPointTVector octree_center_list_arg;
pcl::octree::OctreePointCloudSearch<Point> octree(filter_param.octree_depth);
octree.setInputCloud(input_point_cloud);
octree.addPointsFromInputCloud();
octree.getOccupiedVoxelCenters(octree_center_list_arg);
//std::cerr << "the number of voxel are : " << voxel_center_list_arg.size() << std::endl;
PointCloudPtr octree_center_point_cloud(new PointCloud);
octree_center_point_cloud->resize(octree_center_list_arg.size());
std::vector<std::vector<int>> octree_center_idx_vec;
octree_center_idx_vec.resize(octree_center_list_arg.size());
#pragma omp parallel for num_threads(4)
for (size_t i = 0; i < octree_center_list_arg.size(); ++i){
Point octree_center_point;
octree_center_point.x = octree_center_list_arg[i].x;
octree_center_point.y = octree_center_list_arg[i].y;
octree_center_point.z = octree_center_list_arg[i].z;
octree_center_point.intensity = OctreeState::hight_intensity;
/*Extraction of point cloud features inside octree*/
std::vector<int> voxel_point_idx_vec;
if (octree.voxelSearch(octree_center_point, voxel_point_idx_vec)){
if (voxel_point_idx_vec.size() <= 5){
octree_center_point.intensity = OctreeState::free_space;
continue;
}else{
/*Statistical intensity information*/
int low_intensity_num = 0;
double voxel_point_intensity_threshold = GetIntensityThreshold(octree_center_point);
if (octree_center_list_arg[i].intensity <= voxel_point_intensity_threshold)
low_intensity_num++;
for (size_t j = 0; j < voxel_point_idx_vec.size(); ++j){
Point octree_voxel_point = input_point_cloud->points[voxel_point_idx_vec[j]];
if (octree_voxel_point.intensity <= voxel_point_intensity_threshold){
low_intensity_num++;
}
}
double low_Intensity_scale = (low_intensity_num *1.0) / (voxel_point_idx_vec.size() + 1);
if ( low_Intensity_scale > filter_param.min_low_Intensity_scale) {
octree_center_point.intensity = OctreeState::low_intensity;
}
}
}else{
octree_center_point.intensity = OctreeState::free_space;
}
octree_center_idx_vec[i] = voxel_point_idx_vec;
octree_center_point_cloud->points[i] = octree_center_point;
}
/*Create Kdtree Searcher*/
pcl::KdTreeFLANN<Point>::Ptr kd_tree(new pcl::KdTreeFLANN<Point>());
kd_tree->setInputCloud(octree_center_point_cloud);
std::vector<int> noise_cloud_indices(octree_center_point_cloud->size());
#pragma omp parallel for num_threads(4)
for (size_t i = 0; i < octree_center_point_cloud->points.size(); ++i){
Point point = octree_center_point_cloud->points[i];
if (point.intensity == OctreeState::free_space){
noise_cloud_indices[i] = i;
continue;
}
/*Set Dynamic Search Radius*/
double search_radius_dynamic = std::sqrt(std::pow(point.x, 2) + std::pow(point.y, 2)) *
filter_param.search_factor;
double min_search_radius = filter_param.min_search_radius;
if (min_search_radius < filter_param.octree_depth)
min_search_radius *= 2* std::sqrt(std::pow(filter_param.octree_depth, 2) * 2) + 0.1;
if (search_radius_dynamic < min_search_radius)
search_radius_dynamic = min_search_radius;
/*Radius Search Around Points*/
std::vector<int> point_idx_radius_search;
std::vector<float> point_radius_squared_distance;
int point_neighbors = kd_tree->radiusSearch(point, search_radius_dynamic,
point_idx_radius_search,
point_radius_squared_distance);
if (point_neighbors < 2 && point.intensity != OctreeState::hight_intensity){
noise_cloud_indices[i] = i;
continue;
}
int low_intensity_region = 0;
if (point.intensity == OctreeState::low_intensity) low_intensity_region++;
for (size_t j = 0; j < point_neighbors; ++j){
if (octree_center_point_cloud->points[point_idx_radius_search[j]].intensity !=
OctreeState::hight_intensity){
low_intensity_region++;
}
}
double low_intensity_region_scale = (low_intensity_region * 1.0) / (point_neighbors + 1);
if((low_intensity_region_scale > ( filter_param.min_low_Intensity_scale - 0.3) &&
point.intensity == OctreeState::low_intensity) ||
(low_intensity_region_scale > (filter_param.min_low_Intensity_scale) &&
point.intensity == OctreeState::hight_intensity)){
noise_cloud_indices[i] = i;
}
}
/*classification*/
pcl::PointIndices::Ptr noise_outliners(new pcl::PointIndices());
for (size_t i = 0; i < noise_cloud_indices.size(); ++i){
if(noise_cloud_indices[i] != 0){
noise_outliners->indices.push_back(noise_cloud_indices[i]);
noise_outliners->indices.insert(noise_outliners->indices.end(),
octree_center_idx_vec[noise_cloud_indices[i]].begin(),
octree_center_idx_vec[noise_cloud_indices[i]].end());
}
}
pcl::ExtractIndices<Point> extract;
extract.setInputCloud(input_point_cloud);
extract.setIndices(noise_outliners);
extract.setNegative(false);
extract.filter(*raise_dust_point_cloud);
extract.setNegative(true);
extract.filter(*output_point_cloud);
return;
}
…