本教程演示如何在代码中使用迭代最近点(ICP)算法,该算法通过最小化两个点云的点之间的距离,并变换它们来确定一个点云是否是另一个点云的刚性变换。
程序将加载两个不同的点云,然后,ICP 算法将其中一个点云和另外一个点云对齐。用户每次按“空格”键时,都会进行一次 ICP 迭代并刷新可视化窗口。
我的点云数据是从 D435i 深度相机采集的,里面本身就有一些无效点,故在代码中有一个函数用于剔除无效点,下面看代码
#include <iostream>
#include <Eigen/Dense>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/filters/filter.h>
#include <pcl/registration/icp.h>
#include <pcl/common/time.h>
#include <pcl/visualization/pcl_visualizer.h>
bool next_iteration = false;
// 此函数是可视化窗口的回调函数。当可视化窗口被激活时,只要按空格键,就会调用此函数,将布尔变量next_iteration 设置为 true
void keyboardEventOccurred (const pcl::visualization::KeyboardEvent& event, void* nothing)
{
if (event.getKeySym () == "space" && event.keyDown ())
next_iteration = true;
}
void down_sample(const pcl::PointCloud<pcl::PointXYZRGB>::Ptr& cloud_in,
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr& cloud_out)
{
pcl::ScopeTime time("down sample");
int down_sample_rate = 8;
int height = cloud_in->height;
int width = cloud_in->width;
int height_ = height / down_sample_rate;
int width_ = width / down_sample_rate;
cloud_out->resize(height_ * width_);
for (int i = 0; i < height; i = i + down_sample_rate)
{
for (int j = 0; j < width; j = j + down_sample_rate)
{
int index = i * width + j;
int index_ = i * width / down_sample_rate / down_sample_rate + j / down_sample_rate;
cloud_out->points[index_] = cloud_in->points[index];
}
}
cloud_out->height = height_;
cloud_out->width = width_;
}
void remove_nan(const pcl::PointCloud<pcl::PointXYZRGB>::Ptr& cloud_1,
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr& cloud_2)
{
// 如果想正确使用 pcl::removeNaNFromPointCloud 函数,最好先把点云的 is_dense 属性设置为 false
// 因为 pcl::removeNaNFromPointCloud 函数会先检查 is_dense 属性,如果该属性原本为 true 那么该函数将不会对点云进行任何处理
cloud_1->is_dense = false;
cloud_2->is_dense = false;
std::vector<int> indices_1;
pcl::removeNaNFromPointCloud(*cloud_1, *cloud_1, indices_1);
std::vector<int> indices_2;
pcl::removeNaNFromPointCloud(*cloud_2, *cloud_2, indices_2);
cloud_1->is_dense = true;
cloud_2->is_dense = true;
}
void icp_function(const pcl::PointCloud<pcl::PointXYZRGB>::Ptr& cloud_source,
const pcl::PointCloud<pcl::PointXYZRGB>::Ptr& cloud_target)
{
int iterations = 10;
// ICP 算法
pcl::IterativeClosestPoint<pcl::PointXYZRGB, pcl::PointXYZRGB> icp;
icp.setMaximumIterations (iterations); // 设置要执行的初始迭代次数(默认值为1)
icp.setInputSource(cloud_source);
icp.setInputTarget(cloud_target);
icp.align(*cloud_source);
icp.setMaximumIterations(1); // 在第一次对齐之后,将这个变量设置为1,下次调用.align()函数的时候就只会迭代一次
if (icp.hasConverged ())
{
std::cout << "\nICP has converged, score is " << icp.getFitnessScore () << std::endl;
std::cout << "\nICP transformation " << iterations << " : cloud_source -> cloud_target" << std::endl;
Eigen::Matrix4d transformation_matrix = icp.getFinalTransformation ().cast<double>();
std::cout << transformation_matrix << std::endl;
}
else
{
std::cerr << "\nICP has not converged." << std::endl;
}
// 可视化部分
pcl::visualization::PCLVisualizer viewer;
// 目标点云为白色
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> white(cloud_target, 250, 250, 250);
viewer.addPointCloud<pcl::PointXYZRGB> (cloud_target, white, "cloud_target");
// 原点云为红色
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> red (cloud_source, 250, 0, 0);
viewer.addPointCloud (cloud_source, red, "cloud_source");
// 键盘回调函数
viewer.registerKeyboardCallback (&keyboardEventOccurred, (void*) nullptr);
// 最终可视化的时候会看到,每按一次空格键,红色的原点云会逐渐逼近白色的目标点云
while (!viewer.wasStopped())
{
viewer.spinOnce ();
// if you pressed "space"
if (next_iteration)
{
icp.align (*cloud_source); // 每一次循环,只迭代一次
if (icp.hasConverged ())
{
std::cout << "\nICP has converged, score is " << icp.getFitnessScore () << std::endl;
std::cout << "\nICP transformation " << ++iterations << " : cloud_source -> cloud_target" << std::endl;
Eigen::Matrix4d transformation_matrix = icp.getFinalTransformation ().cast<double>();
std::cout << transformation_matrix << std::endl;
viewer.updatePointCloud (cloud_source, red, "cloud_source");
}
else
{
std::cerr << "\nICP has not converged.\n" << std::endl;
}
}
next_iteration = false;
}
}
int main()
{
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_source (new pcl::PointCloud<pcl::PointXYZRGB>); // 原点云
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_target (new pcl::PointCloud<pcl::PointXYZRGB>); // 目标点云
std::string name_1 = "XXX.pcd" ;
pcl::io::loadPCDFile<pcl::PointXYZRGB>(name_1, *cloud_source);
std::string name_2 = "YYY.pcd" ;
pcl::io::loadPCDFile<pcl::PointXYZRGB>(name_2, *cloud_target);
std::cout << "[Cloud 1] Number of Points = " << cloud_source->points.size () << std::endl;
std::cout << "[Cloud 2] Number of Points = " << cloud_target->points.size () << std::endl;
// 进行降采样,把原始点云的尺寸从 640 * 480 降采样到 80 * 60
down_sample(cloud_source, cloud_source);
down_sample(cloud_target, cloud_target);
std::cout << "[Cloud 1] Number of Points = " << cloud_source->points.size () << std::endl;
std::cout << "[Cloud 2] Number of Points = " << cloud_target->points.size () << std::endl;
// 点云中有些点是无效的,要将其去除,要不然 ICP 会报错
remove_nan(cloud_source, cloud_target);
icp_function(cloud_source, cloud_target);
return 0;
}
下面是可视化窗口的展示和控制台的打印输出
图片中,白色是目标点云,红色是原点云,当你每按一次空格键,红色的原点云会逼近白色的目标点云,不过这一次运气比较好,在最初的 10 次迭代内就已经收敛了。你们可以把最初的迭代次数调得小一点比如2,这样最初的 2 次迭代内算法还不会收敛,可以看到更加明显的逼近效果。
注意:在你们实际使用 ICP 算法的时候要把最初迭代次数调得大一点,这样最终的迭代效果会更加准确,这里用的最初迭代次数比较小纯粹是为了演示 ICP 的每一步迭代。
如下是控制台的打印输出
可以看到,我们想要的变换矩阵,ICP 已经给你求好了。好了,关于 ICP 的教程就先讲到这里,下回见。Enjoy~
评论(0)
您还未登录,请登录后发表或查看评论