最近在学习三维重建的一些知识,作为一个小白,从入门到完成一次重建过程是一个需要花一段比较长时间的过程,在网上搜索各种资料时,会因为资料不全,导致无法理清楚逻辑,从而导致入门困难。
我主要是通过书和网上的博客来学习的,如果有时间的话,建议看书,会更加深入的理解整个过程,我这里推荐经典的一本书《点云库PCL从入门到精通》。
这里我将按照我的理解尽可能用简洁的语言来讲清楚三维重建这件事情。
什么是三维重建
你已知一个事物或者一个场景从各个角度得到的三维点云片段,点云与点云之间存在部分重合,你需要做的就是通过某种方式将这些点云拼接在一起,这就是所谓的三维重建。
点云从哪里来
有人会问点云从哪里来,点云的来源有很多种方式,常见的是通过双目视觉,深度相机,结构光或者声纳等硬件设备来获取三维点云,这里不详细讲了
点云重建过程
当你拿到一系列点云时,你需要做以下步骤来进行点云拼接
- 采样处理,由于你拿到的点云是非常庞大的,所以你需要进行采样处理,减少计算量,这里我采用的是下体素采样。
- 法向量估计,两幅点云之所以能较为准确的拼接在一起,是因为两幅点云之间有共同的特征,这里选用的是法线特征,所以你需要估计法线特征
- 计算描述子,这里使用的是fpfh描述子,描述子是用来对匹配两幅点云的,根据法向量才能得到描述子,通过描述子才能知道两幅图的哪些点云是匹配的
- 特征点匹配,特征点匹配分为两步,第一步是粗配,使两个点云基本重合,这里用的方法是采样一致性粗配,然后通过icp方法精配,这样就能得到拼接完成的点云了,但是精度跟你设置的参数有关,主要是setMaxCorrespondenceDistance()方法设置最大匹配的距离,记住粗配时要设得尽量大一点,这样才能搜索到正确的匹配点,精配时需要设置小一点,否则会匹配失效(这是因为ICP只有在两个点云非常接近的时候才能使用,ICP算法没有用到特征点,而是直接根据点云与点云之间的最小距离来匹配点云,属于硬配,不了解ICP算法的同学可以去查一下相关资料)。
- 点云坐标变换,根据第四步你可以得到两点云之间的变换矩阵,然后如果以某一点云作为基准点云,那么你就可以得到其他点云与这个点云之间的变换矩阵,然后通过变换将所有点云旋转平移到统一坐标系下,即可得到重建完的点云图。
以上是点云坐标转换最基本的流程,适合初学者完成一个简单的demo
你需要做的一些前期工作
- c++开发环境,我选用的是VS2015
- 配置PCL点云库 https://blog.csdn.net/uniqueyyc/article/details/79245009
- 点云集链接:https://pan.baidu.com/s/1_YgGuo97b5iueEU8wXhQiA
提取码:orjj
将点云集解压后放在c++项目的根目录下,然后就可以运行了。
下面是代码,供大家参考,有任何问题可以在留言区给我留言~
//
///* 目标:对输入的model,scene计算.匹配关系,并估计位姿
//* 下采样->计算特征(pfh)->sac粗配->icp精配-》点云显示
//* 可视化点云以及关键点,并可视化其特征直方图
//*/
#include<string>
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/point_types.h>
#include <pcl/common/io.h>
#include <pcl/impl/point_types.hpp>
#include <pcl/point_cloud.h>
#include <pcl/registration/ia_ransac.h>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/features/fpfh.h>
#include <pcl/features/pfh.h>
#include <pcl/features/shot_omp.h>
#include <pcl/search/kdtree.h>
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include<pcl/io/io.h>
#include <pcl/filters/filter.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/registration/icp.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/common/io.h>
#include <time.h>
#include <iostream>
#include <pcl/keypoints/iss_3d.h>
#include <cstdlib>
#include <pcl/visualization/pcl_plotter.h>// 直方图的可视化 方法2
#include <pcl/registration/sample_consensus_prerejective.h> // pose estimate
#include <pcl/keypoints/sift_keypoint.h> // shift关键点相关
#include <pcl/pcl_macros.h>
#include <pcl\features\fpfh.h>
#include <pcl\features\fpfh_omp.h>//omp加速计算
using pcl::NormalEstimation;
using pcl::search::KdTree;
typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloud;
typedef pcl::PointXYZ PointType;
typedef pcl::Normal NormalType;
typedef pcl::SHOT352 DescriptorType;
float descr_rad_(3.0f); // shot描述子的搜索半径
// sift相关
namespace pcl
{
template<>
struct SIFTKeypointFieldSelector<PointXYZ>
{
inline float
operator () (const PointXYZ &p) const
{
return p.z;
}
};
}
//可视化对应关系
//input: model_keypoints, scene_keypoints, model_scene_corrs
void
visualize_corrs(pcl::PointCloud<pcl::PointXYZ>::Ptr model_keypoints, pcl::PointCloud<pcl::PointXYZ>::Ptr scene_keypoints,
pcl::PointCloud<pcl::PointXYZ>::Ptr model, pcl::PointCloud<pcl::PointXYZ>::Ptr scene,
pcl::CorrespondencesPtr model_scene_corrs)
{
// 添加关键点
pcl::visualization::PCLVisualizer viewer("corrs Viewer");
viewer.setBackgroundColor(0, 0, 0);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> model_color(model, 0, 255, 0);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> scene_color(scene, 255, 0, 0);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> model_keypoint_color(model_keypoints, 0, 255, 255);
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> scene_keypoint_color(scene_keypoints, 0, 0, 255);
// viewer.addPointCloud(model, model_color, "model");
// viewer.addPointCloud(scene, scene_color, "scene");
viewer.addPointCloud(model_keypoints, model_keypoint_color, "model_keypoints");
viewer.addPointCloud(scene_keypoints, scene_keypoint_color, "scene_keypoints");
viewer.addPointCloud(model, model_color, "model");
viewer.addPointCloud(scene, scene_color, "scene");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "model_keypoints");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 7, "scene_keypoints");
viewer.addCorrespondences<pcl::PointXYZ>(model_keypoints, scene_keypoints, *model_scene_corrs);
// //添加对应关系
// int i=1;
// for(auto iter=model_scene_corrs->begin();iter!=model_scene_corrs->end();++iter)
// {
// std::stringstream ss_line;
// ss_line << "correspondence_line" << i ;
// i++;
// PointType& model_point = model_keypoints->at (iter->index_query); // 从corrs中获取对应点
// PointType& scene_point = scene_keypoints->at (iter->index_match);
// viewer.addLine<PointType, PointType> (model_point, scene_point, 0, 0, 0, ss_line.str ());
// viewer.setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_LINE_WIDTH, 5, ss_line.str ()); // 设置线宽
//
//
//
// }
// 显示
while (!viewer.wasStopped())
{
viewer.spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(100000));
}
}
//采样
void filter_volexgrid(PointCloud::Ptr cloud_src, PointCloud::Ptr &cloud_filter) {
pcl::VoxelGrid<pcl::PointXYZ> filter;
filter.setLeafSize(0.003, 0.003, 0.003);
filter.setInputCloud(cloud_src);
filter.filter(*cloud_filter);
cout << "降采样后点云数量:" << cloud_filter->size() << endl;
}
//计算分辨率
void cal_resolution(PointCloud::Ptr cloud) {
double resolution = 0.0;
int numberOfPoints = 0;
int nres;
std::vector<int> indices(2);
std::vector<float> squaredDistances(2);
pcl::search::KdTree<pcl::PointXYZ> tree;
tree.setInputCloud(cloud);
for (size_t i = 0; i < cloud->size(); ++i)
{
if (!pcl_isfinite((*cloud)[i].x))
continue;
// Considering the second neighbor since the first is the point itself.
nres = tree.nearestKSearch