宜昌網(wǎng)站推廣關(guān)鍵詞優(yōu)化怎么弄
前言
通常從傳感器(3D相機(jī)、雷達(dá))中獲取到的點(diǎn)云存在噪點(diǎn)(雜點(diǎn)、離群點(diǎn)、孤島點(diǎn)等各種叫法)。噪點(diǎn)產(chǎn)生的原因有不同,可能是掃描到了不想要掃描的物體,可能是待測(cè)工件表面反光形成的,也可能是相機(jī)內(nèi)部的原因。在進(jìn)行其他算法處理之前,通常需要先去除噪聲,避免帶來(lái)干擾。去噪的方法有很多,效率和效果也是各不相同,應(yīng)用場(chǎng)景也不太一樣,本篇內(nèi)容就是想要將不同的去噪方法進(jìn)行歸納。
環(huán)境:
Windows + VS2019 + PCL1.11.1
在開(kāi)始之前,先貼出用于測(cè)試的點(diǎn)云,可以看出來(lái)空間中漂浮了很多噪點(diǎn)。原始點(diǎn)云總數(shù)為5882482個(gè)點(diǎn),x和y方向間距為0.05。
1.半徑濾波
PCL中集成有半徑濾波,可以用于噪點(diǎn)的去除。主要需要設(shè)定兩個(gè)參數(shù),一個(gè)是搜索半徑,另一個(gè)是在搜索半徑內(nèi)近鄰點(diǎn)的最小數(shù)量。這兩個(gè)參數(shù)需要根據(jù)點(diǎn)云的x,y,z方向上的點(diǎn)間距來(lái)設(shè)定(或者說(shuō)分辨率)。一般使用線掃相機(jī)或者結(jié)構(gòu)光相機(jī)這類(lèi)3D相機(jī)得到的點(diǎn)在x,y方向上間距都比較均勻。假設(shè)點(diǎn)間距是0.05,那么將搜索半徑設(shè)置為0.1,則搜索半徑內(nèi)理論上在上下左右方向上分別有2個(gè)點(diǎn),共8個(gè)點(diǎn),所以近鄰點(diǎn)最起碼也有8個(gè)。
頭文件:
#include <pcl/filters/radius_outlier_removal.h>
代碼如下:
/// <summary>
/// 使用半徑濾波進(jìn)行點(diǎn)云噪點(diǎn)去除
/// </summary>
/// <param name="cloud">輸入點(diǎn)云</param>
/// <param name="cloud_out">輸出除噪后的點(diǎn)云</param>
/// <param name="radius">搜索半徑</param>
/// <param name="neighbors">單個(gè)搜索內(nèi)近鄰點(diǎn)數(shù)</param>
/// <returns>return true表示去噪成功,return false表示失敗</returns>
bool NoiseRemoveROR(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_out, const float& radius,const int& neighbors)
{if (cloud == nullptr) return false;if (cloud->points.size() < 10) return false;pcl::RadiusOutlierRemoval<pcl::PointXYZ> rorfilter(true); // 初始化為true以獲取被移除的索引rorfilter.setInputCloud(cloud);rorfilter.setRadiusSearch(radius); // 設(shè)置搜索半徑rorfilter.setMinNeighborsInRadius(neighbors); // 設(shè)置搜索半徑內(nèi)滿(mǎn)足的點(diǎn)數(shù)//rorfilter.setUserFilterValue(1); // 將不符合要求的點(diǎn)更改為新的值,而不是去去除它們,與setKeepOrganized一起使用//rorfilter.setKeepOrganized(false); // 保證有序點(diǎn)云的結(jié)構(gòu),與setUserFilterValue一起使用rorfilter.filter(*cloud_out); // 進(jìn)行離群點(diǎn)去除return true;
}
一般近鄰點(diǎn)的數(shù)量設(shè)置與搜索半徑有直接關(guān)系。而經(jīng)過(guò)測(cè)試發(fā)現(xiàn),搜索的近鄰點(diǎn)數(shù)量需要越多則耗時(shí)越長(zhǎng)。較小的搜索半徑只需要設(shè)置較小數(shù)量的近鄰點(diǎn),但是這樣可能會(huì)導(dǎo)致有的噪點(diǎn)去除不干凈。稍大一點(diǎn)的搜索半徑以及近鄰點(diǎn)數(shù)量適當(dāng)調(diào)大可以得到更穩(wěn)定的去噪效果。因此,效果和效率之間需要找到一個(gè)平衡點(diǎn)。
測(cè)試及結(jié)果:搜索半徑設(shè)置為0.2,近鄰點(diǎn)數(shù)量為20,耗時(shí)10.94s。效果如下:
針對(duì)于半徑濾波耗時(shí)問(wèn)題其實(shí)可以將點(diǎn)云使用直通濾波分成若干份,然后使用OpenMP對(duì)這幾份點(diǎn)云并行處理進(jìn)行半徑濾波,將結(jié)果再合并成一個(gè)點(diǎn)云,這個(gè)方法可能能夠起到加速的作用。
有一篇論文《Fast Radius Outlier Filter Variant for Large Point Clouds》,更快速的半徑濾波,還未找到源碼,若有人找到請(qǐng)聯(lián)系我,感謝。
2.統(tǒng)計(jì)學(xué)濾波
統(tǒng)計(jì)學(xué)濾波也是一種比較常見(jiàn)的去噪算法。其官方解釋如下:
The statistical outlier removal process is a bit more refined. First, for every point, the mean distance to its K neighbors is computed. Then, if we asume that the result is a normal (gaussian) distribution with a mean μ and a standard deviation σ, we can deem it safe to remove all points with mean distances that fall out of the global mean plus deviation. Basically, it runs a statistical analysis of the distances between neighboring points, and trims all which are not considered “normal” (you define what “normal” is with the parameters of the algorithm).
個(gè)人理解就是遍歷點(diǎn)云,對(duì)于每個(gè)點(diǎn),都先搜索與其最相近的k個(gè)點(diǎn),計(jì)算這k個(gè)點(diǎn)與該點(diǎn)的距離,并得到一個(gè)平均距離和一個(gè)平均距離的標(biāo)準(zhǔn)差。然后比較距離是否是大于μ+stddev*σ,如果大于則表示是離群點(diǎn)。
頭文件:#include <pcl/filters/statistical_outlier_removal.h>
代碼如下:
/// <summary>
/// 使用PCL中集成的統(tǒng)計(jì)學(xué)濾波進(jìn)行去噪
/// </summary>
/// <param name="cloud">輸入點(diǎn)云</param>
/// <param name="cloud_out">輸出點(diǎn)云</param>
/// <param name="k">搜索近鄰點(diǎn)的個(gè)數(shù)</param>
/// <param name="stddev">平均距離標(biāo)準(zhǔn)差的乘數(shù)</param>
/// <returns>return true表示去噪成功,return false表示失敗</returns>
bool NoiseRemoveSOR(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_out, const int& k, const double& stddev)
{if (cloud == nullptr) return false;if (cloud->points.size() < 10) return false;pcl::StatisticalOutlierRemoval<pcl::PointXYZ> filter; // Filter object.filter.setInputCloud(cloud);filter.setMeanK(k); // Set number of neighbors to consider to K.filter.setStddevMulThresh(stddev); // Points with a distance larger than stddev standard deviation of the mean distance will be outliers.filter.filter(*cloud_out);return true;
}
使用這個(gè)統(tǒng)計(jì)學(xué)濾波感覺(jué)還是十分耗時(shí),但是效果確實(shí)是不錯(cuò)的。
測(cè)試及結(jié)果:近鄰點(diǎn)k值的數(shù)量為20,系數(shù)設(shè)定為1.0,耗時(shí)11.07s。效果如下:
有一篇論文《Fast statistical outlier removal based method for large 3d point clouds of outdoor environments》,根據(jù)其題目理解來(lái)說(shuō)是一種更快的統(tǒng)計(jì)學(xué)濾波方法去除離群點(diǎn),論文還沒(méi)看,源碼也沒(méi)找到,誰(shuí)能找到麻煩聯(lián)系我,感謝!
3.RANSAC
沒(méi)錯(cuò),就是RANSAC。使用RANSAC去去噪需要滿(mǎn)足條件,那就是目標(biāo)點(diǎn)云是具有幾何特征的。如果目標(biāo)點(diǎn)云是一個(gè)平面,那么就可以使用RANSAC擬合一個(gè)平面,并且將距離平面較遠(yuǎn)的點(diǎn)(外點(diǎn))去除。這樣當(dāng)然也可以達(dá)到去噪的效果,而且速度還比較快。
頭文件:
#include <pcl/kdtree/kdtree.h>
#include <pcl/segmentation/sac_segmentation.h>
/// <summary>
/// 使用PCL中集成的用于點(diǎn)云分割的RANSAC方法進(jìn)行平面擬合
/// </summary>
/// <param name="cloud_in">輸入待擬合的點(diǎn)云</param>
/// <param name="inliers">RANSAC擬合得到的內(nèi)點(diǎn)</param>
/// <param name="coefficients">得到的平面方程參數(shù)</param>
/// <param name="iterations">平面擬合最大迭代次數(shù)</param>
/// <param name="threshold">RANSAC擬合算法距離閾值</param>
void SEG_RANSAC(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_in, pcl::PointIndices::Ptr& inliers, Eigen::VectorXf& coefficients,const int& iterations, const double& threshold)
{if (inliers == nullptr) inliers.reset(new pcl::PointIndices);pcl::ModelCoefficients::Ptr coefficients_m(new pcl::ModelCoefficients);pcl::shared_ptr<pcl::search::KdTree<pcl::PointXYZ>> tree(new pcl::search::KdTree<pcl::PointXYZ>);tree->setInputCloud(cloud_in);pcl::SACSegmentation<pcl::PointXYZ> seg;seg.setOptimizeCoefficients(true);seg.setModelType(pcl::SACMODEL_PLANE);seg.setMethodType(pcl::SAC_RANSAC);seg.setMaxIterations(iterations); // 設(shè)置最大迭代次數(shù)seg.setDistanceThreshold(threshold); // 設(shè)定閾值seg.setNumberOfThreads(10); // 設(shè)置線程數(shù)量seg.setSamplesMaxDist(3, tree);seg.setInputCloud(cloud_in);seg.segment(*inliers, *coefficients_m);coefficients.resize(4);coefficients[0] = coefficients_m->values[0]; coefficients[1] = coefficients_m->values[1];coefficients[2] = coefficients_m->values[2]; coefficients[3] = coefficients_m->values[3];std::cout << "SEG coefficients: " << coefficients[0] << ", " << coefficients[1] << ", " << coefficients[2] << ", " << coefficients[3] << std::endl;
}
運(yùn)行時(shí)間只需要1.5s,迭代了200次。速度比半徑濾波和統(tǒng)計(jì)學(xué)濾波要快很多。但是這個(gè)方法也是很有局限性的,只適合特定點(diǎn)云才能用,而且如下圖所示,距離平面較近的噪點(diǎn)無(wú)法去除,所以是存在去除噪點(diǎn)不干凈的情況,如果追求速度,而對(duì)去噪要求沒(méi)那么高,則可以考慮使用該方法。
4.歐式聚類(lèi)
歐式聚類(lèi)既可以用于分割,也可以用于去噪,其實(shí)跟上述半徑濾波區(qū)別不大。噪點(diǎn)肯定是距離想要的點(diǎn)云比較“遠(yuǎn)”的,設(shè)置好minSize,把想要的點(diǎn)聚成一個(gè)類(lèi),噪點(diǎn)自然就去除了。
代碼如下:
/// <summary>
/// PCL中集成的歐式聚類(lèi)
/// </summary>
/// <param name="cloud">輸入點(diǎn)云</param>
/// <param name="cluster_indices">聚類(lèi)索引的數(shù)組</param>
/// <param name="tolerance ">距離閾值</param>
/// <param name="MinClusterSize">單個(gè)類(lèi)最少的點(diǎn)數(shù)</param>
/// <param name="MaxClusterSize">單個(gè)類(lèi)最大的點(diǎn)數(shù)</param>
/// <returns>return true表示有聚類(lèi)結(jié)果,return false表示聚類(lèi)失敗</returns>
bool NoiseRemoveEC(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, std::vector<pcl::PointIndices>& cluster_indices, const double& tolerance,const int& MinClusterSize, const int& MaxClusterSize)
{if (cloud == nullptr) return false;if (cloud->points.size() < 10) return false;int maxSize = MaxClusterSize;if (maxSize < 0) maxSize = cloud->points.size();if (MinClusterSize > maxSize) return false;pcl::shared_ptr<pcl::search::KdTree<pcl::PointXYZ>> tree(new pcl::search::KdTree<pcl::PointXYZ>); // Creating the KdTree object for the search method of the extractiontree->setInputCloud(cloud);cluster_indices.clear();pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec; // 歐式聚類(lèi)對(duì)象ec.setClusterTolerance(tolerance); // 設(shè)置近鄰搜索的搜索半徑為 單位是mec.setMinClusterSize(MinClusterSize); // 設(shè)置一個(gè)聚類(lèi)需要的最少的點(diǎn)數(shù)目ec.setMaxClusterSize(maxSize); // 設(shè)置一個(gè)聚類(lèi)需要的最大點(diǎn)數(shù)目ec.setSearchMethod(tree); // 設(shè)置點(diǎn)云的搜索機(jī)制ec.setInputCloud(cloud); // 輸入聚類(lèi)點(diǎn)云ec.extract(cluster_indices); // 從點(diǎn)云中提取聚類(lèi),并將點(diǎn)云索引保存在cluster_indices中if (cluster_indices.empty()) return false;return true;
}
設(shè)置的距離閾值為0.1,是兩倍的點(diǎn)距大小(x,y方向點(diǎn)距都是0.05)。耗時(shí)10.9s。當(dāng)距離閾值tolerance設(shè)置的比較大時(shí),就會(huì)特別耗時(shí),該方法就幾乎不可用了。