手写SAC_IA实现基于FPFH描述子的点云帧间匹配

1-特征点直方图(PFH):

        计算两个点的法向量的关系,设两个点的法向量为ns和nt,ns和nt的关系可以用三个角度(两个向量的旋转关系)+两点之间的欧氏距离来衡量。

过程:确定查询点pi,确定pi的搜索半径r,确在r内pi点的k近邻的所有元素集合M,计算M中的所有点的两两点间的法线关系,创建出直方图;

n个点的点云的PFH,时间复杂度O(n*k^2);

2-快速特征点直方图(FPFH):

        先计算查询点pi的SPFH(简单点特征直方图),即只计算查询点pi与邻域点之间的法线关系;重新确定k近邻,计算邻域点k近邻的SPFH,以下公式计算pi点的FPFH值:FPFH(pi) = 

5c85e4b20c754542aca55c4bf070a64c.png

 其中,k为近邻值,wk为权重,一般为距离;

n 个点 的点云的FPFH,时间复杂度O(n*k);

3-差异:PFH计算查询点精确的r范围所有点之间的关系,FPFH计算查询点r内只与近邻点的关系以及近邻点r内的关系(2r内);

4-pcl代码:

输入点云:kitti的 64线束velo

【描述子提取】:

pcl::FPFHSignature33说明:内部创建了不相关的直方图11个(pcl默认的),特征维度3,每个特征对应一个直方图,所以是FPFHSignature33(横轴33);

pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normal;   
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());  
pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>); 
normal.setInputCloud(inut_cloud);    
normal.setSearchMethod(tree);      
normal.setRadiusSearch(0.6);      //查询点周领域搜索半径0.6  
normal.compute(*cloud_normals);   //计算法线
//构建fpfh对象,传入 点云,法线,计算出fpfh特征        
//构建kd树搜索,速度快,FPFHSignature33: 3个角特征 × 11个直方图   
pcl::FPFHEstimation<pcl::PointXYZ, pcl::Normal, pcl::FPFHSignature33> fpfh;  
pcl::search::KdTree<PointXYZ>::Ptr tree_fpfh(new pcl::search::KdTree<pcl::PointXYZ>);       
fpfh.setInputCloud(inut_cloud);       
fpfh.setInputNormals(cloud_normals);   
fpfh.setSearchMethod(tree_fpfh);         
//    fpfh特征 
pcl::PointCloud<pcl::FPFHSignature33>::Ptr fpfhs(new pcl::PointCloud<pcl::FPFHSignature33>());     
fpfh.setRadiusSearch(0.8);       // 使用半径必须大于估计法线时使用的半径       fpfh.compute(*fpfhs);

输入源点云时:

0e305c5473c249078b7518220ea9fb03.png

095e1b35ee2b48399835ce157584aa50.png

输入滤波后的点云时:

504edad17a0446bf8a52376f786efe9c.png

5467014c1126463896e9ff3e991adb01.png

 ==> 点云数量对描述子提取速度影响甚大,我用原始点云12w左右,速度21s才提取完,更别说后面配准的时间,点云数目再1w左右,提取效率还算可以,0.09s(我电脑比较拉跨)。

【帧间匹配,位姿估计】:

pcl库计算normal和fpfh可以使用openMP加速:

#include <pcl/features/normal_3d_omp.h>//使用OMP需要添加的头文件
#include <pcl/features/fpfh_omp.h> //fpfh加速计算的omp(多核并行计算)
//-------------------------法向量估计-----------------------    
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>()); 
pcl::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> n_est; 
n_est.setInputCloud(input_cloud);    
n_est.setNumberOfThreads(8);//设置openMP的线程数    
n_est.setSearchMethod(tree);  
n_est.setKSearch(10);   
n_est.compute(*normals);
//------------------FPFH估计-------------------------------
pcl::PointCloud<pcl::FPFHSignature33>::Ptr fpfh(newpcl::PointCloud<pcl::FPFHSignature33>()); 
pcl::FPFHEstimationOMP<pcl::PointXYZ, pcl::Normal, pcl::FPFHSignature33> f_est;  
f_est.setNumberOfThreads(8); //指定8核计算    
f_est.setInputCloud(input_cloud);   
f_est.setInputNormals(normals);    
f_est.setSearchMethod(tree);    
f_est.setKSearch(10);    
f_est.compute(*fpfh);

pcl库的sac_ia配准结果:(源点云较目标点云移动(x=0.5, y=0.5))

对比上一篇文章的pcl的icp和ndt配准,此次配准结果是相当准的,但是时间很长很长!!!

RANSAC算法配准的实现步骤:[随机采样一致性]
(1)在对应点集中随机选取3个对应点对,并求解刚体变换矩阵;
(2)计算对应点集中剩余点对在此刚体变换矩阵下的距离误差,若其中一点对的距离误差小于设定的阈值误差,则该点为样本内点,否则为样本外点,同时统计样本内点的数目;
(3)重复以上步骤直至抵达迭代次数的上限。统计不同刚体变换模型下的样本内点数量,样本内点数量最多的作为最佳数学模型,保留所有样本内点,剔除样本外点之后,剩余点对作为正确的点对用于点云配准操作;

SAC_IA算法配准的实现步骤:[采样一致性初始对齐算法]

1)计算两幅点云的特征(这里我测试的是fpfh);

2)根据特征,进行最近邻搜索,得到source特征点对应的K个target特征;

3)随机采样n(n>=3便于计算)个source点,再为每个source点匹配其K近邻的target中的点,形成n个点对;

4)进行匹配,计算R,t,重复计算,直到两幅点云的误差满足收敛条件,则输出最佳最佳变换矩阵;

SAC_IA 改进之处在于:

1)限制了采样点之间的距离;

2)不再是RANSAC直接的点与点距离的查找,而是根据特征找点;

以下是我手写的sac_ia配准代码,还请大家探讨探讨,完整代码上传到: git@github.com:qqngwzlb/pcl_registration.git

// 目标点云特征target_fpfhs,源点云特征source_fpfhs,采样点大小sample_size,输出变换矩阵T
void my_sac_ia ( pcl::PointCloud<pcl::PointXYZ>::Ptr& target_cloud, 
                 pcl::PointCloud<pcl::PointXYZ>::Ptr& source_cloud, 
                 pcl::PointCloud<pcl::FPFHSignature33>::Ptr& target_fpfhs,
                 pcl::PointCloud<pcl::FPFHSignature33>::Ptr& source_fpfhs,
                 int sample_size, Eigen::Matrix4d& T) {  

       // std::cout << "source->at: " << target_cloud->at(0) << std::endl;
       // (26.5125,5.4335,-11.2905) ==> 类型pcl::PointXYZ
// 1-查找  为每个源特征查找对应 k个近邻的目标特征  
       int K = 10;   // 设置K的个数  
       std::vector<int> idx(K);  // 创建一个 搜索后 保存 的 点的索引值 的 向量
       std::vector<float> squaredDis(K);     // 创建一个 搜索后 保存 的 点的距离平方值 的 向量     
       size_t source_size = source_fpfhs->size();
       std::vector<relate> matchSet(source_size);        // 作为左值,必须先初始化

       pcl::search::KdTree<pcl::FPFHSignature33>::Ptr fpfh_tree(new pcl::search::KdTree<pcl::FPFHSignature33>());
       fpfh_tree->setInputCloud(target_fpfhs);
       // 通过描述子找最近邻
       for (int i=0; i<source_size; ++i) { 
              // source_fpfhs[i] 与 target_fpfhs最近的K个
              fpfh_tree->nearestKSearch(*source_fpfhs, i, K, idx, squaredDis);
              matchSet.at(i).source_index = i;
              matchSet.at(i).target_index = idx; 
       } 

// 2-在matchSet中随机选择匹配点
       int sampleSize = sample_size;
       std::vector<int> final_target_matches; // 存储点对
       std::vector<int> final_source_matches; 

       srand((unsigned int)time(NULL)); 

   while (1) {
       for(;;) { 
              int temp_rand_idx = (rand() % (source_size-0)) + 0;           // [0, source_size)
              final_source_matches.push_back(matchSet[temp_rand_idx].source_index);
              if (calc_cloud_dist(source_cloud, final_source_matches, 100) == sampleSize) {
                     std::cout << "step1 over!" << std::endl;

                     for (int i = 0; i<final_source_matches.size(); ++i) {

                            std::cout <<     final_source_matches.at(i)  << std::endl;
                            std::cout << "_________________" << std::endl;
                     }
                     break;
              }
       }

       // 为选出的每个源点云的点,在目标点云的k近邻中寻找对应的一个匹配点
       for (int i=0; i<sampleSize; i++) { 

              std::vector<int> source2target_idx_vec( matchSet[final_source_matches.at(i)].target_index );

              int temp_rand_idx = (rand() % (K-0)) + 0; 
              final_target_matches.push_back( source2target_idx_vec.at(temp_rand_idx) );
              source2target_idx_vec.clear();
       }
       std::cout << "step2 over!" << std::endl;
       for (int i = 0; i<final_target_matches.size(); ++i) {

              std::cout << final_target_matches.at(i)  << std::endl;
              std::cout << "_________________" << std::endl;
       }  

// 3-求解:R,t
       std::vector<Eigen::Vector3d>  source_points, target_points;
       T = Eigen::Matrix4d::Identity();
       for (int i=0; i< sampleSize; i++) {
              target_points.push_back( Eigen::Vector3d( target_cloud->points[final_target_matches.at(i)].x, 
                                                        target_cloud->points[final_target_matches.at(i)].y, 
                                                        target_cloud->points[final_target_matches.at(i)].z) );
              source_points.push_back( Eigen::Vector3d(source_cloud->points[final_source_matches.at(i)].x, 
                                                        source_cloud->points[final_source_matches.at(i)].y, 
                                                        source_cloud->points[final_source_matches.at(i)].z) );
              std::cout << "chose source points: \n" << source_points.at(i) << std::endl;
              std::cout << "chose target points: \n" << target_points.at(i) << std::endl;
       }
       
       _3d23d_est(target_points, source_points, T);    
// 误差计算:两次变化矩阵的差值
       int error = 0.0;
       error = compute_error(source_cloud, target_cloud, T);
       if ( error < 2) {
              std::cout << "calculate finished!!!" << std::endl;
              break;
       }else {
              std::cout << "now error:\n" << error << std::endl;
       }
   }

}
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
SAC-IA(Sample Consensus Initial Alignment)是一种点云配准法,具有高效准确的特点。该法主要用于将多个局部点云配准成全局点云,并且能够从初始点云对齐中获取相对较好的初始转换矩阵。 SAC-IA法主要包含以下步骤: 1. 选择随机采样的点对:从参考点云和目标点云中随机选择一定数量的点对。 2. 计初步变换矩阵:通过计两个点对的刚性变换矩阵,估计初始的点云对齐情况。 3. 通过采样一致性检验筛选优质点对:对于剩余的点对,通过计其与估计的初步变换矩阵的拟合程度进行评估,筛选出与估计变换矩阵一致的点对。 4. 重新计最优变换矩阵:通过使用筛选出的优质点对重新计最优的刚性变换矩阵,以进一步提高配准精度。 5. 收敛判断:比较新计的变换矩阵与之前的变换矩阵之的差异,如果差异小于设定的阈值,则认为法已经收敛。 SAC-IA法主要依靠采样一致性检验的方式,通过迭代计出最优的刚性变换矩阵。与传统的ICP法相比,SAC-IA在计效率和配准准确性方面都有所提升。通过随机采样和筛选优质点对的方式,SAC-IA法能够尽可能避免由于噪声或离群点的存在而导致的误配准问题。 总的来说,SAC-IA是一种高效准确的点云配准法,适用于多个局部点云的配准和构建全局点云。它在多个领域,如计机视觉、机器人导航和工业制造中都具有广泛的应用前景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值