(02)Cartographer源码无死角解析-(50) 2D点云扫描匹配→相关性暴力匹配2:RealTimeCorrelativeScanMatcher2D

news2025/1/11 15:07:22

讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下:
(02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885
 
文末正下方中心提供了本人 联系方式, 点击本人照片即可显示 W X → 官方认证 {\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证} 文末正下方中心提供了本人联系方式,点击本人照片即可显示WX官方认证
 

一、前言

上一篇博客中对类 SearchParameters 进行了详细的介绍,同时对 src/cartographer/cartographer/mapping/internal/2d/scan_matching/real_time_correlative_scan_matcher_2d.cc 文件中的 RealTimeCorrelativeScanMatcher2D::Match() 进行了大致的讲解。下面对 RealTimeCorrelativeScanMatcher2D 的各个函数进行一个具体的分析。

其给构造函数十分简单,就不单独讲解,主要就是获取如下配置参数然后赋值给成员变量 options_:

  -- 是否使用 real_time_correlative_scan_matcher 为ceres提供先验信息
  -- 计算复杂度高 , 但是很鲁棒 , 在odom或者imu不准时依然能达到很好的效果
  use_online_correlative_scan_matching = false,
  real_time_correlative_scan_matcher = {
    linear_search_window = 0.1,             -- 线性搜索窗口的大小
    angular_search_window = math.rad(20.),  -- 角度搜索窗口的大小
    translation_delta_cost_weight = 1e-1,   -- 用于计算各部分score的权重
    rotation_delta_cost_weight = 1e-1,
  },

不过再分析 RealTimeCorrelativeScanMatcher2D 的各个函数之前,需要先把 correlative_scan_matcher_2d.cc 文件中的如下两个函数进行讲解:

// 生成按照不同角度旋转后的点云集合
std::vector<sensor::PointCloud> GenerateRotatedScans(const sensor::PointCloud& point_cloud,const SearchParameters& search_parameters) {......}

// 将旋转后的点云集合按照预测出的平移量进行平移, 获取平移后的点在地图中的索引
std::vector<DiscreteScan2D> DiscretizeScans(const MapLimits& map_limits, const std::vector<sensor::PointCloud>& scans,const Eigen::Translation2f& initial_translation) {......}

为了方便后续的讲解,这里把上一篇博客的图示粘贴一下:
在这里插入图片描述

图1

 

二、GenerateRotatedScans()

该函数需要传递两个参数:①point_cloud→点云数据;②search_parameters→已经计算好的搜索配置参数。该函数的逻辑比较简单,就是对点云数据做多次旋转,每次旋转度数都是在上一次旋转的基础上再增加角分辨率度数。总的旋转次数为 search_parameters.num_scans,该参数在上一篇博客中提到过,如下:

  // 范围除以分辨率得到个数
  num_angular_perturbations =std::ceil(angular_search_window / angular_perturbation_step_size);
  // num_scans是要生成旋转点云的个数, 将 num_angular_perturbations 扩大了2倍
  num_scans = 2 * num_angular_perturbations + 1;

需要注意的是,这里的 point_cloud 点云数据是相对于机器人的,且已经进行过重力矫正,所以对该点云的旋转只需要绕z轴即可,记 scan_index=i 次旋转之后的点云为 p o i n t s s c a n _ i t r a c k i n g points^{tracking}_{scan\_i} pointsscan_itracking,初始点云 p o i n t _ c l o u d = p o i n t s i n i t t r a c k i n g point\_ cloud=points^{tracking}_{init} point_cloud=pointsinittracking,那么使用数学公式表示如下:
p o i n t s s c a n _ i t r a c k i n g = R i n i t s c a n _ i ∗ p o i n t s i n i t t r a c k i n g (01) \color{Green} \tag{01} points^{tracking}_{scan\_i}=\mathbf R^{scan\_i}_{init}* points^{tracking}_{init} pointsscan_itracking=Rinitscan_ipointsinittracking(01)
其上的 R i n i t s c a n _ i \mathbf R^{scan\_i}_{init} Rinitscan_i 等价于源码中的 transform::Rigid3f::Rotation(Eigen::AngleAxisf(delta_theta, Eigen::Vector3f::UnitZ()))。每次变换之后的结果都存储于 rotated_scans 中,遍历完成之后返回该变量,源码注释如下:

// 生成按照不同角度旋转后的点云集合
std::vector<sensor::PointCloud> GenerateRotatedScans(
    const sensor::PointCloud& point_cloud,
    const SearchParameters& search_parameters) {
  std::vector<sensor::PointCloud> rotated_scans;
  // 生成 num_scans 个旋转后的点云
  rotated_scans.reserve(search_parameters.num_scans);
  // 起始角度
  double delta_theta = -search_parameters.num_angular_perturbations *
                       search_parameters.angular_perturbation_step_size;
  // 进行遍历,生成旋转不同角度后的点云集合
  for (int scan_index = 0; scan_index < search_parameters.num_scans;
       ++scan_index,
           delta_theta += search_parameters.angular_perturbation_step_size) {
    // 将 point_cloud 绕Z轴旋转了delta_theta
    rotated_scans.push_back(sensor::TransformPointCloud(
        point_cloud, transform::Rigid3f::Rotation(Eigen::AngleAxisf(
                         delta_theta, Eigen::Vector3f::UnitZ()))));
  }
  return rotated_scans;
}

 

三、DiscretizeScans()

该函数主要的功能是对传入的点云数据做一个平移,其需要传递三个函数:
①map_limits→用于获取点云数据
②scans→通常情况下就是上一函数 GenerateRotatedScans() 的返回结果,存储 num_scans 帧不同角度的点云数据;
③initial_translation→所有点云数据需要平移的数量参数

源码中会进行两层遍历,第一层遍历for循环,其会获得每个角度的所有点云数据 scan;第二层遍历for循环,对单个角度下的每个点云数据进行处理,其处理角较为简单,就是进行简单的平移,并且计算出平移之后点云数据在栅格地图中的二维索引(坐标),存储于变量 discrete_scans 中返回。源码注释如下:

// 将旋转后的点云集合按照预测出的平移量进行平移, 获取平移后的点在地图中的索引
std::vector<DiscreteScan2D> DiscretizeScans(
    const MapLimits& map_limits, const std::vector<sensor::PointCloud>& scans,
    const Eigen::Translation2f& initial_translation) {
  // discrete_scans的size 为 旋转的点云的个数
  std::vector<DiscreteScan2D> discrete_scans;
  discrete_scans.reserve(scans.size());

  for (const sensor::PointCloud& scan : scans) {
    // discrete_scans中的每一个 DiscreteScan2D 的size设置为这一帧点云中所有点的个数
    discrete_scans.emplace_back();
    discrete_scans.back().reserve(scan.size());

    // 点云中的每一个点进行平移, 获取平移后的栅格索引
    for (const sensor::RangefinderPoint& point : scan) {
      // 对scan中的每个点进行平移
      const Eigen::Vector2f translated_point =
          Eigen::Affine2f(initial_translation) * point.position.head<2>();

      // 将旋转后的点 对应的栅格的索引放入discrete_scans
      discrete_scans.back().push_back(
          map_limits.GetCellIndex(translated_point));
    }
  }
  return discrete_scans;
}

该函数的价值,其主要用于加权平均,在后续会体现出来,到时进行详细分析。
 

四、GenerateExhaustiveSearchCandidates()

对 correlative_scan_matcher_2d.cc 文件中的两个函数分析完之后,来看看类 RealTimeCorrelativeScanMatcher2D 中的函数,首先要讲解的就是 GenerateExhaustiveSearchCandidates()。从函数命名来看,表示使用穷举的方式生成候选者。那么,下面就来看看其具体是如何实现的。

( 1 ) \color{blue}(1) (1) 这里把角度遍历与范围遍历组合起来的结果,称为候选解,最终的目的就是从这些候选解中找到最优者。在这之前,该函数首先计算出候选解的个数,上一篇博客中,提到了 SearchParameters::linear_bounds 成员变量,该变量描述的是需要遍历的区域,也就是 图1 中的蓝色正方形区域,其以像素(栅格)为单位。遍历区域的栅格数目 为 num_linear_x_candidates*num_linear_y_candidates,其本质就是以像素(栅格)为单位,求面积。总的候选解还要乘以 search_parameters.num_scans,源码中使用加法的方式,应该也是一样的效果。

( 2 ) \color{blue}(2) (2) 创建一个保存候选解的变量 std::vector<Candidate2D> candidates,每个候选解都是 Candidate2D 类型,创建其实例对象需要参数: ①scan_index→角度遍历的索引;②x_index_offset, y_index_offset→偏移值,这里可以理解为确定搜索区域的原点之后,先对于该原点的偏移值,注意,其以像素(栅格)为单位。

( 3 ) \color{blue}(3) (3) 使用三个for循环进行遍历,外面的两个循环用于控制搜索(遍历)区域,最里面的循环用于控制角度搜索。这里就完成了 图1 中蓝色正方形区域每个位置及其角度的搜寻,角度的范围由配置文件中的 angular_search_window 参数进行控制。

该函数所有候选解都存储于 candidates 变量中,让后返回,源码如下:

// 生成所有的候选解
std::vector<Candidate2D>
RealTimeCorrelativeScanMatcher2D::GenerateExhaustiveSearchCandidates(
    const SearchParameters& search_parameters) const {
  int num_candidates = 0;
  // 计算候选解的个数
  for (int scan_index = 0; scan_index != search_parameters.num_scans;
       ++scan_index) {
    const int num_linear_x_candidates =
        (search_parameters.linear_bounds[scan_index].max_x -
         search_parameters.linear_bounds[scan_index].min_x + 1);
    const int num_linear_y_candidates =
        (search_parameters.linear_bounds[scan_index].max_y -
         search_parameters.linear_bounds[scan_index].min_y + 1);
    num_candidates += num_linear_x_candidates * num_linear_y_candidates;
  }

  std::vector<Candidate2D> candidates;
  candidates.reserve(num_candidates);

  // 生成候选解, 候选解是由像素坐标的偏差组成的
  for (int scan_index = 0; scan_index != search_parameters.num_scans;
       ++scan_index) {
    for (int x_index_offset = search_parameters.linear_bounds[scan_index].min_x;
         x_index_offset <= search_parameters.linear_bounds[scan_index].max_x;
         ++x_index_offset) {
      for (int y_index_offset =
               search_parameters.linear_bounds[scan_index].min_y;
           y_index_offset <= search_parameters.linear_bounds[scan_index].max_y;
           ++y_index_offset) {
        candidates.emplace_back(scan_index, x_index_offset, y_index_offset,
                                search_parameters);
      }
    }
  }
  CHECK_EQ(candidates.size(), num_candidates);
  return candidates;
}

 

五、ScoreCandidates()

 
 
 

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

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

相关文章

【论文速递】TPAMI2022 - 小样本分割的整体原型激活

【论文速递】TPAMI2022 - 小样本分割的整体原型激活 【论文原文】&#xff1a;Holistic Prototype Activation for Few-Shot Segmentation 获取地址&#xff1a;https://ieeexplore.ieee.org/document/9839487 CSDN下载&#xff1a;https://download.csdn.net/download/qq_36…

三十、RabbitMQ(1)

&#x1f33b;&#x1f33b; 目录一、 关于中间件的概述二、基于消息中间件的分布式系统的架构2.1 消息中间件应用的场景2.2 常见的消息中间件2.3 消息中间件的本质及设计2.4 消息中间件的核心组成部分2.5 小总结三、消息队列协议3.1 什么是协议3.2 网络协议的三要素3.3 AMQP 协…

JAVA 23种设计模式示例

目录 一.单例模式 二.工厂方法模式 三.抽象工厂模式 四.建造者模式 五.原型模式 六.享元模式 七.门面模式 八.适配器模式 九.装饰者模式 十.策略模式 十一.模板方法模式 十二.观察者模式 十三.责任链模式 十四.代理模式 十五.桥接模式 十六.组合模式 十七.命令…

openGauss数据库PostGIS 安装与使用

目录 概述 1.PostGIS 安装 1.1 GCC-7.3编译器安装 1.2PostGIS依赖库安装 1.3.安装Postgis 2.使用Extension 2.1创建PostGIS Extension 2.2使用Extension 2.3删除Extension 概述 PostGIS Extension是PostgreSQL的空间数据库扩展&#xff0c;提供如下空间信息服务功能&…

SpringBoot+VUE前后端分离项目学习笔记 - 【21 权限菜单 中】

1 新建了sys_dict表以及相应Dict类保存菜单menu的icon数据 2 新建了sys_role_menu表以及相应RoleMenu类保存前端Role页面传来的角色菜单ID的绑定关系 3 在MenuController里增加获取Dict里icon的方法 提供前端菜单页面显示 4 在RoleController增加Post接口&#xff0c;获取前台传…

66页3万字医疗行业大数据治理解决方案

【版权声明】本资料来源网络&#xff0c;知识分享&#xff0c;仅供个人学习&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 目 录 1. 1、医疗行业大数据管…

分享116个PHP源码,总有一款适合您

PHP源码 分享116个PHP源码&#xff0c;总有一款适合您 116个PHP源码链接&#xff1a;https://pan.baidu.com/s/1dsupZiZbKqvHPmlpIAgWqA?pwdg52q 提取码&#xff1a;g52q import os import shutil import time from time import sleepimport requests from bs4 import Bea…

C++11静态断言static_assert

C11静态断言static_assert一、运行时断言二、静态断言的需求三、静态断言四、单参数版本的静态断言一、运行时断言 断言&#xff08;assertion&#xff09;是一种编程中常用的手段。在通常情况下&#xff0c;断言就是将一个返回值总是需要为真的判别式放在语句中&#xff0c;用…

Oracle No-Fee Terms and Conditions (NFTC)到底有啥条款?

1995年Sun微系统公司推出Java至今已有28年的历史&#xff0c;由于厂商持续升级优化&#xff0c;使用场景广阔&#xff0c;生态完善&#xff0c;Java目前仍然保持着非常旺盛的生命力。 付费许可 2019年java更新了许可政策 https://www.oracle.com/java/technologies/javase/ja…

【一文速通】机器学习样本不均衡/数据分布不同怎么办?

样本不均衡是什么意思样本&#xff08;类别&#xff09;样本不平衡&#xff08;class-imbalance&#xff09;指的是分类任务中不同类别的训练样例数目差别很大的情况&#xff0c;一般地&#xff0c;样本类别比例&#xff08;Imbalance Ratio&#xff09;&#xff08;多数类vs少…

antd中Tree组件使用方法个人笔记

一、前言 最近在自己自学前端&#xff0c;不清楚学习路线&#xff0c;只能盯着公司的前端项目硬看。 公司的前端项目是react框架&#xff0c;Ant Design Pro。 之前刚把router.config.js的逻辑理顺&#xff0c;目前准备开发个简单的前端页面。 在此总结下antd中<Tree>…

【算法刷题 DAY04】剑指offer树3和队列与栈总结

JZ36 二叉搜索树与双向链表 描述 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的双向链表。如下图所示 注意: 1.要求不能创建任何新的结点&#xff0c;只能调整树中结点指针的指向。当转化完成以后&#xff0c;树中节点的左指针需要指向前驱&#xff0c;树中…

虹科新闻 | 虹科与weeve正式建立合作伙伴关系

近日&#xff0c;虹科与weeve正式建立合作伙伴关系&#xff0c;双方就工业应用自动化领域进行深入的交流与合作&#xff0c;未来将共同致力于为中国市场提供完整的物联网边缘服务解决方案&#xff0c;解决中国客户的物联网挑战。 虹科与weeve都表示十分期待这次的合作。“虹科…

day36【代码随想录】贪心算法之根据身高重建队列、用最少数量的箭引爆气球、无重叠区间

文章目录前言一、根据身高重建队列&#xff08;力扣406&#xff09;二、用最少数量的箭引爆气球&#xff08;力扣452&#xff09;三、无重叠区间&#xff08;力扣435&#xff09;前言 1、根据身高重建队列 2、用最少数量的箭引爆气球 3、无重叠区间 一、根据身高重建队列&…

魔改插线板,让电视控制周边设备开关机

一.我的需求 本人是一个极简主义风格的人&#xff0c;自从用了N1盒子刷了coreELEC 系统后&#xff0c;就不断的进行折腾&#xff0c;跟大家说下我的心路历程。 1.我家很少看电视&#xff0c;不想因为偶尔开一次电视就每个月交24块钱&#xff0c;所以把广电的机顶盒停掉了。 2.电…

TextView

1.简介 向用户显示文本的用户界面元素。 2.常见使用 2.1 设置文本内容 //xml 硬编码 <TextView android:text"文本"/> //xml 推荐放在string.xml,为了国际化考虑 <TextView android:text"string/app_name"/> //kotlin tv.text getStr…

零基础学员的shell脚本的写作思路详解

前言 这两天一直再批改学员的脚本作业&#xff0c;大多数学员写的很好&#xff0c;有的学员写的不太好。 还有一些还没有入门到学员不知道脚本该咋写。 不知道脚本怎么写的学员&#xff0c;绝大多数犯了一个错误&#xff1a;一上来就把脚本想的太复杂了。 我们今天单独聊聊这…

以研究用途搭建OpenStreetMap Virtualbox服务器

又到了新年伊始&#xff0c;下载OpenStreetMap全球数据的时候了。结果惊奇的发现&#xff0c;主站已经无法打开。仔细了解了原委&#xff0c;表示理解。好在PBF数据依旧可以获取&#xff0c;只是瓦片服务已经关停。 1.OpenStreetMap的主要问题 OpenStreetMap之所以被Blocked&…

力扣刷题记录——459.重复的字符串、461. 汉明距离、476. 数字的补数

本专栏主要记录力扣的刷题记录&#xff0c;备战蓝桥杯&#xff0c;供复盘和优化算法使用&#xff0c;也希望给大家带来帮助&#xff0c;博主是算法小白&#xff0c;希望各位大佬不要见笑&#xff0c;今天要分享的是——《459.重复的字符串、461. 汉明距离、476. 数字的补数》。…

Mysql数据库中的表

创建表 和之前的创建库差不多&#xff0c;需要多指定一个engine&#xff08;不写默认INNODB&#xff09;这个后面说 字符集还有校对规则不指定的话和库的保持一致 案例 图形化创建就不说了&#xff0c;主要就指令 # 演示关于创建表的操作 # 在db02创建表USER包含id-整形 name…