在獲取點雲數據時 ,由於設備精度,操作者經驗環境因素帶來的影響,以及電磁波的衍射特性,被測物體表面性質變化和數據拼接配準操作過程的影響,點雲數據中講不可避免的出現一些噪聲。在點雲處理流程中濾波處理作爲預處理的第一步,對後續的影響比較大,只有在濾波預處理中將噪聲點 ,離羣點,孔洞,數據壓縮等按照後續處理定製,才能夠更好的進行配準,特徵提取,曲面重建,可視化等後續應用處理,PCL中點雲濾波模塊提供了很多靈活實用的濾波處理算法,例如:雙邊濾波,高斯濾波,條件濾波,直通濾波,基於隨機採樣一致性濾波。雙邊濾波算法是通過取臨近採樣點和加權平均來修正當前採樣點的位置,從而達到濾波效果,同時也會有選擇剔除與當前採樣點“差異”太大的相鄰採樣點,從而保持原特徵的目的。
PCL中總結了幾種需要進行點雲濾波處理情況,這幾種情況分別如下:
(1) 點雲數據密度不規則需要平滑
(2) 因爲遮擋等問題造成離羣點需要去除
(3) 大量數據需要下采樣
(4) 噪聲數據需要去除
對應的方案如下:
(1)按照給定的規則限制過濾去除點
(2) 通過常用濾波算法修改點的部分屬性
(3)對數據進行下采樣
下面將分別舉例:
(1)在PCL 中使用直通濾波器對點雲進行濾波處理
#include "pch.h"
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
int
main(int argc, char** argv)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
//生成並填充點雲
cloud->width = 50;
cloud->height = 1;
cloud->points.resize(cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size(); ++i) //填充數據
{
cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
std::cerr << "Cloud before filtering: " << std::endl; //打印
for (size_t i = 0; i < cloud->points.size(); ++i)
std::cerr << " " << cloud->points[i].x << " "
<< cloud->points[i].y << " "
<< cloud->points[i].z << std::endl;
/************************************************************************************
創建直通濾波器的對象,設立參數,濾波字段名被設置爲Z軸方向,可接受的範圍爲(0.0,1.0)
即將點雲中所有點的Z軸座標不在該範圍內的點過濾掉或保留,這裏是過濾掉,由函數setFilterLimitsNegative設定
***********************************************************************************/
// 設置濾波器對象
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud); //設置輸入點雲
pass.setFilterFieldName("z"); //設置過濾時所需要點雲類型的Z字段
pass.setFilterLimits(0.0, 1000.0); //設置在過濾字段的範圍
//pass.setFilterLimitsNegative (true); //設置保留範圍內還是過濾掉範圍內
pass.filter(*cloud_filtered); //執行濾波,保存過濾結果在cloud_filtered
std::cerr << "Cloud after filtering: " << std::endl; //打印
for (size_t i = 0; i < cloud_filtered->points.size(); ++i)
std::cerr << " " << cloud_filtered->points[i].x << " "
<< cloud_filtered->points[i].y << " "
<< cloud_filtered->points[i].z << std::endl;
return (0);
}
(2)使用VoxelGrid濾波器對點雲進行下采樣
使用體素化網格方法實現下采樣,即減少點的數量 減少點雲數據,並同時保存點雲的形狀特徵,在提高配準,曲面重建,形狀識別等算法速度中非常實用,PCL是實現的VoxelGrid類通過輸入的點雲數據創建一個三維體素柵格,容納後每個體素內用體素中所有點的重心來近似顯示體素中其他點,這樣該體素內所有點都用一個重心點最終表示,對於所有體素處理後得到的過濾後的點雲,這種方法比用體素中心逼近的方法更慢,但是對於採樣點對應曲面的表示更爲準確。
#include "pch.h"
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
int main(int argc, char** argv)
{
pcl::PCLPointCloud2::Ptr cloud(new pcl::PCLPointCloud2());
pcl::PCLPointCloud2::Ptr cloud_filtered(new pcl::PCLPointCloud2());
//點雲對象的讀取
pcl::PCDReader reader;
reader.read("bunny.pcd", *cloud); //讀取點雲到cloud中
std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height
<< " data points (" << pcl::getFieldsList(*cloud) << ").";
/******************************************************************************
創建一個葉大小爲1cm的pcl::VoxelGrid濾波器,
**********************************************************************************/
pcl::VoxelGrid<pcl::PCLPointCloud2> sor; //創建濾波對象
sor.setInputCloud(cloud); //設置需要過濾的點雲給濾波對象
sor.setLeafSize(0.01f, 0.01f, 0.01f); //設置濾波時創建的體素體積爲1cm的立方體
sor.filter(*cloud_filtered); //執行濾波處理,存儲輸出
std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height
<< " data points (" << pcl::getFieldsList(*cloud_filtered) << ").";
pcl::PCDWriter writer;
writer.write("table_scene_lms400_downsampled.pcd", *cloud_filtered,
Eigen::Vector4f::Zero(), Eigen::Quaternionf::Identity(), false);
return (0);
}
(4)使用statisticalOutlierRemoval濾波器移除離羣點
使用統計分析技術,從一個點雲數據中集中移除測量噪聲點(也就是離羣點)比如:激光掃描通常會產生密度不均勻的點雲數據集,另外測量中的誤差也會產生稀疏的離羣點,使效果不好,估計局部點雲特徵(例如採樣點處法向量或曲率變化率)的運算複雜,這會導致錯誤的數值,反過來就會導致點雲配準等後期的處理失敗。
解決辦法:每個點的鄰域進行一個統計分析,並修剪掉一些不符合一定標準的點,稀疏離羣點移除方法基於在輸入數據中對點到臨近點的距離分佈的計算,對每一個點,計算它到它的所有臨近點的平均距離,,假設得到的結果是一個高斯分佈,其形狀是由均值和標準差決定,平均距離在標準範圍之外的點,可以被定義爲離羣點並可從數據中去除。
#include "pch.h"
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/statistical_outlier_removal.h>
int main(int argc, char** argv)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
// 定義讀取對象
pcl::PCDReader reader;
// 讀取點雲文件
reader.read<pcl::PointXYZ>("bunny.pcd", *cloud);
std::cerr << "Cloud before filtering: " << std::endl;
std::cerr << *cloud << std::endl;
// 創建濾波器,對每個點分析的臨近點的個數設置爲50 ,並將標準差的倍數設置爲1 這意味着如果一
//個點的距離超出了平均距離一個標準差以上,則該點被標記爲離羣點,並將它移除,存儲起來
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor; //創建濾波器對象
sor.setInputCloud(cloud); //設置待濾波的點雲
sor.setMeanK(50); //設置在進行統計時考慮查詢點臨近點數
//sor.setStddevMulThresh(1.0); //設置判斷是否爲離羣點的閥值
sor.setStddevMulThresh(0.00034); //設置判斷是否爲離羣點的閥值
sor.filter(*cloud_filtered); //存儲
std::cerr << "Cloud after filtering: " << std::endl;
std::cerr << *cloud_filtered << std::endl;
pcl::PCDWriter writer;
writer.write<pcl::PointXYZ>("test1_inliers.pcd", *cloud_filtered, false);
sor.setNegative(true);
sor.filter(*cloud_filtered);
writer.write<pcl::PointXYZ>("test1_outliers.pcd", *cloud_filtered, false);
return (0);
}
運行結果
( 2)使用參數化模型投影點雲
如何將點投影到一個參數化模型上(平面或者球體等),參數化模型通過一組參數來設定,對於平面來說使用其等式形式.在PCL中有特意存儲常見模型係數的數據結構。
#include "pch.h"
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/ModelCoefficients.h> //模型係數頭文件
#include <pcl/filters/project_inliers.h> //投影濾波類頭文件
int main(int argc, char** argv)
{
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_projected(new pcl::PointCloud<pcl::PointXYZ>);
//創建點雲並打印出來
cloud->width = 5;
cloud->height = 1;
cloud->points.resize(cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size(); ++i)
{
cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
std::cerr << "Cloud before projection: " << std::endl;
for (size_t i = 0; i < cloud->points.size(); ++i)
std::cerr << " " << cloud->points[i].x << " "
<< cloud->points[i].y << " "
<< cloud->points[i].z << std::endl;
// 填充ModelCoefficients的值,使用ax+by+cz+d=0平面模型,其中 a=b=d=0,c=1 也就是X——Y平面
//定義模型係數對象,並填充對應的數據
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients());
coefficients->values.resize(4);
coefficients->values[0] = coefficients->values[1] = 0;
coefficients->values[2] = 1.0;
coefficients->values[3] = 0;
// 創建ProjectInliers對象,使用ModelCoefficients作爲投影對象的模型參數
pcl::ProjectInliers<pcl::PointXYZ> proj; //創建投影濾波對象
proj.setModelType(pcl::SACMODEL_PLANE); //設置對象對應的投影模型
proj.setInputCloud(cloud); //設置輸入點雲
proj.setModelCoefficients(coefficients); //設置模型對應的係數
proj.filter(*cloud_projected); //投影結果存儲
std::cerr << "Cloud after projection: " << std::endl;
for (size_t i = 0; i < cloud_projected->points.size(); ++i)
std::cerr << " " << cloud_projected->points[i].x << " "
<< cloud_projected->points[i].y << " "
<< cloud_projected->points[i].z << std::endl;
return (0);
}
運行時候可能會報錯“Error C4996 'pcl::SAC_SAMPLE_SIZE': This map is deprecated and is kept only to prevent breaking existing user code. Starting from PCL 1.8.0 model sample size is a protected member of the SampleConsensusModel class ”
解決方法看這裏:https://blog.csdn.net/daidaiisdaidai/article/details/84430154
運行結果:
(5)從一個點雲中提取索引
本例將講解如何使用一個,基於某一分割算法提取點雲中的一個子集。
#include "pch.h"
#include <iostream>
#include <pcl/ModelCoefficients.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/extract_indices.h>
int
main(int argc, char** argv)
{
/**********************************************************************************************************
從輸入的.PCD 文件載入數據後,創建一個VOxelGrid濾波器對數據進行下采樣,在這裏進行下才樣是爲了加速處理過程,
越少的點意味着分割循環中處理起來越快
**********************************************************************************************************/
pcl::PCLPointCloud2::Ptr cloud_blob(new pcl::PCLPointCloud2), cloud_filtered_blob(new pcl::PCLPointCloud2);//申明濾波前後的點雲
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>), cloud_p(new pcl::PointCloud<pcl::PointXYZ>), cloud_f(new pcl::PointCloud<pcl::PointXYZ>);
// 讀取PCD文件
pcl::PCDReader reader;
reader.read("test1.pcd", *cloud_blob);
//統計濾波前的點雲個數
std::cerr << "PointCloud before filtering: " << cloud_blob->width * cloud_blob->height << " data points." << std::endl;
// 創建體素柵格下采樣: 下采樣的大小爲1cm
pcl::VoxelGrid<pcl::PCLPointCloud2> sor; //體素柵格下采樣對象
sor.setInputCloud(cloud_blob); //原始點雲
//sor.setLeafSize(0.01f, 0.01f, 0.01f); // 設置採樣體素大小
sor.setLeafSize(3.0f, 3.0f, 3.0f); // 設置採樣體素大小
sor.filter(*cloud_filtered_blob); //保存
// 轉換爲模板點雲
pcl::fromPCLPointCloud2(*cloud_filtered_blob, *cloud_filtered);
std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height << " data points." << std::endl;
// 保存下采樣後的點雲
pcl::PCDWriter writer;
writer.write<pcl::PointXYZ>("test1_downsampled.pcd", *cloud_filtered, false);
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients());
pcl::PointIndices::Ptr inliers(new pcl::PointIndices());
pcl::SACSegmentation<pcl::PointXYZ> seg; //創建分割對象
seg.setOptimizeCoefficients(true); //設置對估計模型參數進行優化處理
seg.setModelType(pcl::SACMODEL_PLANE); //設置分割模型類別
seg.setMethodType(pcl::SAC_RANSAC); //設置用哪個隨機參數估計方法
seg.setMaxIterations(1000); //設置最大迭代次數
seg.setDistanceThreshold(0.01); //判斷是否爲模型內點的距離閥值
// 設置ExtractIndices的實際參數
pcl::ExtractIndices<pcl::PointXYZ> extract; //創建點雲提取對象
int i = 0, nr_points = (int)cloud_filtered->points.size();
// While 30% of the original cloud is still there
while (cloud_filtered->points.size() > 0.3 * nr_points)
{
// 爲了處理點雲包含的多個模型,在一個循環中執行該過程並在每次模型被提取後,保存剩餘的點進行迭代
seg.setInputCloud(cloud_filtered);
seg.segment(*inliers, *coefficients);
if (inliers->indices.size() == 0)
{
std::cerr << "Could not estimate a planar model for the given dataset." << std::endl;
break;
}
// Extract the inliers
extract.setInputCloud(cloud_filtered);
extract.setIndices(inliers);
extract.setNegative(false);
extract.filter(*cloud_p);
std::cerr << "PointCloud representing the planar component: " << cloud_p->width * cloud_p->height << " data points." << std::endl;
std::stringstream ss;
ss << "test1_plane_" << i << ".pcd";
writer.write<pcl::PointXYZ>(ss.str(), *cloud_p, false);
// Create the filtering object
extract.setNegative(true);
extract.filter(*cloud_f);
cloud_filtered.swap(cloud_f);
i++;
}
return (0);
}
運行結果
(6)使用ConditionalRemoval 或RadiusOutlinerRemoval移除離羣點
如何在濾波模塊使用幾種不同的方法移除離羣點,對於ConditionalRemoval濾波器,可以一次刪除滿足對輸入的點雲設定的一個或多個條件指標的所有的數據點,RadiusOutlinerRemoval濾波器,它可以刪除在輸入點雲一定範圍內沒有至少達到足夠多近鄰的所有數據點。
關於RadiusOutlinerRemoval的理解,在點雲數據中,設定每個點一定範圍內周圍至少有足夠多的近鄰,不滿足就會被刪除
關於ConditionalRemoval 這個濾波器刪除點雲中不符合用戶指定的一個或者多個條件的數據點
#include "pch.h"
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
int
main (int argc, char** argv)
{
if (argc != 2) //確保輸入的參數
{
std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
exit(0);
}
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZ>);
//填充點雲
cloud->width = 5;
cloud->height = 1;
cloud->points.resize (cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size (); ++i)
{
cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0f);
cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0f);
cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0f);
}
if (strcmp(argv[1], "-r") == 0){
pcl::RadiusOutlierRemoval<pcl::PointXYZ> outrem; //創建濾波器
outrem.setInputCloud(cloud); //設置輸入點雲
outrem.setRadiusSearch(0.8); //設置半徑爲0.8的範圍內找臨近點
outrem.setMinNeighborsInRadius (2); //設置查詢點的鄰域點集數小於2的刪除
// apply filter
outrem.filter (*cloud_filtered); //執行條件濾波 在半徑爲0.8 在此半徑內必須要有兩個鄰居點,此點纔會保存
}
else if (strcmp(argv[1], "-c") == 0){
//創建條件限定的下的濾波器
pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond (new
pcl::ConditionAnd<pcl::PointXYZ> ()); //創建條件定義對象
//爲條件定義對象添加比較算子
range_cond->addComparison (pcl::FieldComparison<pcl::PointXYZ>::ConstPtr (new
pcl::FieldComparison<pcl::PointXYZ> ("z", pcl::ComparisonOps::GT, 0.0))); //添加在Z字段上大於0的比較算子
range_cond->addComparison (pcl::FieldComparison<pcl::PointXYZ>::ConstPtr (new
pcl::FieldComparison<pcl::PointXYZ> ("z", pcl::ComparisonOps::LT, 0.8))); //添加在Z字段上小於0.8的比較算子
// 創建濾波器並用條件定義對象初始化
pcl::ConditionalRemoval<pcl::PointXYZ> condrem;
condrem.setCondition (range_cond);
condrem.setInputCloud (cloud); //輸入點雲
condrem.setKeepOrganized(true); //設置保持點雲的結構
// 執行濾波
condrem.filter (*cloud_filtered); //大於0.0小於0.8這兩個條件用於建立濾波器
}
else{
std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
exit(0);
}
std::cerr << "Cloud before filtering: " << std::endl;
for (size_t i = 0; i < cloud->points.size (); ++i)
std::cerr << " " << cloud->points[i].x << " "
<< cloud->points[i].y << " "
<< cloud->points[i].z << std::endl;
// display pointcloud after filtering
std::cerr << "Cloud after filtering: " << std::endl;
for (size_t i = 0; i < cloud_filtered->points.size (); ++i)
std::cerr << " " << cloud_filtered->points[i].x << " "
<< cloud_filtered->points[i].y << " "
<< cloud_filtered->points[i].z << std::endl;
return (0);
}
從中可以看出ConditionalRemoval 或RadiusOutlinerRemoval的區別
RadiusOutlinerRemoval比較適合去除單個的離羣點 ConditionalRemoval 比較靈活,可以根據用戶設置的條件靈活過濾