1.PCL濾波概述
在獲取點雲數據時 ,由於設備精度,操作者經驗環境因素帶來的影響,以及電磁波的衍射特性,被測物體表面性質變化和數據拼接配準操作過程的影響,點雲數據中講不可避免的出現一些噪聲。在點雲處理流程中濾波處理作爲預處理的第一步,對後續的影響比較大,只有在濾波預處理中將噪聲點 ,離羣點,孔洞,數據壓縮等按照後續處理定製,才能夠更好的進行配準,特徵提取,曲面重建,可視化等後續應用處理,PCL中點雲濾波模塊提供了很多靈活實用的濾波處理算法,例如:PassThrough直通濾波、VoxelGrid過濾、StatisticalOutlierRemoval過濾、點雲投影、提取索引等;下面將對幾種常見濾波進行介紹。
2.PCL中點雲濾波的方案
PCL中總結了幾種需要進行點雲濾波處理情況,這幾種情況分別如下:
(1) 點雲數據密度不規則需要平滑
(2) 因爲遮擋等問題造成離羣點需要去除
(3) 大量數據需要下采樣
(4) 噪聲數據需要去除
對應的方案如下:
(1)按照給定的規則限制過濾去除點
(2) 通過常用濾波算法修改點的部分屬性
(3)對數據進行下采樣
3.常見濾波概述
直通濾波:快速過濾掉用戶自定義區間範圍內的點雲,效果最爲明顯並且也最容易理解。
體素濾波:在分割、配準前,如果點雲數量太多會影響後續時間。此時,需要對點雲進行下采樣處理,體素濾波爲採用體素網格方法採樣,減少點雲數量。
統計濾波:統計濾波往往去除離羣點,利用統計分析技術刪除噪聲異常值等。
投影點雲:將點投影到一個參數化模型上(平面或者球體等)。
提取索引:基於某一分割算法提取點雲中的子集。
4.幾種常見濾波使用實例
對於下面各種濾波的使用並沒有只用文字來闡述,而是主要在代碼的註釋中,我認爲這樣會便於理解。
4.1.直通濾波
直通濾波通俗來講就是設置一個範圍,經該範圍內或者該範圍外的點雲數據剔除,是一種簡單暴力但效果明顯的方式。
代碼:
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/cloud_viewer.h>
int main (int argc, char** argv)
{
//點雲對象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
//處理後點雲對象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_handle(new pcl::PointCloud<pcl::PointXYZ>);
//處理後點雲對象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_handle2(new pcl::PointCloud<pcl::PointXYZ>);
//從點雲文件中讀取點雲數據
pcl::io::loadPCDFile("d:/2.pcd", *cloud);
//目的:通過直通濾波器將某個方向範圍的點雲數據過濾掉
// 設置濾波器對象
pcl::PassThrough<pcl::PointXYZ> pass;
//z座標軸
pass.setInputCloud (cloud); //設置輸入點雲
pass.setFilterFieldName ("x"); //設置過濾的座標軸方向
pass.setFilterLimits (-90, 90); //設置在過濾掉的座標範圍
pass.setFilterLimitsNegative (false); //設置過濾範圍內還是範圍外,當爲false爲範圍外
pass.filter (*cloud_handle); //過濾器過濾,過濾後點雲數據保存在cloud_handle中
// 設置濾波器對象
pcl::PassThrough<pcl::PointXYZ> pass2;
//z座標軸
pass.setInputCloud (cloud_handle); //設置輸入點雲
pass.setFilterFieldName ("y"); //設置過濾的座標軸方向
pass.setFilterLimits (-86, 50); //設置在過濾掉的座標範圍
pass.setFilterLimitsNegative (false); //設置過濾範圍內還是範圍外,當爲false爲範圍外
pass.filter (*cloud_handle2); //過濾器過濾,過濾後點雲數據保存在cloud_handle中
//處理後點雲顯示
pcl::visualization::CloudViewer viewer("PCL濾波");
viewer.showCloud(cloud_handle2);
while (!viewer.wasStopped()){
}
return (0);
}
處理前:
處理後:
這種濾波處理方式效果很明顯,但是它受限於採樣設備的角度,如果角度不佳,僅僅對XYZ軸方向進行過濾就得不到理想的結果。
4.2.體素濾波
使用體素化網格方法實現下采樣,即減少點的數量 減少點雲數據,並同時保存點雲的形狀特徵,在提高後期點雲處理速度方面將起到顯著效果。PCL是實現的VoxelGrid類通過輸入的點雲數據創建一個三維體素柵格,容納後每個體素內用體素中所有點的重心來近似顯示體素中其他點,這樣該體素內所有點都用一個重心點最終表示,對於所有體素處理後得到的過濾後的點雲,這種方法比用體素中心逼近的方法更慢,但是對於採樣點對應曲面的表示更爲準確。
代碼:
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/cloud_viewer.h>
int main (int argc, char** argv)
{
//點雲對象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
//處理後點雲對象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_handle(new pcl::PointCloud<pcl::PointXYZ>);
//從點雲文件中讀取點雲數據
pcl::io::loadPCDFile("d:/2.pcd", *cloud);
pcl::VoxelGrid<pcl::PointXYZ> sor; //創建濾波對象
sor.setInputCloud (cloud); //設置需要過濾的點雲給濾波對象
sor.setLeafSize (1, 1, 1); //設置濾波時創建的體素體積,單位m,因爲保留點雲大體形狀的方式是保留每個體素的中心點,所以體素體積越大那麼過濾掉的點雲就越多
sor.filter (*cloud_handle); //執行濾波處理
//處理後點雲顯示
pcl::visualization::CloudViewer viewer("PCL濾波");
viewer.showCloud(cloud_handle);
while (!viewer.wasStopped()){
}
return (0);
}
處理前:
處理後:
4.3.統計濾波
使用統計分析技術,從一個點雲數據中集中移除測量噪聲點(也就是離羣點)比如:激光掃描通常會產生密度不均勻的點雲數據集,另外測量中的誤差也會產生稀疏的離羣點,使效果不好,估計局部點雲特徵(例如採樣點處法向量或曲率變化率)的運算複雜,這會導致錯誤的數值,反過來就會導致點雲配準等後期的處理失敗。
解決辦法:每個點的鄰域進行一個統計分析,並修剪掉一些不符合一定標準的點,稀疏離羣點移除方法基於在輸入數據中對點到臨近點的距離分佈的計算,對每一個點,計算它到它的所有臨近點的平均距離,,假設得到的結果是一個高斯分佈,其形狀是由均值和標準差決定,平均距離在標準範圍之外的點,可以被定義爲離羣點並可從數據中去除。
代碼:
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/cloud_viewer.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_handle(new pcl::PointCloud<pcl::PointXYZ>);
//從點雲文件中讀取點雲數據
pcl::io::loadPCDFile("d:/2.pcd", *cloud);
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sta; //創建濾波器對象
sta.setInputCloud (cloud); //設置待濾波的點雲
sta.setMeanK (1); //設置在進行統計時考慮查詢點臨近點數
sta.setStddevMulThresh (1.0); //設置判斷是否爲離羣點的閥值,根據原理來說,閾值越小過濾掉的點就越多
sta.filter (*cloud_handle); //存儲
//處理後點雲顯示
pcl::visualization::CloudViewer viewer("PCL濾波");
viewer.showCloud(cloud_handle);
while (!viewer.wasStopped()){
}
return (0);
}
處理前:
處理後:
統計濾波我目前的理解是通過算法按照一定閾值將點雲中的可能存在干擾的離散點過濾掉。
4.4.點雲投影
點雲投影主要是將三維結構投影到二維平面上。
代碼:
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/filters/project_inliers.h>
#include <pcl/ModelCoefficients.h>
int main (int argc, char** argv)
{
//點雲對象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
//處理後點雲對象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_handle(new pcl::PointCloud<pcl::PointXYZ>);
//從點雲文件中讀取點雲數據
pcl::io::loadPCDFile("d:/2.pcd", *cloud);
//定義模型係數對象,並填充對應的數據,填充ModelCoefficients的值
//使用ax+by+cz+d=0平面模型,其中 a=b=d=0,c=1 也就是X——Z平面
pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients ());
coefficients->values.resize (4);
coefficients->values[0] = coefficients->values[1] = 1.0;
coefficients->values[2] = 0;
coefficients->values[3] = 0;
pcl::ProjectInliers<pcl::PointXYZ> proj; //創建投影濾波對象
proj.setModelType (pcl::SACMODEL_PLANE); //設置對象對應的投影模型
proj.setInputCloud (cloud); //設置輸入點雲
proj.setModelCoefficients (coefficients); //設置模型對應的係數
proj.filter (*cloud_handle); //投影結果存儲
//處理後點雲顯示
pcl::visualization::CloudViewer viewer("PCL濾波");
viewer.showCloud(cloud_handle);
while (!viewer.wasStopped()){
}
return (0);
}
處理前:
處理後:
這裏就變成了一個在x-z平面的二維投影。
4.5.提取索引
代碼:
#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>
#include <pcl/visualization/cloud_viewer.h>
int main (int argc, char** argv)
{
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 ("d:/2.pcd", *cloud_blob);
//統計濾波前的點雲個數
//從輸入的.PCD 文件載入數據後,創建一個VOxelGrid濾波器對數據進行下采樣,在這裏進行下才樣是爲了加速處理過程,
//越少的點意味着分割循環中處理起來越快
// 創建體素柵格下采樣: 下采樣的大小爲1cm
pcl::VoxelGrid<pcl::PCLPointCloud2> sor; //體素柵格下采樣對象
sor.setInputCloud (cloud_blob); //原始點雲
sor.setLeafSize (1, 1, 1); // 設置採樣體素大小
sor.filter (*cloud_filtered_blob); //保存
// 轉換爲模板點雲
pcl::fromPCLPointCloud2 (*cloud_filtered_blob, *cloud_filtered);
// 保存下采樣後的點雲
pcl::PCDWriter writer;
writer.write<pcl::PointXYZ> ("table_scene_lms400_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)
{
break;
}
// Extract the inliers
extract.setInputCloud (cloud_filtered);
extract.setIndices (inliers);
extract.setNegative (false);
extract.filter (*cloud_p);
std::stringstream ss;
ss << "table_scene_lms400_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++;
}
//處理後點雲顯示
pcl::visualization::CloudViewer viewer("PCL濾波");
viewer.showCloud(cloud_f);
while (!viewer.wasStopped()){
}
return (0);
}
處理前:
處理後: