PCL之ICP演算法
轉自:https://blog.csdn.net/u010696366/article/details/8941938?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param
void PairwiseICP(const pcl::PointCloud<PointT>::Ptr &cloud_target, const pcl::PointCloud<PointT>::Ptr &cloud_source, pcl::PointCloud<PointT>::Ptr &output )
{
PointCloud<PointT>::Ptr src(new PointCloud<PointT>);
PointCloud<PointT>::Ptr tgt(new PointCloud<PointT>);
tgt = cloud_target;
src = cloud_source;
pcl::IterativeClosestPoint<PointT, PointT> icp;
icp.setMaxCorrespondenceDistance(0.1);
icp.setTransformationEpsilon(1e-10);
icp.setEuclideanFitnessEpsilon(0.01);
icp.setMaximumIterations (100);
icp.setInputSource (src);
icp.setInputTarget (tgt);
icp.align (*output);
// std::cout << "has converged:" << icp.hasConverged() << " score: " <<icp.getFitnessScore() << std::endl;
output->resize(tgt->size()+output->size());
for (int i=0;i<tgt->size();i++)
{
output->push_back(tgt->points[i]);
}
cout<<"After registration using ICP:"<<output->size()<<endl;
}
原始碼pcl.h裡給出了Usage example, 原始碼從github上下載之後可以在這個目錄找到
.\pcl-master\registration\include\pcl\registration。
- 一是PCL的icp裡的transformation estimation是基於SVD的, SVD是奇異值分解,Singular Value Decomposition,後面我們會提到;
- 二是使用之前要至少set三個引數:
- setMaximumIterations, 最大迭代次數,icp是一個迭代的方法,最多迭代這些次;
- setTransformationEpsilon, 前一個變換矩陣和當前變換矩陣的差異小於閾值時,就認為已經收斂了,是一條收斂條件;
- setEuclideanFitnessEpsilon, 還有一條收斂條件是均方誤差和小於閾值, 停止迭代。
- setMaximumIterations, 最大迭代次數,icp是一個迭代的方法,最多迭代這些次;
/** \brief @b IterativeClosestPoint provides a base implementation of the Iterative Closest Point algorithm.
* The transformation is estimated based on Singular Value Decomposition (SVD).
*
* The algorithm has several termination criteria:
*
* <li>Number of iterations has reached the maximum user imposed number of iterations (via \ref setMaximumIterations)</li>
* <li>The epsilon (difference) between the previous transformation and the current estimated transformation is smaller than an user imposed value (via \ref setTransformationEpsilon)</li>
* <li>The sum of Euclidean squared errors is smaller than a user defined threshold (via \ref setEuclideanFitnessEpsilon)</li>
* </ol>
* Usage example:
* ...
* \author Radu B. Rusu, Michael Dixon
**/
執行上面的程式碼就能得到兩個點雲配準的結果了,先不管結果好不好,看看他內部做了什麼,實際配准演算法都在aign裡實現。icp.h裡看到IterativeClosestPoint類是Registration的子類,icp.h給出了icp類的定義,而align的具體實現在上面的registration資料夾下的impl下的icp.hpp裡,都說align with initial guess,如果可以從一個好的初始猜想變換矩陣開始迭代,那麼演算法將會在比較少的迭代之後就收斂,配準結果也較好,當像我們這裡沒有指定初始guess時,就預設使用單位陣Matrix4::Identity() ,迭代部分就像下面這樣:
// Repeat until convergence
do
{
// Get blob data if needed
PCLPointCloud2::Ptr input_transformed_blob;
if (need_source_blob_)
{
input_transformed_blob.reset (new PCLPointCloud2);
toPCLPointCloud2 (*input_transformed, *input_transformed_blob);
}
// Save the previously estimated transformation
previous_transformation_ = transformation_;
// Set the source each iteration, to ensure the dirty flag is updated
correspondence_estimation_->setInputSource (input_transformed);
if (correspondence_estimation_->requiresSourceNormals ())
correspondence_estimation_->setSourceNormals (input_transformed_blob);
// Estimate correspondences
if (use_reciprocal_correspondence_)
correspondence_estimation_->determineReciprocalCorrespondences (*correspondences_, corr_dist_threshold_);
else
correspondence_estimation_->determineCorrespondences (*correspondences_, corr_dist_threshold_);
//...
size_t cnt = correspondences_->size ();
// Check whether we have enough correspondences
if (cnt < min_number_correspondences_)
{
PCL_ERROR ("[pcl::%s::computeTransformation] Not enough correspondences found. Relax your threshold parameters.\n", getClassName ().c_str ());
convergence_criteria_->setConvergenceState(pcl::registration::DefaultConvergenceCriteria<Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
converged_ = false;
break;
}
// Estimate the transform
transformation_estimation_->estimateRigidTransformation (*input_transformed, *target_, *correspondences_, transformation_);
// Tranform the data
transformCloud (*input_transformed, *input_transformed, transformation_);
// Obtain the final transformation
final_transformation_ = transformation_ * final_transformation_;
++nr_iterations_;
converged_ = static_cast<bool> ((*convergence_criteria_));
}
while (!converged_);
這裡的 transformation_estimation_->estimateRigidTransformation (*input_transformed, *target_, *correspondences_, transformation_);做了最重要的估計變換矩陣,
- 第一,SVD奇異值分解在pcl裡是直接呼叫Eigen內部的Umeyama實現的,我看了一眼,太過數學,暫時跳過,在另一篇部落格中提過,Eigen是專門處理矩陣運算的庫,pcl用的3rdParty之一,pcl用了很多第三方,這也是為什麼當初配環境如此痛苦的原因之一,畢竟pcl這個庫最開始只是一個德國博士的畢業論文順手寫出來的; 另外還可以看到,這裡的實現除了用SVD還有另一種方法,else裡提到的是先算兩個點雲的3D Centeroid, 再getTransformationFromCorrelation. 後面我們再來提這種思路。
- 第二, final_transformation = current_transformation * final_transformation, 這和KinFu那篇論文提到的“變換矩陣不斷疊加,最終得到唯一的全域性攝像頭位置(global camera pose)”是一致的。
template <typename PointSource, typename PointTarget, typename Scalar> inline void
pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::estimateRigidTransformation (
ConstCloudIterator<PointSource>& source_it,
ConstCloudIterator<PointTarget>& target_it,
Matrix4 &transformation_matrix) const
{
// Convert to Eigen format
const int npts = static_cast <int> (source_it.size ());
if (use_umeyama_)
{
Eigen::Matrix<Scalar, 3, Eigen::Dynamic> cloud_src (3, npts);
Eigen::Matrix<Scalar, 3, Eigen::Dynamic> cloud_tgt (3, npts);
for (int i = 0; i < npts; ++i)
{
cloud_src (0, i) = source_it->x;
cloud_src (1, i) = source_it->y;
cloud_src (2, i) = source_it->z;
++source_it;
cloud_tgt (0, i) = target_it->x;
cloud_tgt (1, i) = target_it->y;
cloud_tgt (2, i) = target_it->z;
++target_it;
}
// Call Umeyama directly from Eigen (PCL patched version until Eigen is released)
transformation_matrix = pcl::umeyama (cloud_src, cloud_tgt, false);
}
else
{
source_it.reset (); target_it.reset ();
// <cloud_src,cloud_src> is the source dataset
transformation_matrix.setIdentity ();
Eigen::Matrix<Scalar, 4, 1> centroid_src, centroid_tgt;
// Estimate the centroids of source, target
compute3DCentroid (source_it, centroid_src);
compute3DCentroid (target_it, centroid_tgt);
source_it.reset (); target_it.reset ();
// Subtract the centroids from source, target
Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> cloud_src_demean, cloud_tgt_demean;
demeanPointCloud (source_it, centroid_src, cloud_src_demean);
demeanPointCloud (target_it, centroid_tgt, cloud_tgt_demean);
getTransformationFromCorrelation (cloud_src_demean, centroid_src, cloud_tgt_demean, centroid_tgt, transformation_matrix);
}
}
到這裡,pcl的演算法基本上也捋清楚了,我們大概總結一下:首先icp是一步一步迭代得到較好的結果,解的過程其實是一個優化問題,並不能達到絕對正解。這個過程中求兩個點雲之間變換矩陣是最重要的,PCL裡是用奇異值分解SVD實現的。
二. 各種ICP變種
ICP有很多變種,有point-to-point的,也有point-to-plain的,一般後者的計算速度快一些,是基於法向量的,需要輸入資料有較好的法向量,而法向量估計目前我對於自己的輸入資料還沒有很好的解決,具體使用時建議根據自己的需要及可用的輸入資料選擇具體方法。
三. 廣義配準Registration
Pairwise registration
兩個點集的對應,輸出通常是一個4×4剛性變換矩陣:代表旋轉和平移,它應用於源資料集,結果是完全與目標資料集匹配。下圖是“雙對應”演算法中一次迭代的步驟:
對兩個資料來源a,b匹配運算步驟如下:
- 從其中一個資料來源a出發,分析其最能代表兩個資料來源場景共同點的關鍵點k
- 在每個關鍵點ki處,算出一個特徵描述子fi
- 從這組特徵描述子{fi}和他們在a和b中的XYZ座標位置,基於fi和xyz的相似度,找出一組對應
- 由於實際資料來源是有噪的,所以不是所有的對應都有效,這就需要一步一步排除對匹配起負作用的對應
- 從剩下的較好對應中,估計出一個變換
匹配過程中模組
Keypoints(關鍵點)
關鍵點是場景中有特殊性質的部分,一本書的邊角,書上印的字母P都可以稱作關鍵點。PCL中提供的關鍵點演算法如NARF,SIFT,FAST。你可以選用所有點或者它的子集作為關鍵點,但需要考慮的是按毎幀有300k點來算,就有300k^2種對應組合。
Feature descriptors(特徵描述子)
根據選取的關鍵點生成特徵描述。把有用資訊集合在向量裡,進行比較。方法有:NARF, FPFH, BRIEF 或SIFT.
Correspondences estimation(對應關係估計)
已知從兩個不同的掃描圖中抽取的特徵向量,找出相關特徵,進而找出資料中重疊的部分。根據特徵的型別,可以選用不同的方法。
點匹配(point matching, 用xyz座標作為特徵),無論資料有無重組,都有如下方法:
- brute force matching(強制匹配),
- kd-tree nearest neighbor search (FLANN)(kd樹最近鄰搜尋),
- searching in the image space of organized data(在影像空間搜尋有組織的資料),
- searching in the index space of organized data(按索引搜尋有組織的資料).
特徵匹配(feature matching, 用特徵做為特徵),只有下面兩種方法:
- brute force matching (強制匹配)
- kd-tree nearest neighbor search (FLANN)(kd樹最近鄰搜尋).
除了搜尋法,還有兩種著名對應估計:
- 直接估計對應關係(預設),對點雲A中的每一點,搜尋在B中的對應關係
- “Reciprocal” 相互對應關係估計,只用A,B重疊部分,先從A到B找對應,再從B到A找對應。
Correspondences rejection(剔除錯誤估計)
剔除錯誤估計,可用 RANSAC 演算法,或減少數量,只用一部分對應關係。有一種特殊的一到多對應,即模型中一個點對應源中的一堆點。這種情況可以用最短路徑對應或檢查附近的其他匹配過濾掉。
Transformation estimation(最後一步,計算變換)
- 基於上述匹配評估錯誤測量值;
- 評估相機不同pose之間所作的剛性變換(運動估計),使錯誤測量值最小化;
- 優化點雲結構;
- E.g, - SVD 運動估計; - Levenberg-Marquardt用不同核心作運動估計;
- 用剛性變換旋轉/平移源資料到目標位置,可能需要對所有點/部分點/關鍵點內部執行ICP迭代迴圈;
- 迭代,直到滿足某些收斂標準。
匹配流程總結
相關文章
- pcl::PointCloud和pcl::PontCloud::Ptr之間的關係和轉換方式Cloud
- 【點雲PCL入門】PCL+QT+VTK顯示點雲QT
- pcl常用小知識
- 關於pcl索引的使用索引
- PCL(19)點雲顯示
- ICP備案底部程式碼
- ICP備案掃盲帖
- icp配準進度條
- MySQL MRR和ICP介紹MySql
- 辦理ICP許可證
- ubuntu編譯安裝pcl教程。Ubuntu編譯
- 網站建好之後要不要進行ICP備案?為什麼?網站
- ubuntu18.04安裝python-pclUbuntuPython
- MySQL Index Condition Pushdown(ICP)的使用限制MySqlIndex
- mariadb 分割槽表 不能使用 ICP
- ICP證辦理流程是什麼?
- ICP證和EDI證的區別
- Visual Studio 2019設定PCL 1.12.1環境
- MySQL:關於ICP特性的說明(未完)MySql
- WIN10+PCL1.8.1+VS2017環境配置Win10
- 論文解讀(PCL)《Probabilistic Contrastive Learning for Domain Adaptation》ASTAIAPT
- 3分鐘搞清ICP和SP證區別
- 技術分享 | SQL 最佳化:ICP 的缺陷SQL
- 在c++MFC下用PCL顯示操作點雲檔案 MFC對話方塊顯示操作PCL點雲C++
- PCL 計算點雲的面積和體積
- MAC 如何配置 PCL 庫並在 VS Code 中使用Mac
- 從頭梳理,看看中國的 ICP 備案制度
- 演算法之KMP演算法KMP
- 常用演算法之貪心演算法演算法
- 演算法那些事之冒泡演算法演算法
- 基礎演算法之排序演算法演算法排序
- 最短路演算法之:Dijkstra 演算法演算法
- 最短路演算法之:floyd 演算法演算法
- PCL(9)PLC庫和OpenCV庫中的FLANN衝突OpenCV
- Win10系統PCL + VS2019的安裝Win10
- PCL(26)cropHull任意多邊形內部點雲提取
- 分享一個域名ICP備案接入檢測程式碼
- Point Cloud Library學習之ICP迭代最近點匹配法NDT2D正態分佈轉換法Cloud