Spin Image自旋图像描述符可视化以及ICP配准

news2025/1/29 13:58:38

一、Spin Image自旋图像描述符可视化

C++

#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/search/kdtree.h>
#include <pcl/io/pcd_io.h>
#include <pcl/features/normal_3d_omp.h>//使用OMP需要添加的头文件
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/spin_image.h>
#include <boost/thread/thread.hpp>

#include <pcl/visualization/pcl_plotter.h>// 直方图的可视化 
using namespace std;
int main()
{
	//------------------加载点云数据-----------------
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
	if (pcl::io::loadPCDFile<pcl::PointXYZ>("pcd/pig_view1.pcd", *cloud) == -1)//需使用绝对路径
	{
		PCL_ERROR("Could not read file\n");
	}

	//--------------------计算法线------------------
	pcl::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> n;//OMP加速
	pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
	//建立kdtree来进行近邻点集搜索
	pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
	n.setNumberOfThreads(6);//设置openMP的线程数
	n.setInputCloud(cloud);
	n.setSearchMethod(tree);
	n.setRadiusSearch(0.3);//半径搜素
	n.compute(*normals);//开始进行法向计算

	// ------------------spin image图像计算------------------
	pcl::SpinImageEstimation<pcl::PointXYZ, pcl::Normal, pcl::Histogram<153> > spin_image_descriptor(8, 0.05, 10);
	//三个数字分别表示:旋转图像分辨率;最小允许输入点与搜索曲面点之间的法线夹角的余弦值,以便在支撑中保留该点;
	//小支持点数,以正确估计自旋图像。如果在某个点支持包含的点较少,则抛出异常
	spin_image_descriptor.setInputCloud(cloud);
	spin_image_descriptor.setInputNormals(normals);
	// 使用法线计算的KD树
	spin_image_descriptor.setSearchMethod(tree);
	pcl::PointCloud<pcl::Histogram<153> >::Ptr spin_images(new pcl::PointCloud<pcl::Histogram<153> >);
	spin_image_descriptor.setRadiusSearch(40);
	// 计算spin image图像
	spin_image_descriptor.compute(*spin_images);
	cout << "SI output points.size (): " << spin_images->points.size() << endl;

	// 显示和检索第一点的自旋图像描述符向量。
	pcl::Histogram<153> first_descriptor = spin_images->points[0];
	cout << first_descriptor << endl;

	//========自旋图像描述符可视化=============================

	pcl::visualization::PCLPlotter plotter;
	plotter.addFeatureHistogram(*spin_images, 600); //设置的横坐标长度,该值越大,则显示的越细致
	plotter.setWindowName("Spin Image");
	plotter.plot();

	return 0;
}

关键代码解析:

    pcl::SpinImageEstimation<pcl::PointXYZ, pcl::Normal, pcl::Histogram<153> > spin_image_descriptor(8, 0.05, 10);
	//三个数字分别表示:旋转图像分辨率;最小允许输入点与搜索曲面点之间的法线夹角的余弦值,以便在支撑中保留该点;
	//小支持点数,以正确估计自旋图像。如果在某个点支持包含的点较少,则抛出异常
	spin_image_descriptor.setInputCloud(cloud);
	spin_image_descriptor.setInputNormals(normals);
	// 使用法线计算的KD树
	spin_image_descriptor.setSearchMethod(tree);
	pcl::PointCloud<pcl::Histogram<153> >::Ptr spin_images(new pcl::PointCloud<pcl::Histogram<153> >);
	spin_image_descriptor.setRadiusSearch(40);
	// 计算spin image图像
	spin_image_descriptor.compute(*spin_images);
  1. pcl::SpinImageEstimation<pcl::PointXYZ, pcl::Normal, pcl::Histogram<153> > spin_image_descriptor(8, 0.05, 10);

    • 这行代码创建了一个 Spin Image 特征估计器对象。
    • 第一个参数 8 是旋转图像分辨率,指定了生成的 spin image 的分辨率。这个值越大,生成的 spin image 的分辨率就越高,但计算开销也会增加。
    • 第二个参数 0.05 是最小允许输入点与搜索曲面点之间的法线夹角的余弦值。这个值控制了在计算 spin image 时保留点的支撑度。值越大,要求点与法线的夹角越小,以保留更多的点。较小的值会过滤掉与法线夹角较大的点,从而减少噪声的影响。
    • 第三个参数 10 是小支持点数,指定了在估计 spin image 时,支持点所需的最小点数。如果某个点支持包含的点数少于这个值,将抛出异常。
  2. spin_image_descriptor.setInputCloud(cloud);

    • 这行代码设置输入点云数据。
  3. spin_image_descriptor.setInputNormals(normals);

    • 这行代码设置输入点云的法线数据。
  4. spin_image_descriptor.setSearchMethod(tree);

    • 这行代码设置了法线计算的 KD 树搜索方法。KD 树是一种数据结构,用于快速查找最近邻点。
  5. pcl::PointCloud<pcl::Histogram<153> >::Ptr spin_images(new pcl::PointCloud<pcl::Histogram<153> >);

    • 这行代码创建了一个指向 pcl::PointCloud<pcl::Histogram<153> > 类型对象的指针,用于存储计算得到的 spin image。
  6. spin_image_descriptor.setRadiusSearch(40);

    • 这行代码设置了搜索半径,指定了在计算 spin image 时用于搜索最近邻点的半径范围。只有位于这个半径范围内的点才会被考虑在内。这个值越大,搜索的范围就越广,但也会增加计算开销。
  7. spin_image_descriptor.compute(*spin_images);

    • 这行代码执行 spin image 的计算,并将结果存储在 spin_images 中。

参数的设置会直接影响到计算得到的 spin image 的质量和计算效率。调整这些参数可以根据具体的应用场景和需求进行优化,以获得更好的特征描述符。

	// 显示和检索第一点的自旋图像描述符向量。
	pcl::Histogram<153> first_descriptor = spin_images->points[0];
	cout << first_descriptor << endl;

	//========自旋图像描述符可视化=============================

	pcl::visualization::PCLPlotter plotter;
	plotter.addFeatureHistogram(*spin_images, 600); //设置的横坐标长度,该值越大,则显示的越细致
	plotter.setWindowName("Spin Image");
	plotter.plot();
  1. pcl::Histogram<153> first_descriptor = spin_images->points[0];

    • 这行代码获取了计算得到的自旋图像描述符向量中的第一个描述符,并将其存储在 first_descriptor 变量中。
    • pcl::Histogram<153> 表示自旋图像描述符的数据类型,其中 153 是描述符的维度。
  2. pcl::visualization::PCLPlotter plotter;

    • 这行代码创建了一个 PCLPlotter 对象,用于绘制可视化图形。
  3. plotter.addFeatureHistogram(*spin_images, 600);

    • 这行代码将自旋图像描述符向量添加到绘图器中进行可视化。
    • *spin_images 是指向自旋图像描述符向量的指针。
    • 600 是设置的横坐标长度,该值越大,则显示的越细致。
  4. plotter.setWindowName("Spin Image");

    • 这行代码设置绘图窗口的标题为 "Spin Image"。
  5. plotter.plot();

    • 这行代码执行绘图操作,显示自旋图像描述符的可视化结果。

参数的设置会直接影响到可视化结果的显示效果。例如,通过调整横坐标长度可以改变柱状图的分辨率,使其更细致或更粗略。可根据需要调整这些参数以获得更好的可视化效果。

结果:

 

二、 Spin Image结合ICP配准

C++

#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/features/normal_3d_omp.h>
#include <pcl/features/spin_image.h>
#include <pcl/registration/icp.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>
#include <pcl/keypoints/iss_3d.h>
#include <pcl/registration/ia_ransac.h>
#include <pcl/visualization/pcl_plotter.h>
typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloud;
typedef pcl::Histogram<153> HisT;
typedef pcl::PointCloud<HisT> PointCloudHis;
typedef pcl::VFHSignature308 VFHT;
typedef pcl::PointCloud<VFHT> PointCloudVFH;
typedef pcl::search::KdTree<PointT> Tree;
// 关键点提取
void extract_keypoint(PointCloud::Ptr& cloud, PointCloud::Ptr& keypoint, Tree::Ptr& tree)
{
    pcl::ISSKeypoint3D<PointT, PointT> iss;
    iss.setInputCloud(cloud);
    iss.setSearchMethod(tree);
    iss.setNumberOfThreads(8);     //初始化调度器并设置要使用的线程数
    iss.setSalientRadius(5);  // 设置用于计算协方差矩阵的球邻域半径
    iss.setNonMaxRadius(5);   // 设置非极大值抑制应用算法的半径
    iss.setThreshold21(0.95);     // 设定第二个和第一个特征值之比的上限
    iss.setThreshold32(0.95);     // 设定第三个和第二个特征值之比的上限
    iss.setMinNeighbors(6);       // 在应用非极大值抑制算法时,设置必须找到的最小邻居数
    iss.compute(*keypoint);
}
// 法线计算和 计算特征点的Spinimage描述子
void computeKeyPointsSpinimage(PointCloud::Ptr& cloud_in, PointCloud::Ptr& key_cloud, PointCloudHis::Ptr& dsc, Tree::Ptr& tree)
{
    pcl::NormalEstimationOMP<PointT, pcl::Normal> n;//OMP加速
    pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
    n.setNumberOfThreads(6);//设置openMP的线程数
    n.setInputCloud(key_cloud);
    n.setSearchSurface(cloud_in);
    n.setSearchMethod(tree);
    //n.setKSearch(10);
    n.setRadiusSearch(0.3);
    n.compute(*normals);


    pcl::SpinImageEstimation<PointT, pcl::Normal, HisT> spin_image_descriptor;
    spin_image_descriptor.setInputCloud(key_cloud);
    spin_image_descriptor.setInputNormals(normals);
    spin_image_descriptor.setSearchMethod(tree);
    spin_image_descriptor.setRadiusSearch(40); // 设置搜索半径
    spin_image_descriptor.compute(*dsc);
}

//Histogram<153>转换成VFHSignature308
void histogramVFHSignature(PointCloudHis::Ptr& dsc, PointCloudVFH::Ptr feature)
{
    pcl::VFHSignature308 midpoint;
    for (int i = 0; i < 308; i++) {
        midpoint.histogram[i] = 0;
    }
    for (int j = 0; j < dsc->size(); ++j)
    {
        for (int i = 0; i < 153; i++)
        {
            midpoint.histogram[i] = dsc->points[j].histogram[i];
        }
        feature->push_back(midpoint);
    }
}

// 点云可视化
void visualize_pcd(PointCloud::Ptr icp_result, PointCloud::Ptr cloud_target)
{
    //创建初始化目标
    pcl::visualization::PCLVisualizer viewer("registration Viewer");
    pcl::visualization::PointCloudColorHandlerCustom<PointT> final_h(icp_result, 0, 255, 0);
    pcl::visualization::PointCloudColorHandlerCustom<PointT> tgt_h(cloud_target, 255, 0, 0);
    viewer.setBackgroundColor(0, 0, 0);
    viewer.addPointCloud(cloud_target, tgt_h, "tgt cloud");
    viewer.addPointCloud(icp_result, final_h, "final cloud");

    while (!viewer.wasStopped())
    {
        viewer.spinOnce(100);
        boost::this_thread::sleep(boost::posix_time::microseconds(100000));
    }
}

void visualize_his(PointCloudHis::Ptr& dsc)
{
    pcl::visualization::PCLPlotter plotter;
    plotter.addFeatureHistogram(*dsc, 600); //设置的横坐标长度,该值越大,则显示的越细致
    plotter.setWindowName("Spin Image");
    plotter.plot();
}
int main()
{
    // 加载源点云和目标点云
    PointCloud::Ptr cloud(new PointCloud);
    PointCloud::Ptr cloud_target(new PointCloud);
    if (pcl::io::loadPCDFile<pcl::PointXYZ>("pcd/pig_view1.pcd", *cloud) == -1)
    {
        PCL_ERROR("加载点云失败\n");
    }
    if (pcl::io::loadPCDFile<pcl::PointXYZ>("pcd/pig_view2.pcd", *cloud_target) == -1)
    {
        PCL_ERROR("加载点云失败\n");
    }
    visualize_pcd(cloud, cloud_target);
    //关键点
    pcl::PointCloud<PointT>::Ptr keypoints1(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<PointT>::Ptr keypoints2(new pcl::PointCloud<pcl::PointXYZ>);
    Tree::Ptr tree(new Tree);

    extract_keypoint(cloud, keypoints1, tree);
    extract_keypoint(cloud_target, keypoints2, tree);
    cout << "iss完成!" << endl;
    cout << "cloud的关键点的个数:" << keypoints1->size() << endl;
    cout << "cloud_target的关键点的个数:" << keypoints2->size() << endl;



    // 使用SpinImage描述符计算特征
    PointCloudHis::Ptr source_features(new PointCloudHis);
    PointCloudHis::Ptr target_features(new PointCloudHis);
    computeKeyPointsSpinimage(cloud, keypoints1, source_features, tree);
    computeKeyPointsSpinimage(cloud_target, keypoints2, target_features, tree);
    cout << "SpinImage完成!" << endl;

    //========自旋图像描述符可视化=============================
    // 显示和检索第一点的自旋图像描述符向量。
    pcl::Histogram<153> first_descriptor_src = source_features->points[0];
    pcl::Histogram<153> first_descriptor_tgt = target_features->points[0];
    cout << "source_features第一点的自旋图像描述符向量:" << endl << first_descriptor_src << endl;
    cout << "target_features第一点的自旋图像描述符向量:" << endl << first_descriptor_tgt << endl;
    visualize_his(source_features);
    visualize_his(target_features);



    //Histogram<153>转换成VFHSignature308
    PointCloudVFH::Ptr source_feature(new PointCloudVFH);
    PointCloudVFH::Ptr target_feature(new PointCloudVFH);
    histogramVFHSignature(source_features, source_feature);
    histogramVFHSignature(target_features, target_feature);
    cout << "Histogram<153>转换VFHSignature308完成!" << endl;

    //SAC配准
    pcl::SampleConsensusInitialAlignment<PointT, PointT, VFHT> scia;
    scia.setInputSource(keypoints1);
    scia.setInputTarget(keypoints2);
    scia.setSourceFeatures(source_feature);
    scia.setTargetFeatures(target_feature);
    scia.setMinSampleDistance(7);     // 设置样本之间的最小距离
    scia.setNumberOfSamples(100);       // 设置每次迭代计算中使用的样本数量(可省),可节省时间
    scia.setCorrespondenceRandomness(6);// 在选择随机特征对应时,设置要使用的邻居的数量;
    PointCloud::Ptr sac_result(new PointCloud);
    scia.align(*sac_result);
    Eigen::Matrix4f sac_trans;
    sac_trans = scia.getFinalTransformation();
    cout << "SAC配准完成!" << endl;

    //icp配准
    PointCloud::Ptr icp_result(new PointCloud);
    pcl::IterativeClosestPoint<PointT, PointT> icp;
    icp.setInputSource(keypoints1);
    icp.setInputTarget(keypoints2);
    icp.setMaxCorrespondenceDistance(20);
    icp.setMaximumIterations(35);        // 最大迭代次数
    icp.setTransformationEpsilon(1e-10); // 两次变化矩阵之间的差值
    icp.setEuclideanFitnessEpsilon(0.01);// 均方误差
    icp.align(*icp_result, sac_trans);
    cout << "ICP配准完成!" << endl;

    // 输出配准结果
    std::cout << "ICP converged: " << icp.hasConverged() << std::endl;
    std::cout << "Transformation matrix:\n" << icp.getFinalTransformation() << std::endl;
    Eigen::Matrix4f icp_trans;
    icp_trans = icp.getFinalTransformation();
    cout << icp_trans << endl;
    使用创建的变换对未过滤的输入点云进行变换
    pcl::transformPointCloud(*cloud, *icp_result, icp_trans);

    visualize_pcd(icp_result, cloud_target);

    return 0;
}

关键代码解析:

typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloud;
typedef pcl::Histogram<153> HisT;
typedef pcl::PointCloud<HisT> PointCloudHis;
typedef pcl::VFHSignature308 VFHT;
typedef pcl::PointCloud<VFHT> PointCloudVFH;
typedef pcl::search::KdTree<PointT> Tree;

 

  1. typedef pcl::PointXYZ PointT;

    • 这行代码定义了一个名为 PointT 的新类型,它实际上是 pcl::PointXYZ 的别名。pcl::PointXYZ 是 PCL 中表示三维点的结构体,包含了 xy 和 z 坐标。
  2. typedef pcl::PointCloud<PointT> PointCloud;

    • 这行代码定义了一个名为 PointCloud 的新类型,它是 pcl::PointCloud<PointT> 的别名。pcl::PointCloud 是 PCL 中用于表示点云的类模板,而 PointT 则是点云中包含的点的类型。
  3. typedef pcl::Histogram<153> HisT;

    • 这行代码定义了一个名为 HisT 的新类型,它是 pcl::Histogram<153> 的别名。pcl::Histogram 通常用于表示直方图,而这里的 <153> 表示直方图的大小为 153。
  4. typedef pcl::PointCloud<HisT> PointCloudHis;

    • 这行代码定义了一个名为 PointCloudHis 的新类型,它是 pcl::PointCloud<HisT> 的别名。这表示一个点云,其中每个点都是类型为 HisT 的直方图。
  5. typedef pcl::VFHSignature308 VFHT;

    • 这行代码定义了一个名为 VFHT 的新类型,它是 pcl::VFHSignature308 的别名。pcl::VFHSignature308 是 PCL 中用于表示视点特征直方图(VFH)的类型,这里的 308 表示 VFH 的长度为 308。
  6. typedef pcl::PointCloud<VFHT> PointCloudVFH;

    • 这行代码定义了一个名为 PointCloudVFH 的新类型,它是 pcl::PointCloud<VFHT> 的别名。这表示一个点云,其中每个点都是类型为 VFHT 的 VFH 特征。
  7. typedef pcl::search::KdTree<PointT> Tree;

    • 这行代码定义了一个名为 Tree 的新类型,它是 pcl::search::KdTree<PointT> 的别名。pcl::search::KdTree 是 PCL 中用于进行点云搜索的类,这里指定了 PointT 作为搜索的点类型。

void extract_keypoint(PointCloud::Ptr& cloud, PointCloud::Ptr& keypoint, Tree::Ptr& tree)
{
    pcl::ISSKeypoint3D<PointT, PointT> iss;
    iss.setInputCloud(cloud);
    iss.setSearchMethod(tree);
    iss.setNumberOfThreads(8);     //初始化调度器并设置要使用的线程数
    iss.setSalientRadius(5);  // 设置用于计算协方差矩阵的球邻域半径
    iss.setNonMaxRadius(5);   // 设置非极大值抑制应用算法的半径
    iss.setThreshold21(0.95);     // 设定第二个和第一个特征值之比的上限
    iss.setThreshold32(0.95);     // 设定第三个和第二个特征值之比的上限
    iss.setMinNeighbors(6);       // 在应用非极大值抑制算法时,设置必须找到的最小邻居数
    iss.compute(*keypoint);
}
  1. void extract_keypoint(PointCloud::Ptr& cloud, PointCloud::Ptr& keypoint, Tree::Ptr& tree)

    • 这是一个函数定义,用于从输入的点云 cloud 中提取关键点,并将结果存储在 keypoint 中。tree 是 KD 树的搜索方法。
  2. pcl::ISSKeypoint3D<PointT, PointT> iss;

    • 创建了一个 ISS 关键点检测器对象。PointT 应该是点云中点的类型。
  3. iss.setInputCloud(cloud);

    • 设置输入点云数据。
  4. iss.setSearchMethod(tree);

    • 设置搜索方法,这里使用了提供的 KD 树。
  5. iss.setNumberOfThreads(8);

    • 设置初始化调度器并设置要使用的线程数。这个参数用于控制并行计算的线程数量。设置为 8 表示使用 8 个线程,从而提高计算效率。
  6. iss.setSalientRadius(5);

    • 设置用于计算协方差矩阵的球邻域半径。这个值影响关键点的计算,较大的值可能导致检测到更大的关键点。
  7. iss.setNonMaxRadius(5);

    • 设置非极大值抑制应用算法的半径。这个值影响检测到的关键点之间的最小距离,较大的值可能导致保留更少的关键点。
  8. iss.setThreshold21(0.95);

    • 设置第二个和第一个特征值之比的上限。这个参数影响关键点的选择,控制特征值之间的比率。
  9. iss.setThreshold32(0.95);

    • 设置第三个和第二个特征值之比的上限。同样,这个参数也影响关键点的选择,控制特征值之间的比率。
  10. iss.setMinNeighbors(6);

    • 在应用非极大值抑制算法时,设置必须找到的最小邻居数。这个参数控制关键点周围邻居的数量,可能影响非极大值抑制的效果。
  11. iss.compute(*keypoint);

    • 执行关键点的计算,并将结果存储在 keypoint 中。
void computeKeyPointsSpinimage(PointCloud::Ptr& cloud_in, PointCloud::Ptr& key_cloud, PointCloudHis::Ptr& dsc, Tree::Ptr& tree)
{
    pcl::NormalEstimationOMP<PointT, pcl::Normal> n;//OMP加速
    pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
    n.setNumberOfThreads(6);//设置openMP的线程数
    n.setInputCloud(key_cloud);
    n.setSearchSurface(cloud_in);
    n.setSearchMethod(tree);
    //n.setKSearch(10);
    n.setRadiusSearch(0.3);
    n.compute(*normals);


    pcl::SpinImageEstimation<PointT, pcl::Normal, HisT> spin_image_descriptor;
    spin_image_descriptor.setInputCloud(key_cloud);
    spin_image_descriptor.setInputNormals(normals);
    spin_image_descriptor.setSearchMethod(tree);
    spin_image_descriptor.setRadiusSearch(40); // 设置搜索半径
    spin_image_descriptor.compute(*dsc);
}
  1. void computeKeyPointsSpinimage(PointCloud::Ptr& cloud_in, PointCloud::Ptr& key_cloud, PointCloudHis::Ptr& dsc, Tree::Ptr& tree)

    • 这是一个函数声明,它接受四个参数:
      • cloud_in:输入的点云,类型为 PointCloud::Ptr,表示指向点云的指针。
      • key_cloud:关键点的点云,类型为 PointCloud::Ptr,表示指向关键点点云的指针。
      • dsc:描述子的点云,类型为 PointCloudHis::Ptr,表示指向描述子点云的指针。
      • tree:搜索树,类型为 Tree::Ptr,表示指向搜索树对象的指针。
  2. pcl::NormalEstimationOMP<PointT, pcl::Normal> n;

    • 创建了一个使用 OpenMP 加速的法线估计对象 n,该对象用于计算点云中每个点的法线。
  3. pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);

    • 创建了一个指向法线点云的指针 normals,该点云用于存储计算得到的法线信息。
  4. n.setNumberOfThreads(6);

    • 设置 OpenMP 的线程数为 6,以便并行计算法线。
  5. n.setInputCloud(key_cloud);

    • 将关键点的点云设置为法线估计的输入。
  6. n.setSearchSurface(cloud_in);

    • 设置搜索表面为输入点云 cloud_in,这意味着法线估计将在整个输入点云上进行搜索。
  7. n.setSearchMethod(tree);

    • 设置法线估计所用的搜索方法为 tree,即使用了之前定义的搜索树。
  8. n.setRadiusSearch(0.3);

    • 设置法线估计的搜索半径为 0.3,这表示法线将在距离每个点不超过 0.3 的邻域内计算。
  9. n.compute(*normals);

    • 执行法线估计,计算每个关键点的法线,并将结果存储在 normals 指向的点云中。
  10. pcl::SpinImageEstimation<PointT, pcl::Normal, HisT> spin_image_descriptor;

    • 创建了一个 Spin Image 描述子估计对象 spin_image_descriptor,用于计算关键点的 Spin Image 描述子。
  11. spin_image_descriptor.setInputCloud(key_cloud);

    • 将关键点的点云设置为 Spin Image 描述子估计的输入。
  12. spin_image_descriptor.setInputNormals(normals);

    • 将之前计算得到的法线设置为 Spin Image 描述子估计的输入法线。
  13. spin_image_descriptor.setSearchMethod(tree);

    • 设置 Spin Image 描述子估计所用的搜索方法为 tree,即使用之前定义的搜索树。
  14. spin_image_descriptor.setRadiusSearch(40);

    • 设置 Spin Image 描述子估计的搜索半径为 40,这表示描述子将在距离每个关键点不超过 40 的邻域内计算。
  15. spin_image_descriptor.compute(*dsc);

    • 执行 Spin Image 描述子的计算,计算每个关键点的描述子,并将结果存储在 dsc 指向的点云中。

这些参数的设置会影响计算结果的精度、计算速度以及描述子的数量和质量。例如,搜索半径的选择将影响到所考虑的点的数量,从而影响描述子的丰富程度和计算的时间。增加线程数可能会加快法线估计的速度,但也可能导致系统资源的过度使用。

void histogramVFHSignature(PointCloudHis::Ptr& dsc, PointCloudVFH::Ptr feature)
{
    pcl::VFHSignature308 midpoint;
    for (int i = 0; i < 308; i++) {
        midpoint.histogram[i] = 0;
    }
    for (int j = 0; j < dsc->size(); ++j)
    {
        for (int i = 0; i < 153; i++)
        {
            midpoint.histogram[i] = dsc->points[j].histogram[i];
        }
        feature->push_back(midpoint);
    }
}
  1. 由于SpinImage不能直接应用于配准,需要转换成VFH

  2. void histogramVFHSignature(PointCloudHis::Ptr& dsc, PointCloudVFH::Ptr feature)

    • 这是一个函数声明,它接受两个参数:
      • dsc:描述子的点云,类型为 PointCloudHis::Ptr,表示指向描述子点云的指针。
      • feature:VFH 特征的点云,类型为 PointCloudVFH::Ptr,表示指向 VFH 特征点云的指针。
  3. pcl::VFHSignature308 midpoint;

    • 创建了一个长度为 308 的 VFH 签名对象 midpoint,用于存储计算得到的 VFH 特征。
  4. for (int i = 0; i < 308; i++) { midpoint.histogram[i] = 0; }

    • 将 midpoint 对象中的直方图初始化为零,确保所有的直方图元素都被清零。
  5. for (int j = 0; j < dsc->size(); ++j)

    • 循环遍历描述子点云中的每个点。
  6. for (int i = 0; i < 153; i++) { midpoint.histogram[i] = dsc->points[j].histogram[i]; }

    • 将描述子点云中第 j 个点的直方图值复制到 midpoint 对象的直方图中。这个循环的长度是 153,因为 VFH 特征的长度为 308,而描述子长度为 153。
  7. feature->push_back(midpoint);

    • 将存储在 midpoint 中的 VFH 特征添加到 feature 点云中。

参数设置和影响:

  • 代码中的参数主要是描述子的长度和 VFH 特征的长度。在这里,描述子长度为 153,而 VFH 特征的长度为 308。这些长度是基于特定的问题和数据集选择的,不同的问题和数据集可能需要不同的长度。
  • 对于直方图中的值,这段代码假设描述子的长度是 153,因此循环将从 0 到 152 进行迭代。如果描述子的长度不是 153,那么需要相应地调整循环的长度。
  • 由于 VFH 特征是基于描述子计算的,因此描述子的质量和准确性会直接影响到最终的 VFH 特征的质量和准确性。
  • 在实际应用中,还需要考虑到 VFH 特征的归一化、特征选择等步骤,以进一步提高特征的性能和鲁棒性。
    //sac配准
    pcl::SampleConsensusInitialAlignment<PointT, PointT, VFHT> scia;
    scia.setInputSource(keypoints1);
    scia.setInputTarget(keypoints2);
    scia.setSourceFeatures(source_feature);
    scia.setTargetFeatures(target_feature);
    scia.setMinSampleDistance(7);     // 设置样本之间的最小距离
    scia.setNumberOfSamples(100);       // 设置每次迭代计算中使用的样本数量(可省),可节省时间
    scia.setCorrespondenceRandomness(6);// 在选择随机特征对应时,设置要使用的邻居的数量;
    PointCloud::Ptr sac_result(new PointCloud);
    scia.align(*sac_result);
    Eigen::Matrix4f sac_trans;
    sac_trans = scia.getFinalTransformation();
    cout << "SAC配准完成!" << endl;

    //icp配准
    PointCloud::Ptr icp_result(new PointCloud);
    pcl::IterativeClosestPoint<PointT, PointT> icp;
    icp.setInputSource(keypoints1);
    icp.setInputTarget(keypoints2);
    icp.setMaxCorrespondenceDistance(20);
    icp.setMaximumIterations(35);        // 最大迭代次数
    icp.setTransformationEpsilon(1e-10); // 两次变化矩阵之间的差值
    icp.setEuclideanFitnessEpsilon(0.01);// 均方误差
    icp.align(*icp_result, sac_trans);
    cout << "ICP配准完成!" << endl;

    // 输出配准结果
    std::cout << "ICP converged: " << icp.hasConverged() << std::endl;
    std::cout << "Transformation matrix:\n" << icp.getFinalTransformation() << std::endl;
    Eigen::Matrix4f icp_trans;
    icp_trans = icp.getFinalTransformation();
    cout << icp_trans << endl;
    使用创建的变换对未过滤的输入点云进行变换
    pcl::transformPointCloud(*cloud, *icp_result, icp_trans);
  1. pcl::SampleConsensusInitialAlignment<PointT, PointT, VFHT> scia;: 这里创建了一个用于初始对齐的对象 scia,其中 PointT 是点云中点的类型,VFHT 是用于识别特征的估计方法(例如,VFH 特征)。

  2. scia.setInputSource(keypoints1);scia.setInputTarget(keypoints2);: 将待配准的源点云 keypoints1 和目标点云 keypoints2 设置为输入。

  3. scia.setSourceFeatures(source_feature);scia.setTargetFeatures(target_feature);: 设置源点云和目标点云的特征。

  4. scia.setMinSampleDistance(7);, scia.setNumberOfSamples(100);, scia.setCorrespondenceRandomness(6);: 这些函数设置了 Sample Consensus Initial Alignment(SAC-IA)算法的参数,例如最小样本距离、每次迭代使用的样本数量和对应关系随机性等。

  5. PointCloud::Ptr sac_result(new PointCloud);: 创建一个指向点云的指针,用于存储 SAC-IA 算法的结果。

  6. scia.align(*sac_result);: 执行 SAC-IA 配准。

  7. Eigen::Matrix4f sac_trans; sac_trans = scia.getFinalTransformation();: 获取 SAC-IA 配准的最终变换矩阵。

  8. cout << "SAC配准完成!" << endl;: 输出 SAC-IA 配准完成的消息。

  9. PointCloud::Ptr icp_result(new PointCloud);: 创建一个指向点云的指针,用于存储 ICP 算法的结果。

  10. pcl::IterativeClosestPoint<PointT, PointT> icp;: 创建一个 Iterative Closest Point(ICP)对象 icp

  11. icp.setInputSource(keypoints1);icp.setInputTarget(keypoints2);: 设置 ICP 算法的输入。

  12. icp.setMaxCorrespondenceDistance(20);, icp.setMaximumIterations(35);, icp.setTransformationEpsilon(1e-10);, icp.setEuclideanFitnessEpsilon(0.01);: 这些函数设置了 ICP 算法的参数,如最大对应距离、最大迭代次数、变换阈值等。

  13. icp.align(*icp_result, sac_trans);: 执行 ICP 配准,其中 sac_trans 是之前执行 SAC-IA 得到的变换矩阵,作为初始猜测。

  14. cout << "ICP配准完成!" << endl;: 输出 ICP 配准完成的消息。

  15. std::cout << "ICP converged: " << icp.hasConverged() << std::endl;: 输出 ICP 是否收敛的信息。

  16. std::cout << "Transformation matrix:\n" << icp.getFinalTransformation() << std::endl;: 输出最终的变换矩阵。

  17. Eigen::Matrix4f icp_trans; icp_trans = icp.getFinalTransformation();: 获取 ICP 配准的最终变换矩阵。

  18. cout << icp_trans << endl;: 输出变换矩阵。

  19. pcl::transformPointCloud(*cloud, *icp_result, icp_trans);: 使用获取的变换矩阵对原始点云进行变换,将变换后的结果存储在 icp_result 中。

结果:

输入点云与输出点云

对输入点云进行Spin Image自旋图像描述符可视化 

 

对输出点云进行Spin Image自旋图像描述符可视化  

配准结果 

 

输出数据 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1454123.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【机器学习笔记】12 聚类

无监督学习概述 监督学习 在一个典型的监督学习中&#xff0c;训练集有标签&#x1d466; &#xff0c;我们的目标是找到能够区分正样本和负样本的决策边界&#xff0c;需要据此拟合一个假设函数。无监督学习 与此不同的是&#xff0c;在无监督学习中&#xff0c;我们的数据没…

Json格式文件

1.把Java对象转换成Json格式 1.1.导入依赖 这里推荐一个插件Jackson&#xff0c;其提供的类可以让Java的类转换成Jason格式文件 <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><vers…

记录一次安卓手机使用wm命令错误参数,导致的屏幕大小错误以及挽救

使用安卓终端管理器运行wm命令调整屏幕大小 wm size wxh原本分辨率1024x2048&#xff0c;失手调成了800x600&#xff0c;手机屏幕一下子变成800x600 wm size 800x600wm命令重启后依旧会保持分辨率&#xff0c;所以重启手机没有用。 看锁屏界面连解锁图案都没了&#xff0c;通…

苹果公司宣布,为Apple Vision Pro打造了超过600款新应用

深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领域的领跑者。点击订阅&#xff0c;与未来同行&#xff01; 订阅&#xff1a;https://rengongzhineng.io/ 。 2月…

Java学习day13

流&#xff08;Stream&#xff09; 流是一个非常强大的概念&#xff0c;它提供了一种高效且便捷的方式来处理集合数据。你可以将流看作是一系列数据项的管道&#xff0c;你可以对这些数据进行各种操作&#xff0c;如过滤、映射、排序和归约。 流的创建 在Java中&#xff0c;…

MySQL事务的概念

一、事务定义 事务&#xff1a;事务是一个最小的不可在分的工作单元&#xff1b;通常一个事务对应一个完整的业务(例如银行账户转账业务&#xff0c;该业务是一个最小的工作单元)一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成。事务只和DML语句有关&a…

新款条码扫描数据采集终端 仓库盘点机 快递手持机 PDA手持终端

而随着技术的不断进步&#xff0c;数据采集终端也在不断地升级和优化。新款条码扫描数据采集终端、仓库盘点机PDA手持终端等高性能智能数据采集终端&#xff0c;正是在这样的背景下应运而生。 这些高性能智能数据采集终端&#xff0c;采用了最先进的技术和设计&#xff0c;具有…

计算机设计大赛 深度学习中文汉字识别

文章目录 0 前言1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习中文汉字识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xf…

14. Longest Common Prefix(最长公共前缀)

问题描述 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 问题分析 方法一&#xff1a;我们可以假设一个字符串是最长的公共子串&#xff0c;然后去验证&#xff0c;如果此串被验证了&#xff0c;再增加一位字符进行验…

SpringBoot2——工程创建、启动原理分析、配置文件、集成框架

一、SpringBoot简介 1.1 Spring Boot概述 Spring Boot 是所有基于 Spring Framework 5.0 开发的项目。Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序&#xff0c;并且尽可能减少你的配置文件。 设计目的&#xff1a; 用来简化 Spring 应用的初始搭建以及开…

Java实现停车场收费系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 停车位模块2.2 车辆模块2.3 停车收费模块2.4 IC卡模块2.5 IC卡挂失模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 停车场表3.2.2 车辆表3.2.3 停车收费表3.2.4 IC 卡表3.2.5 IC 卡挂失表 四、系统实现五、核心代码…

STM32F1 - 系统时钟SysTick

SysTick 1> SysTick硬件框图2> SysTick的时钟源3> 1ms定时_中断方式4> 思考&#xff1a;无符号数 0 - 255 ?相关资料 1> SysTick硬件框图 SysTick属于Cotex-M3&#xff0c;是CPU外设&#xff1b; SysTick: 位宽24bit&#xff0c; 递减计数&#xff0c;自动重装…

Debezium发布历史136

原文地址&#xff1a; https://debezium.io/blog/2023/01/06/change-data-capture-with-questdb-and-debezium/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. Change Data Capture with QuestDB and Debezium …

UE5中的DataTable说明

创建DataTable 在编辑器中创建 在文件夹空白处右击&#xff0c;选择Miscellaneous/DataTable&#xff0c;如图&#xff1a; 使用代码创建 // 创建DataTable实例 UDataTable* MyDataTable NewObject(); // 创建一个行结构体 UStruct* RowStruct UStruct::CreateEmpty(); // 添…

UE蓝图 Set节点和源码

文章目录 Set节点说明相关源码 Set节点说明 UE蓝图中的Set节点是用于对变量进行赋值操作的重要组件。它具有多种功能和作用&#xff0c;具体如下&#xff1a; 变量赋值&#xff1a;Set节点可以用于设置不同类型的变量值&#xff0c;包括整数、浮点数、布尔值、字符串等。在游戏…

c++开发基础之保障多线程编程中的原子操作InterlockedIncrement和InterlockedDecrement用法详解

一、介绍 在多线程编程中&#xff0c;确保对共享变量进行原子操作是至关重要的。当多个线程同时访问和修改同一共享资源时&#xff0c;如果没有合适的同步机制&#xff0c;可能会导致数据竞争、内存一致性问题&#xff0c;甚至造成程序崩溃。为了解决这个问题&#xff0c;C提供…

【退役之重学前端】JavaScript 类、构造器、原型的关系

ES6中类的概念&#xff0c;我之前花了较长的时间学习Java&#xff0c;所以对类感觉很亲切。我并不满足仅仅会使用&#xff0c;让我们一起深究一下 JavaScript 中的类吧。 构造一个类&#xff0c;并实例化一个对象 class Animal{constructor(name){this.name name;}getName(){…

中国传媒网CEO徐晓艺:媒体融合发展业态新媒体年后在沪召开

近日,在“坚守媒体初心,拥抱AI时代”2023外滩新媒体年会上,有多项合作达成。 在当前竞争激烈的市场环境中,媒体宣传已经成为企业品牌推广不可或缺的一环。对于很多企业来说往往会犯一个错误,就是默默地参加了展会,并没有进行媒体营销。展会是一种非常有力的宣传和推广方式,可以…

Vue中 如何监听键盘事件中的按键

在Web前端开发中&#xff0c;键盘事件的处理是非常常见的需求之一。而在Vue框架中&#xff0c;如何监听键盘事件中的按键是一个相对简单但又很实用的功能。本文将为你介绍如何在Vue中监听键盘事件&#xff0c;并演示一些常用的按键操作。 首先&#xff0c;在Vue中监听键盘事件…

数学建模【线性规划】

一、线性规划简介 线性规划通俗讲就是“有限的资源中获取最大的收益”&#xff08;优化类问题&#xff09;。而且所有的变量关系式都是线性的&#xff0c;不存在x、指数函数、对数函数、反比例函数、三角函数等。此模型要优化的就是在一组线性约束条件下&#xff0c;求线性目标…