草庐IT

ROS2+cartographer+激光雷达+IMU里程计数据融合(robot_locazation) 建图

暮尘依旧 2023-04-20 原文

目录

写在前面

之前写了一篇ROS2+cartorgrapher+激光雷达建图并保存,但是由于其只对激光雷达的数据进行订阅,这就导致了其建图在室内会有一个较好的效果(但是也会出现偏差),在室外完全无法使用。究其原因,是因为只用激光雷达且没有比较明显的建筑障碍物的话,cartographer很难计算出一个比较精准的位置和朝向。
因此,为了达到一个更好的建图效果,我们使用了robot_localization包,对IMU和里程计的数据进行融合,并将其发布,使得cartographer的建图效果更上一层楼。且基本上不会出现漂移等问题。

我们也尝试了直接将编码器的值输出为odomcartographer订阅来建图,但是那样的效果非常差劲,而融合了里程计和IMU数据之后,再建图的效果就非常好了

环境:

  • Ubuntu22.04
  • ROS2 humble
  • 激光雷达:镭神激光雷达M10P 网口版
  • cartographer robot_localization IMU 里程计

总体流程

在写具体的使用方法之前,先把我们整套系统的整体流程进行概括。

IMU数据
robot_localization
车轮编码器数据
转化为里程计数据
输出odom系融合数据
cartographer订阅并建图
激光雷达数据

整体概述而言,就是将两个传感器的数据利用robot_locazation包进行融合,让cartographer可以将其订阅,最终得到一个较好的建图效果。

因此,需要了解的是以下几点

  1. IMU数据的接收和发布
  2. 车轮编码器数据的接收并转换为里程计数据
  3. 数据融合——robot_localization
  4. cartographer订阅

分块解释

IMU数据接收和发布

IMU使用的是GY-95T,成本不到100元,三轴(yaw pitch roll)误差不大。底层数据接收驱动和在ROS2中的发布是自己手写的,具体可以参考之前写的这篇博客

ROS2手写接收IMU数据(Imu)代码并发布

车轮编码器数据接收和发布

我们的车是差速轮驱动+两全向轮,且重量较大。因此我们直接用差速轮的编码器值来建立里程计信息。车轮编码器底层的返回值这里不在赘述,这里主要写是如何从编码器的值转换成里程计信息并通过odom发布。

在网上直接找到了编码器转里程计发布的代码,链接如下

https://github.com/jfstepha/differential-drive/blob/master/scripts/diff_tf.py

我们直接将这个项目中的diff_ty.py文件拿出来稍微修改参数就可以使用,减少了工作量。我们可以设置其中的每米多少转,两轮宽距,发布话题名等参数。默认的发布话题名字为odom,但是由于我还需要将里程计的数据进行融合,因此,我将其发布的话题名字改为odom_diff

数据融合——robot_localization

概括

这个包是从ROS开发出来至今一直在广泛使用的包,效果也非常的不错。这里先把官方的wiki文档贴出:

https://docs.ros.org/en/melodic/api/robot_localization/html

既然是ROS2集成好的包,那自然就需要和nav2一样,先将其安装下来。

sudo apt install ros-humble-robot-localization

使用该包的方法也非常简单,只需要调用该包中的某些我们需要的节点即可。该算法其实能够融合非常多的传感器

官方给出该算法图片表明:其实该算法能融合IMU,相机,GPS,里程计和其他3D感知传感器得到的数据,由于我们成本有限,我们仅融合了IMU和里程计的数据。

使用

说完了概括,那么接下来就要来说明我用到的该包中的节点,robot_localization中有两个用于融合传感器数据的两种不同的算法节点ekf_localization_nodeukf_localization_node,它们的主要区别在于使用的滤波器算法不同。

  • ekf_localization_node使用的是扩展卡尔曼滤波器(EKF),它是一种适用于非线性系统的滤波器。当机器人或车辆的运动模型和传感器模型呈现非线性时,EKF可以通过对非线性函数进行线性化来逼近真实状态,并将观测数据和运动模型结合起来进行状态估计。但是,EKF也存在一些缺点,例如需要事先了解运动模型和传感器模型的具体形式,并且在非线性程度较高的情况下可能存在不稳定性。

  • ukf_localization_node使用的是无迹卡尔曼滤波器(UKF),它也是一种适用于非线性系统的滤波器,但是它不需要对非线性函数进行线性化。UKF通过使用一种称为无迹变换的方法,将概率分布从状态空间转换到高斯空间,从而避免了线性化过程中可能出现的不稳定性问题。

因此,ekf_localization_nodeukf_localization_node的主要区别在于它们使用的滤波器算法不同。对于非线性程度较低的情况,EKF通常可以提供较好的性能;对于非线性程度较高的情况,UKF可能更适合使用。我们使用的是扩展卡尔曼滤波器(UKF)。

因此,我们和使用其他包一样使用ros2 run package package_name即可,为了便于启动,我将其放入launch文件中

import launch_ros
import launch 

def generate_launch_description():
    pkg_share = launch_ros.substitutions.FindPackageShare(package='your_package_name').find('your_package_name')

	robot_localization_node = launch_ros.actions.Node(
	         package='robot_localization',
	         executable='ekf_node',
	         name='ekf_filter_node',
	         output='screen',
	         parameters=[os.path.join(pkg_share, 'config/ekf.yaml'), {'use_sim_time': False}]
	    )
	return launch.LaunchDescription([
	robot_localization_node,
	# ...
	# other node
	])

了解完了使用的是哪一个节点之后,我们就需要配置该节点的启动和其对应的参数文件。ROS2中的参数文件一般都是用yaml文件形式给出的。根据上方launch文件给出的形式,我们将扩展卡尔曼滤波节点的配置参数放置到src/your_package_name/config/ekf.yaml当中,配置文件的示例如下:

### ekf config file ###
ekf_filter_node:
    ros__parameters:
# The frequency, in Hz, at which the filter will output a position estimate. Note that the filter will not begin
# computation until it receives at least one message from one of the inputs. It will then run continuously at the
# frequency specified here, regardless of whether it receives more measurements. Defaults to 30 if unspecified.
        frequency: 30.0
        
# ekf_localization_node and ukf_localization_node both use a 3D omnidirectional motion model. If this parameter is
# set to true, no 3D information will be used in your state estimate. Use this if you are operating in a planar
# environment and want to ignore the effect of small variations in the ground plane that might otherwise be detected
# by, for example, an IMU. Defaults to false if unspecified.
        two_d_mode: false

# Whether to publish the acceleration state. Defaults to false if unspecified.
        publish_acceleration: true

# Whether to broadcast the transformation over the /tf topic. Defaults to true if unspecified.
        publish_tf: true
        
# 1. Set the map_frame, odom_frame, and base_link frames to the appropriate frame names for your system.
#     1a. If your system does not have a map_frame, just remove it, and make sure "world_frame" is set to the value of odom_frame.
# 2. If you are fusing continuous position data such as wheel encoder odometry, visual odometry, or IMU data, set "world_frame" 
#    to your odom_frame value. This is the default behavior for robot_localization's state estimation nodes.
# 3. If you are fusing global absolute position data that is subject to discrete jumps (e.g., GPS or position updates from landmark 
#    observations) then:
#     3a. Set your "world_frame" to your map_frame value
#     3b. MAKE SURE something else is generating the odom->base_link transform. Note that this can even be another state estimation node 
#         from robot_localization! However, that instance should *not* fuse the global data.
        map_frame: map              # Defaults to "map" if unspecified
        odom_frame: odom           # Defaults to "odom" if unspecified
        base_link_frame: base_link  # Defaults to "base_link" if unspecified
        world_frame: odom           # Defaults to the value of odom_frame if unspecified
        # odom0: /odom_diff
        # odom0_config: [true,  true,  true,
        #                false, false, false,
        #                false, false, true,
        #                false, false, false,
        #                false, false, false]
        odom0: /odom_diff
        odom0_config: [false,  false,  false,
                       false, false, false,
                       true, true, true,
                       false, false, false,
                       false, false, false]

        imu0: /imu_data
        imu0_config: [false, false, false,
                      true,  true,  true,
                      false, false, false,
                      false, false, false,
                      false, false, false]

后面的参数文件矩阵含义是我们可以在上面的话题中提供给robot_localization的数据,矩阵的各个配置值的含义为(X,Y,Z,roll,pitch,yaw,X˙,Y˙,Z˙,roll˙,pitch˙,yaw˙,X¨,Y¨,Z¨)。由于之前用到的diff_ty.py文件发布出来的数据是带有方向的XYZ速度值,因此需要将X˙,Y˙,Z˙这三个参数对应的矩阵设置为true,上面的话题odom0: /odom_diff是指robotlocalization需要订阅的话题名字,我们里程计数据是由diff_ty.py发布的odom_diff话题,因此在这里改为对应话题名字即可。
同理,对于IMU数据,我IMU驱动底层写的话题发布名字为imu_data,因此,我们也将其订阅的imu话题名字改为imu_data即可。而我们在这里只用到了IMU的roll pitch yaw三轴数据,因此是将第二排的三个参数改为true即可。

而上面的4个frame不需要进行修改,因为我们需要建立的就是map, odom, base_link三个系之间的关系。

其他注释可以跟着官方给出的注释并结合自己的实际情况来进行修改即可。

我们在进行完以上步骤之后,robot_localization就可以输出融合后的数据了,但是需要注意的一点是,robot_localization输出的话题叫做/odometry/filtered

cartographer订阅

我们已经可以输出融合后的数据了,现在就是修改cartographer的参数来让其订阅融合后的数据即可。我们需要修改的参数有

.lua配置参数文件中以下参数改为true

use_odometry = true

其他参数文件和我前面一篇博客的参数文件都一致

launch文件中,将remapping参数进行修改

    cartographer_node = Node(
        package = 'cartographer_ros',
        executable = 'cartographer_node',
        arguments = [
            '-configuration_directory', FindPackageShare('cartographer_ros').find('cartographer_ros') + '/configuration_files',
            '-configuration_basename', 'my_robot.lua'],
        remappings = [
            ('odom', '/odometry/filtered')],
        output = 'screen'
        )

其意思是remap标签将节点的输入话题/odom重映射到/odometry/filtered,以便Cartographer节点可以正确接收odom数据。

然后再重新启动即可

最终的rqt_tree如下图所示

效果

这是融合前后的里程计可视化的效果图,可以发现平滑了很多,且方向准确了很多。

这是融合了所有数据后的建图效果,把整个楼都建出来了,但是会有些角度上的漂移。即弯了。

但是相比于没有数据融合的建图效果,已经是好了太多了。

有关ROS2+cartographer+激光雷达+IMU里程计数据融合(robot_locazation) 建图的更多相关文章

  1. ruby - 融合表 : Why do I keep getting a "400 Bad Request" error when trying to update a table style via Ruby's RestClient gem - 2

    我正在尝试使用RubygemRestClient为我的一个FusionTables更新样式。这是我的代码:require'rest_client'tableId=''styleId=''key=''table_url="https://www.googleapis.com/fusiontables/v1/tables/#{tableId}/styles/#{styleId}?key=#{key}"update='{"polygonOptions":{"strokeColor":"#ffffff"}}'token='STRINGCONTAININGAUTHORIZATIONTOKEN'R

  2. 视频融合技术解决方案,三维全景拼接赋能平台 - 2

    近年来,随着信息化时代的到来,三维全景拼接以视频监控领域为代表的智能硬件公司迅速崛起,随后全国各地在视频监控领域进行了大量的建设。但随着摄像头数量的增加,视频监控画面离散、庞杂、关联性差等诸多问题日渐凸显。如何优化现有视频技术,助力管理者或使用者有效、直观、准确地掌控现场实时动态,成为我国信息化前行路上面临的新课题。视频融合技术平台解决方案北京智汇云舟科技有限公司成立于2012年,专注于创新性的“视频孪生(实时实景数字孪生)”技术研发与应用。公司依托自研三维地理信息引擎(3DGIS),融合建筑信息模型(BIM)、视频监控(Video)、人工智能(AI)及物联网(IOT)等多种技术,并在此基础上

  3. ruby-on-rails - rails 中子域的多个 robots.txt - 2

    我有一个包含多个子域的网站,我希望命名的子域robots.txt与www不同。我尝试使用.htaccess,但FastCGI没有查看它。所以,我试图设置路由,但似乎你不能直接重写,因为每条路由都需要一个Controller:map.connect'/robots.txt',:controller=>?,:path=>'/robots.www.txt',:conditions=>{:subdomain=>'www'}map.connect'/robots.txt',:controller=>?,:path=>'/robots.club.txt'解决这个问题的最佳方法是什么?(我正在为子域

  4. “数实融合 元力觉醒”,苏州市元宇宙生态大会圆满召开! - 2

     为贯彻落实《苏州市培育元宇宙产业创新发展指导意见》,抢抓数字经济发展新机遇,加速培育与元宇宙发展相关的技术底座,“数实融合元力觉醒——苏州市软件行业协会元宇宙专委会成立大会暨元宇宙生态大会”于4月14日成功举办。 苏州和数智能软件有限公司作为苏州市软件行业协会元宇宙专委会轮值理事长单位,参加了“元宇宙专委会揭牌与轮值理事长单位授牌仪式”。 大会上,数字主持人“丹丹”向社会发布了由苏州市软件行业协会、苏州市软件行业协会元宇宙专委会、西交利物浦大学、苏州科技大学、苏州和数智能软件有限公司等单位编写的《元宇宙行业分析报告2023》。该报告立足苏州、辐射长三角,系统梳理了元宇宙行业现状、元宇宙核心技

  5. 最全ROS 入门 - 2

    目录简介ROS诞生背景ROS的设计目标ROS与ROS2安装ROS1.配置ubuntu的软件和更新2.设置安装源3.设置key4.安装5.配置环境变量安装可能出现的问题安装构建依赖卸载ROS架构1.设计者2.维护者3.立足系统架构:ROS可以划分为三层ROS通信机制话题通信理论模型流程通信样例自定义消息的通信服务通信理论模型服务通信自定义srv参数服务器理论模型参数操作常用命令简介rosnoderostopicrosmsgrosservicerossrvrosparam常用API初始化话题与服务相关对象C++回旋函数C++对比时间1.时刻2.持续时间3.持续时间与时刻运算4.设置运行频率(非常常

  6. 基于深度学习的轴承寿命预测实践,开发CNN、融合LSTM/GRU/ATTENTION - 2

    关于轴承相关的项目之前做的大都是故障识别诊断类型的,少有涉及回归预测的,周末的时候宅家发现一个轴承寿命加速实验的数据集就想着拿来做一下寿命预测。首先看下数据集如下:直接百度即可搜到,这里就不再赘述了。Learning_set为训练集Test_set为测试集我这里为了简单处理直接使用Learning_set作为总数据集,随机划分指定比例作为测试集。当然了你也可以选择分别读取加载两部分的数据分别作为训练集和测试集都可以的。每个目录下都是一堆csv文件,样例如下:样例数据内容如下:9,11,19,1.1879e+05,0.059,-0.3729,11,19,1.1883e+05,0.603,-0.0

  7. ROS学习笔记(八):ROS2 - 2

    ROS学习笔记(八):ROS2ROS1存在的问题ROS2ROS2的设计目标ROS2的系统架构ROS2的关键中间件——DDSROS2的通信模型ROS1存在的问题ROS(一般ROS均指ROS1)经过多年的发展,已成为机器人领域的重要的工具与平台。但是ROS1主要存在以下问题:(1)ROS1无法适用于多机器人系统。(2)ROS1基于Linux系统,在Windows、macOS和RTOS上无法应用或功能有限。(3)ROS1缺少实时性方面的设计。(4)ROS1的分布式机制需要良好的网络环境才能保证数据的完整性,而且网络不具备数据加密、安全防护等功能。(5)ROS1的稳定性不好,ROSMaster和节点有

  8. javascript - 计算融合表中点之间的距离 - 2

    我的编程知识非常有限,而且我正在从事一个包含编程的大学项目。我想做的是一张map,显示您当前的位置和回收点的位置。我已经做了当前位置部分,我使用融合表在map上显示回收点。但我也想提供在您当前位置和回收点之间寻找最短路线的选项。所以它会做的是计算你当前位置和每个回收点之间的距离,并显示最短的一个。现在我正在尝试了解googlemapsapi教程,但我不知道这是否可行,使用融合表。所以我想知道是否有人知道如何执行此操作。非常感谢!Cliquenobotão"permitir"paradeixarobrowserencontrarasualocalizaçãofunctionsuccess

  9. javascript - 如何将元素翻译成里程表 - 2

    我有代码:01...CSS:.wrap2[data-num="0"]{transfom:translate(0,0);}.wrap2[data-num="1"]{transform:translate(0,-30px);}https://jsfiddle.net/9t4zsuov/2/但我想像一个里程表一样-数字只能滚动到顶部,而不是底部。任何想法,如何做到这一点? 最佳答案 正如@codyThompsonDev所说,翻转区域是实现这一点的最佳方式。不过,我认为他错过了一些事情,那就是当您从滚动号码变为非滚动号码时会发生什么。例如,

  10. javascript - 如何包含包含冷融合代码的外部 javascript 文件? - 2

    我有几个使用完全相同的javascript代码的coldfusion文件。我想将javascript分离到一个.js文件中,并将其包含在每个文件中,这样我就不必多次重复所有内容。因此,我将javascript代码分离到一个名为“myPage.js”的文件中,并在“myPage.cfm”中包含了一个脚本标记-问题是在使用注入(inject)值的javascript中散布了一些coldfusion代码。s之类的,并且不再被正确翻译,可能是因为它试图将其作为纯javascript来读取。有什么方法可以让我拥有一个外部js文件,但表明我希望它也使用coldfusion来解释它?我发现的一个解决

随机推荐