PCL是點雲處理一個非常強大的庫,可以完成多種點雲的操作算法;而VTK又是三維點雲顯示以及三維重建非常有用的庫。雖然只會調庫難免讓人絕對低端,但這些庫的功能也是真的強大。
PCL系列文章
目錄
第三、將三維點雲轉換爲BA圖(BearingAngleImage)
第一、導入ply文件
pcl支持很多種數據格式,這裏介紹ply格式。這個ply格式有很多參數,不同ply文件包含的內容也可能不同,如有些只有點,有些有點有面,有些有點有顏色。所以導入會造成很多nan點,這一點一定要在後面的點雲處理中注意。
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/io/png_io.h>
//導入ply格式的點雲
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_src_o(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPLYFile<pcl::PointXYZ>("./dragonStandRight_24.ply", *cloud_src_o) == -1) //* load the file
{
return (-1);
}else {
//成功導入
qDebug()<<"src loads ok!";
// for(int i=0;i<cloud_src_o->size();i++)
// {
// qDebug()<<"cloud_src_o點座標x"<<cloud_src_o->points[i].PointXYZ::x;
// qDebug()<<"cloud_src_o點座標y"<<cloud_src_o->points[i].PointXYZ::y;
// qDebug()<<"cloud_src_o點座標z"<<cloud_src_o->points[i].PointXYZ::z;
// }
}
第二、點雲可視化
導入的點雲需要通過vtk庫進行可視化,方便調試。
首先,定義PCLVisualizer類的對象;
然後,可以設置界面的背景顏色,點的顏色;
再添加點的數據;
最後進行顯示。
//引入vtk進行點雲三維顯示
#include <pcl/visualization/pcl_visualizer.h>
//原始點雲和目標點雲可視化
pcl::visualization::PCLVisualizer viewer00("Visualization of original and target point cloud");
viewer00.setBackgroundColor(255,255,255);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> single_color(cloud_src_o, 0, 255, 0);
viewer00.addPointCloud<pcl::PointXYZ>(cloud_src_o, single_color, "sample cloud");
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> single_color2(cloud_tgt_o, 255, 0, 0);
viewer00.addPointCloud<pcl::PointXYZ>(cloud_tgt_o, single_color2, "sample cloud2");
while (!viewer00.wasStopped())
{
viewer00.spinOnce(1);
}
第三、將三維點雲轉換爲BA圖(BearingAngleImage)
這個很多博客都沒有,我也是在國外論壇看到過一點,但是它代碼還有問題,這次回報中文博客社區啦。
什麼是BA圖呢?如下定義。我們把三維點雲轉換爲二維圖像,提取特徵點,匹配特徵點,然後將匹配的特徵點重新轉換爲三維的匹配點,就可以得到匹配的三維點雲對。
#include <pcl/io/png_io.h>
#include <pcl/range_image/bearing_angle_image.h>
//3d點雲轉換爲BA圖。第一步:三維點雲轉換爲二維圖像
pcl::BearingAngleImage cloud_src_2d;
pcl::BearingAngleImage cloud_tgt_2d;
cloud_src_2d.generateBAImage(*cloud_src_o);
cloud_tgt_2d.generateBAImage(*cloud_tgt_o);
//保存BA圖
cv::Mat Im(cloud_src_2d.height, cloud_src_2d.width, CV_8UC1, cv::Scalar(255));
int count = 0;
// qDebug()<<"點雲集1的寬度數:"<<cloud_src_2d.width;
// qDebug()<<"點雲集1的高度數:"<<cloud_src_2d.height;
for (int i=0; i< cloud_src_2d.height; i++)
{
for (int j=0; j< cloud_src_2d.width; j++)
{
Im.at<uchar>(i,j) = (cloud_src_2d.points[count].rgba >> 8) & 0xff;
count++;
}
}
imwrite("src_BA.jpg", Im);
第四、使用opencv中的gms算法進行錯誤匹配點篩選
這裏是opencv的使用,需要注意:matchGMS函數只在opencv的高版本有,大概是opencv3.4以上吧,這裏我用的是opencv4.1,能用高版本儘量高版本。
//剔除錯誤匹配
//GMS進行匹配點篩選
cv::xfeatures2d::matchGMS(Im.size(),Im2.size(),keypoints_1,keypoints_2, matchesAll, matchesGMS);//原始代碼
std::cout << "正確匹配點數: " << matchesGMS.size() << std::endl;
第五、將二維圖像匹配點重映射爲三維點雲對
BA圖的點(i,j)與有序點雲的第i掃描層,第j列是一一對應關係的,因此可以得出匹配的三維點雲對。
//根據匹配點序號得到匹配後的三維點雲對
vector<int> src_matchpoint_Index;
vector<int> tgt_matchpoint_Index;
for(int p=0;p<matchesGMS.size();p++)
{
src_matchpoint_Index.push_back(cloud_src_2d.width*true_keypoints_1[p].pt.y+true_keypoints_1[p].pt.x);
// qDebug()<<"點座標1x"<<true_keypoints_1[p].pt.x;
// qDebug()<<"點座標1y"<<true_keypoints_1[p].pt.y;
tgt_matchpoint_Index.push_back(cloud_tgt_2d.width*true_keypoints_2[p].pt.y+true_keypoints_2[p].pt.x);
// qDebug()<<"點座標2x"<<true_keypoints_2[p].pt.x;
// qDebug()<<"點座標2y"<<true_keypoints_2[p].pt.y;
}
第六、去除NAN點
其中vector<int>定義的是非nan點的序號。removeNaNFromPointCloud裏,第一個參數是輸入點雲,第二個參數是去除nan點的點雲,第三個參數是非nan點的索引。
去除nan點是按照順序消除nan的,因此排列關係是線性的,可知的。
//過濾掉NAN點
std::vector<int> src_mapping_out;
std::vector<int> tgt_mapping_out;
pcl::PointCloud<pcl::PointXYZ>::Ptr src_temp(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr tgt_temp(new pcl::PointCloud<pcl::PointXYZ>);
pcl::removeNaNFromPointCloud(*src_cloudOut, *src_temp, src_mapping_out);
pcl::removeNaNFromPointCloud(*tgt_cloudOut, *tgt_temp, tgt_mapping_out);
第七、用索引複製一個新的點雲
已知一個點雲的某些點的索引,想要將這些點單獨提出來,組成新點雲。可以使用如下代碼
//src_cloudout和tgt_cloudout已經是一一對應啦
pcl::PointCloud<pcl::PointXYZ>::Ptr src_cloudOut(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*cloud_src_o, src_matchpoint_Index, *src_cloudOut);
pcl::PointCloud<pcl::PointXYZ>::Ptr tgt_cloudOut(new pcl::PointCloud<pcl::PointXYZ>);
pcl::copyPointCloud(*cloud_tgt_o, tgt_matchpoint_Index, *tgt_cloudOut);
第八、保存點云爲ply格式
這個比較簡單,需要注意的是保存前點雲文件裏是否有nan點,先去除nan點,再保存。
//保存爲ply格式
std::cerr << "Saving to ply file " << std::endl;
pcl::io::savePLYFileASCII("./src_cloudOut.ply",*true_src_cloudOut);//我去,少了個* 就不行了。有些博客害人啊
第九、對應三維點雲對連線的可視化
使用vtk進行匹配點雲的連線圖可視化。
首先定義一個可以容納多個點雲和點雲連線的ctkCellArray類對象;
然後,添加需要顯示的點,使用循環添加;
然後定義pdata,包含前面的點雲和點雲連線;
最後進行顯示。
//對應點雲連線
vtkSmartPointer<vtkCellArray> lines =vtkSmartPointer<vtkCellArray>::New();//連線集合
for(int i=0;i<true_tgt_cloudOut->size();i++)
{
vtkSmartPointer<vtkLine> line = vtkSmartPointer<vtkLine>::New();
//第一個參數是序號,第二個參數是點的序號
line->GetPointIds()->SetId(0,i);
line->GetPointIds()->SetId(1,true_tgt_cloudOut->size()+i);
lines->InsertNextCell(line);
}
//存儲點雲和連線的數據集
vtkSmartPointer<vtkPolyData> pdata =vtkSmartPointer<vtkPolyData>::New();
//添加所有的點
pdata->SetPoints(pts);
//添加所有的線
pdata->SetLines(lines);
//vtk中繪圖
vtkPolyDataMapper *mapper = vtkPolyDataMapper::New();
mapper->SetInputData(pdata);
vtkSmartPointer<vtkActor> actor =vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
actor->GetProperty()->SetColor(1, 0, 0);
actor->GetProperty()->SetLineWidth(4);
vtkSmartPointer<vtkRenderer> renderer =vtkSmartPointer<vtkRenderer>::New();
vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->AddRenderer(renderer);
vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =vtkSmartPointer<vtkRenderWindowInteractor>::New();
renderWindowInteractor->SetRenderWindow(renderWindow);
renderer->AddActor(actor);
renderer->SetBackground(1, 1, 1);
renderWindow->Render();
renderWindowInteractor->Start();
第十、根據匹配的三維點雲對求剛體變換矩陣
注意,這裏的點雲是一一對應的,所謂一一對應是指:第一個點雲第一個點和第二個點雲第一個點對應。要用好這個函數,需要注意這一點哈!
//根據匹配的三維點對求RT矩陣,true_src_cloudOut,true_tgt_cloudOut
pcl::registration::TransformationEstimationSVD<pcl::PointXYZ,pcl::PointXYZ> TESVD;
pcl::registration::TransformationEstimationSVD<pcl::PointXYZ,pcl::PointXYZ>::Matrix4 transformation_matrix;
TESVD.estimateRigidTransformation(*true_src_cloudOut,*true_tgt_cloudOut,transformation_matrix);
第十一、使用剛體變換矩陣對點雲進行旋轉平移操作
函數的第一個參數:待處理的點雲;第二個參數:旋轉平移後的點雲;第三個參數:剛體變換矩陣。
下面我只定義了這個剛體變換矩陣,沒有賦值,少了這一步要注意哦,可以參考其他博客進行剛體變換矩陣的定義。
Eigen::Matrix4f icp_trans;
//使用創建的變換對未過濾的輸入點雲進行變換
pcl::transformPointCloud(*origin_src_nonan, *icp_result, icp_trans);
第十二、icp進行精確匹配
最重要的是:其中的transformation_matrix是剛體變換矩陣的初值,這個值可以通過粗配準得到。
注意:icp非常依賴這個初值,很多研究也在解決這個問題。
//icp配準,較爲耗時
pcl::PointCloud<pcl::PointXYZ>::Ptr icp_result(new pcl::PointCloud<pcl::PointXYZ>);
pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> icp;
//輸入源點雲和目標點雲
icp.setInputSource(origin_src_nonan);
icp.setInputTarget(origin_tgt_nonan);
//Set the max correspondence distance to 4cm (e.g., correspondences with higher distances will be ignored)
icp.setMaxCorrespondenceDistance(0.04);
// 最大迭代次數
icp.setMaximumIterations(50);
// 兩次變化矩陣之間的差值
icp.setTransformationEpsilon(1e-10);
// 均方誤差,sac_trans爲粗配準得到的剛體矩陣
icp.setEuclideanFitnessEpsilon(0.2);
icp.align(*icp_result, transformation_matrix);
std::cout << "ICP has converged:" << icp.hasConverged()
<< " score: " << icp.getFitnessScore() << std::endl;
Eigen::Matrix4f icp_trans;
icp_trans = icp.getFinalTransformation();
//cout<<"ransformationProbability"<<icp.getTransformationProbability()<<endl;
std::cout << icp_trans << endl;
//使用創建的變換對未過濾的輸入點雲進行變換
pcl::transformPointCloud(*origin_src_nonan, *icp_result, icp_trans);
std::cout <<icp.transformation_epsilon_;//輸出最後一次迭代的迭代誤差
文末福利:看到最後的纔有哦,我的所有頭文件。不懂,就全添加,保證不會錯。
#pragma execution_character_set("utf-8")
#include "mainwindow.h"
#include <QApplication>
#include <QCoreApplication>
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/io/png_io.h>
#include <pcl/range_image/bearing_angle_image.h>
#include <pcl/impl/point_types.hpp>
#include <pcl/console/parse.h>
#include <pcl/common/transforms.h>
#include <pcl/registration/ia_ransac.h>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/features/normal_3d.h>
#include <pcl/features/fpfh.h>
#include <pcl/search/kdtree.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/filter.h>
#include <pcl/registration/icp.h>
//引入vtk進行點雲三維顯示
#include <pcl/visualization/pcl_visualizer.h>
#include <time.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <iostream>
#include <qdebug.h>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/features2d/features2d.hpp>
#include<opencv2/xfeatures2d/nonfree.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/face.hpp>
#include"opencv2/xfeatures2d.hpp"
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <vtkSmartPointer.h>
#include <vtkLineSource.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkLine.h>
#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL); // VTK was built with vtkRenderingOpenGL
VTK_MODULE_INIT(vtkInteractionStyle);
using namespace std;
using namespace cv;
using namespace cv::ml;
using namespace face;
using namespace cv::xfeatures2d;
typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloud;
using pcl::NormalEstimation;
using pcl::search::KdTree;
using namespace pcl;
using namespace pcl::io;