参考网址: (112条消息) Scan Context 学习记录_春至冬去-CSDN博客_scancontext
Lidar定位:Scan Context - 知乎 (zhihu.com)
irapkaist/scancontext: Global LiDAR descriptor for place recognition and long-term localization
原创投稿 | ScanContext 论文详解 - 用途:Lidar SLAM 回环检测、空间描述符 - 哔哩哔哩 (bilibili.com)
(112条消息) 激光闭环检测Scancontext_weixin_35536487的博客-CSDN博客_scancontext
Slam-Project-Of-MyOwn/src at master · softdream/Slam-Project-Of-MyOwn (github.com)
流程
给定一帧点云,划分成20个环,每个环分成60等份,一共1200个格子
每个格子存里面点的最大高度值(z值),这样一帧点云就用一个二维图像表示了,想象成一个带高度的俯视图,或者地形图,记为scan context
scan context进一步计算列的均值,得到一个1x60的向量,记为sector key;计算行的均值,得到一个20x1的向量,记为ring key
用sector key构造kd-tree,并且执行knn搜索
对于候选匹配scan context,首先要左右循环偏移一下,对齐,实际会用sector key去对齐,得到一个偏移量
对候选匹配scan context,施加偏移量,然后作比较
代码 make scanContext 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 MatrixXd SCManager::makeScancontext ( pcl::PointCloud<SCPointType> & _scan_down ) { int num_pts_scan_down = _scan_down.points.size(); const int NO_POINT = -1000 ; MatrixXd desc = NO_POINT * MatrixXd::Ones(PC_NUM_RING, PC_NUM_SECTOR); SCPointType pt; float azim_angle, azim_range; int ring_idx, sctor_idx; for (int pt_idx = 0 ; pt_idx < num_pts_scan_down; pt_idx++) { pt.x = _scan_down.points[pt_idx].x; pt.y = _scan_down.points[pt_idx].y; pt.z = _scan_down.points[pt_idx].z + LIDAR_HEIGHT; azim_range = sqrt (pt.x * pt.x + pt.y * pt.y); azim_angle = xy2theta(pt.x, pt.y); if ( azim_range > PC_MAX_RADIUS ) continue ; ring_idx = std ::max( std ::min( PC_NUM_RING, int (ceil ( (azim_range / PC_MAX_RADIUS) * PC_NUM_RING )) ), 1 ); sctor_idx = std ::max( std ::min( PC_NUM_SECTOR, int (ceil ( (azim_angle / 360.0 ) * PC_NUM_SECTOR )) ), 1 ); if ( desc(ring_idx-1 , sctor_idx-1 ) < pt.z ) desc(ring_idx-1 , sctor_idx-1 ) = pt.z; } for ( int row_idx = 0 ; row_idx < desc.rows(); row_idx++ ) for ( int col_idx = 0 ; col_idx < desc.cols(); col_idx++ ) if ( desc(row_idx, col_idx) == NO_POINT ) desc(row_idx, col_idx) = 0 ; return desc; }
make Ringkey 1 2 3 4 5 6 7 8 9 10 11 12 13 MatrixXd SCManager::makeRingkeyFromScancontext ( Eigen::MatrixXd &_desc ) { Eigen::MatrixXd invariant_key (_desc.rows(), 1 ) ; for ( int row_idx = 0 ; row_idx < _desc.rows(); row_idx++ ) { Eigen::MatrixXd curr_row = _desc.row(row_idx); invariant_key(row_idx, 0 ) = curr_row.mean(); } return invariant_key; }
make Sectorkey 1 2 3 4 5 6 7 8 9 10 11 12 13 MatrixXd SCManager::makeSectorkeyFromScancontext ( Eigen::MatrixXd &_desc ) { Eigen::MatrixXd variant_key (1 , _desc.cols()) ; for ( int col_idx = 0 ; col_idx < _desc.cols(); col_idx++ ) { Eigen::MatrixXd curr_col = _desc.col(col_idx); variant_key(0 , col_idx) = curr_col.mean(); } return variant_key; }
调整偏移,获得偏移量 按列均值,目的是,便于通过调整偏移量来实现对scanContext进行比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int SCManager::fastAlignUsingVkey ( MatrixXd & _vkey1, MatrixXd & _vkey2) { int argmin_vkey_shift = 0 ; double min_veky_diff_norm = 10000000 ; for ( int shift_idx = 0 ; shift_idx < _vkey1.cols(); shift_idx++ ) { MatrixXd vkey2_shifted = circshift(_vkey2, shift_idx); MatrixXd vkey_diff = _vkey1 - vkey2_shifted; double cur_diff_norm = vkey_diff.norm(); if ( cur_diff_norm < min_veky_diff_norm ) { argmin_vkey_shift = shift_idx; min_veky_diff_norm = cur_diff_norm; } } return argmin_vkey_shift; }
闭环检测 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 std ::pair <int , float > SCManager::detectLoopClosureID ( void ) ;std ::pair <double , int > SCManager::distanceBtnScanContext ( MatrixXd &_sc1, MatrixXd &_sc2 ) { MatrixXd vkey_sc1 = makeSectorkeyFromScancontext( _sc1 ); MatrixXd vkey_sc2 = makeSectorkeyFromScancontext( _sc2 ); int argmin_vkey_shift = fastAlignUsingVkey( vkey_sc1, vkey_sc2 ); const int SEARCH_RADIUS = round( 0.5 * SEARCH_RATIO * _sc1.cols() ); std ::vector <int > shift_idx_search_space { argmin_vkey_shift }; for ( int ii = 1 ; ii < SEARCH_RADIUS + 1 ; ii++ ) { shift_idx_search_space.push_back( (argmin_vkey_shift + ii + _sc1.cols()) % _sc1.cols() ); shift_idx_search_space.push_back( (argmin_vkey_shift - ii + _sc1.cols()) % _sc1.cols() ); } std ::sort(shift_idx_search_space.begin(), shift_idx_search_space.end()); int argmin_shift = 0 ; double min_sc_dist = 10000000 ; for ( int num_shift: shift_idx_search_space ) { MatrixXd sc2_shifted = circshift(_sc2, num_shift); double cur_sc_dist = distDirectSC( _sc1, sc2_shifted ); if ( cur_sc_dist < min_sc_dist ) { argmin_shift = num_shift; min_sc_dist = cur_sc_dist; } } return make_pair (min_sc_dist, argmin_shift); }
注意 scanContext方法只是用于简单的位姿对比,对于精确位姿还需要最后使用更加精确的手段进行定位,比如icp,scantomap,图优化都可以.
简单介绍回环检测与图优化结合方式
首先通过sccan_to_map检测是否超过一定距离与角度,若是,设为关键帧
将关键帧保存数组
生成scanContext并加入图优化顶点
检测是否有回环,若有,则根据记录下来的顶点,scan,运动坐标进行图优化,更新坐标
重新建图:先删除之前所有建图信息, 根据估计的关键姿态重建地图