loop_detector.hpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // SPDX-License-Identifier: BSD-2-Clause
  2. #ifndef LOOP_DETECTOR_HPP
  3. #define LOOP_DETECTOR_HPP
  4. #include <boost/format.hpp>
  5. #include <hdl_graph_slam/keyframe.hpp>
  6. #include <hdl_graph_slam/registrations.hpp>
  7. #include <hdl_graph_slam/graph_slam.hpp>
  8. #include <g2o/types/slam3d/vertex_se3.h>
  9. namespace hdl_graph_slam {
  10. struct Loop {
  11. public:
  12. EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  13. using Ptr = std::shared_ptr<Loop>;
  14. Loop(const KeyFrame::Ptr& key1, const KeyFrame::Ptr& key2, const Eigen::Matrix4f& relpose) : key1(key1), key2(key2), relative_pose(relpose) {}
  15. public:
  16. KeyFrame::Ptr key1;
  17. KeyFrame::Ptr key2;
  18. Eigen::Matrix4f relative_pose;
  19. };
  20. /**
  21. * @brief this class finds loops by scam matching and adds them to the pose graph
  22. */
  23. class LoopDetector {
  24. public:
  25. typedef pcl::PointXYZI PointT;
  26. /**
  27. * @brief constructor
  28. * @param pnh
  29. */
  30. LoopDetector(ros::NodeHandle& pnh) {
  31. // distance_thresh = pnh.param<double>("distance_thresh", 5.0); // launch 中给15
  32. // accum_distance_thresh = pnh.param<double>("accum_distance_thresh", 8.0); // launch 中给25
  33. // distance_from_last_edge_thresh = pnh.param<double>("min_edge_interval", 5.0);// launch 中给15
  34. distance_thresh = 15;
  35. accum_distance_thresh = 25;
  36. distance_from_last_edge_thresh = 15;
  37. fitness_score_max_range = pnh.param<double>("fitness_score_max_range", std::numeric_limits<double>::max());
  38. // fitness_score_thresh = pnh.param<double>("fitness_score_thresh", 0.5);
  39. fitness_score_thresh = 2.5;
  40. registration = select_registration_method(pnh);
  41. last_edge_accum_distance = 0.0;
  42. }
  43. /**
  44. * @brief detect loops and add them to the pose graph
  45. * @param keyframes keyframes
  46. * @param new_keyframes newly registered keyframes
  47. * @param graph_slam pose graph
  48. */
  49. std::vector<Loop::Ptr> detect(const std::vector<KeyFrame::Ptr>& keyframes, const std::deque<KeyFrame::Ptr>& new_keyframes, hdl_graph_slam::GraphSLAM& graph_slam) {
  50. std::vector<Loop::Ptr> detected_loops;
  51. for(const auto& new_keyframe : new_keyframes) { // 便利所有new_keyframes
  52. auto candidates = find_candidates(keyframes, new_keyframe); // 候选闭环检测, 基于某些策略(如距离、时间等)从已有关键帧中寻找潜在的闭环候选。
  53. // std::cout << "candidates.size() = " << candidates.size() << std::endl;
  54. auto loop = matching(candidates, new_keyframe, graph_slam); // 对候选关键帧和新关键帧new_keyframe进行进一步匹配,通过计算相似度、位姿差异等来确定是否有闭环关系。
  55. if(loop) {
  56. detected_loops.push_back(loop);
  57. }
  58. }
  59. return detected_loops;
  60. }
  61. double get_distance_thresh() const {
  62. return distance_thresh;
  63. }
  64. private:
  65. /**
  66. * @brief find loop candidates. A detected loop begins at one of #keyframes and ends at #new_keyframe
  67. * @param keyframes candidate keyframes of loop start
  68. * @param new_keyframe loop end keyframe
  69. * @return loop candidates
  70. */
  71. std::vector<KeyFrame::Ptr> find_candidates(const std::vector<KeyFrame::Ptr>& keyframes, const KeyFrame::Ptr& new_keyframe) const {
  72. // too close to the last registered loop edge
  73. // if(new_keyframe->accum_distance - last_edge_accum_distance < distance_from_last_edge_thresh) {
  74. // std::cout << "return std::vector<KeyFrame::Ptr>();" << std::endl;
  75. // return std::vector<KeyFrame::Ptr>();
  76. // }
  77. std::vector<KeyFrame::Ptr> candidates;
  78. candidates.reserve(32);
  79. for(const auto& k : keyframes) {
  80. // traveled distance between keyframes is too small
  81. // if(new_keyframe->accum_distance - k->accum_distance < accum_distance_thresh) {
  82. // std::cout << "new_keyframe->accum_distance - k->accum_distance < accum_distance_thresh" << std::endl;
  83. // continue;
  84. // }
  85. const auto& pos1 = k->node->estimate().translation();
  86. const auto& pos2 = new_keyframe->node->estimate().translation();
  87. // estimated distance between keyframes is too small
  88. double dist = (pos1.head<2>() - pos2.head<2>()).norm();
  89. if(dist > distance_thresh) {
  90. continue;
  91. }
  92. candidates.push_back(k);
  93. }
  94. return candidates;
  95. }
  96. /**
  97. * @brief To validate a loop candidate this function applies a scan matching between keyframes consisting the loop. If they are matched well, the loop is added to the pose graph
  98. * @param candidate_keyframes candidate keyframes of loop start
  99. * @param new_keyframe loop end keyframe
  100. * @param graph_slam graph slam
  101. */
  102. Loop::Ptr matching(const std::vector<KeyFrame::Ptr>& candidate_keyframes, const KeyFrame::Ptr& new_keyframe, hdl_graph_slam::GraphSLAM& graph_slam) {
  103. if(candidate_keyframes.empty()) {
  104. return nullptr;
  105. }
  106. registration->setInputTarget(new_keyframe->cloud);
  107. double best_score = std::numeric_limits<double>::max();
  108. KeyFrame::Ptr best_matched;
  109. Eigen::Matrix4f relative_pose;
  110. // std::cout << std::endl;
  111. // std::cout << "--- loop detection ---" << std::endl;
  112. // std::cout << "num_candidates: " << candidate_keyframes.size() << std::endl;
  113. // std::cout << "matching" << std::flush;
  114. auto t1 = ros::Time::now();
  115. pcl::PointCloud<PointT>::Ptr aligned(new pcl::PointCloud<PointT>());
  116. for(const auto& candidate : candidate_keyframes) {
  117. registration->setInputSource(candidate->cloud);
  118. Eigen::Isometry3d new_keyframe_estimate = new_keyframe->node->estimate();
  119. new_keyframe_estimate.linear() = Eigen::Quaterniond(new_keyframe_estimate.linear()).normalized().toRotationMatrix();
  120. Eigen::Isometry3d candidate_estimate = candidate->node->estimate();
  121. candidate_estimate.linear() = Eigen::Quaterniond(candidate_estimate.linear()).normalized().toRotationMatrix();
  122. Eigen::Matrix4f guess = (new_keyframe_estimate.inverse() * candidate_estimate).matrix().cast<float>();
  123. guess(2, 3) = 0.0;
  124. registration->align(*aligned, guess);
  125. // std::cout << "." << std::flush;
  126. double score = registration->getFitnessScore(fitness_score_max_range);
  127. if(!registration->hasConverged() || score > best_score) {
  128. continue;
  129. }
  130. best_score = score;
  131. best_matched = candidate;
  132. relative_pose = registration->getFinalTransformation();
  133. }
  134. auto t2 = ros::Time::now();
  135. // std::cout << " done" << std::endl;
  136. // std::cout << "best_score: " << boost::format("%.3f") % best_score << " time: " << boost::format("%.3f") % (t2 - t1).toSec() << "[sec]" << std::endl;
  137. if(best_score > fitness_score_thresh) {
  138. // std::cout << "loop not found..." << std::endl;
  139. return nullptr;
  140. }
  141. // std::cout << "loop found!!" << std::endl;
  142. // std::cout << "relpose: " << relative_pose.block<3, 1>(0, 3) << " - " << Eigen::Quaternionf(relative_pose.block<3, 3>(0, 0)).coeffs().transpose() << std::endl;
  143. last_edge_accum_distance = new_keyframe->accum_distance;
  144. return std::make_shared<Loop>(new_keyframe, best_matched, relative_pose);
  145. }
  146. private:
  147. double distance_thresh; // estimated distance between keyframes consisting a loop must be less than this distance
  148. double accum_distance_thresh; // traveled distance between ...
  149. double distance_from_last_edge_thresh; // a new loop edge must far from the last one at least this distance
  150. double fitness_score_max_range; // maximum allowable distance between corresponding points
  151. double fitness_score_thresh; // threshold for scan matching
  152. double last_edge_accum_distance;
  153. pcl::Registration<PointT, PointT>::Ptr registration;
  154. };
  155. } // namespace hdl_graph_slam
  156. #endif // LOOP_DETECTOR_HPP