【ROS+amcl+Movebase】多机器人导航

学过单机器人在已知地图中的导航后,想到如果有多个机器人在同一地图如何导航,于是在网上学习了下,主流方案即在单机器人导航的基础上引入命名空间。
参考文章1
参考文章2
参考文章3

一、实验环境

Ubuntu1804(虚拟机)
ROS(melodic)
Navigation(move_base+amcl)
gazebo(9.0.0)
vscode

二、目录结构

在这里插入图片描述
可以看出,基本和之前单机器人导航没什么区别,改动主要集中在launch目录下,gazebo差速器仿真部分也需要修改,rviz下有少量插件配置变动不是学习重点。

三、主题和TF(关键)

除了之前学过的单机器人导航的关键点,这里最重要的就是命名空间了。
我们之前只有一个机器人,所有的主题、节点、TF变换都是在全局空间下,都是默认的,不需要分辨谁从哪来到哪去给谁用,一旦加入了第二个同样的机器人,它的主题、TF等跟第一个机器人完全相同,这样就会乱套,为了将它们以及它们的种种属性区分开来,引入了命名空间。
从launch文件讲起:

<launch>
    <arg name="verbose" default="false"/>
    <arg name="robot_namespace" default=""/>

    <include file="$(find gazebo_ros)/launch/empty_world.launch">
        <arg name="world_name" value="$(find carbot_multi_navi)/world/space.world"/>
        <arg name="paused" value="false"/>
        <arg name="use_sim_time" value="true"/>
        <arg name="gui" value="true"/>
        <arg name="headless" value="false"/>
        <arg name="verbose" value="$(arg verbose)"/>
        <arg name="debug" value="false"/>
    </include>

    <!-- 运行地图服务器,并且加载设置的地图 -->
    <arg name="map" default="space.yaml" />
    <node name="map_server" pkg="map_server" type="map_server" args="$(find carbot_multi_navi)/maps/$(arg map)"/>

    <!-- 配置不同命名空间下的机器人 -->
    <group ns = "car1">
        <param name="robot_description" command="$(find xacro)/xacro '$(find carbot_multi_navi)/urdf/carbot_gazebo.urdf.xacro'"/>
        <node name="urdf_spawner" pkg="gazebo_ros" type="spawn_model" respawn="false" output="screen"
            args="-urdf -model car1 -param robot_description -y 0.5"/> 

        <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher">
            <param name="publish_frequency" type="double" value="50.0" />
            <param name="tf_prefix" value="car1" />
        </node>
        <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher"/>
    </group>

    <group ns = "car2">
        <param name="robot_description" command="$(find xacro)/xacro '$(find carbot_multi_navi)/urdf/carbot_gazebo.urdf.xacro'"/>

        <node name="urdf_spawner" pkg="gazebo_ros" type="spawn_model" respawn="false" output="screen"
            args="-urdf -model car2 -param robot_description -y 0.0"/> 

        <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher">
            <param name="publish_frequency" type="double" value="50.0" />
            <param name="tf_prefix" value="car2" />
        </node>
        <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher"/>
    </group>

    <group ns = "car3">
        <param name="robot_description" command="$(find xacro)/xacro '$(find carbot_multi_navi)/urdf/carbot_gazebo.urdf.xacro'"/>
        <node name="urdf_spawner" pkg="gazebo_ros" type="spawn_model" respawn="false" output="screen"
            args="-urdf -model car3 -param robot_description -y -0.5"/> 

        <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher">
            <param name="publish_frequency" type="double" value="50.0" />
            <param name="tf_prefix" value="car3" />
        </node>      
        <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher"/>
        <!-- <node pkg="tf" type="static_transform_publisher" name="map_odom_broadcaster" args="0 0 0 0 0 0 /map car3/odom 100" /> -->
    </group>

    <!-- move_base配置 -->
    <include file="$(find carbot_multi_navi)/launch/move_base.launch" >
        <arg name="robot_namespace" value="car1" />
    </include>
    <include file="$(find carbot_multi_navi)/launch/move_base.launch">
        <arg name="robot_namespace" value="car2" />
    </include>
    <include file="$(find carbot_multi_navi)/launch/move_base.launch" >
        <arg name="robot_namespace" value="car3" />
    </include>


    <!-- amcl配置 -->
    <include file="$(find carbot_multi_navi)/launch/amcl.launch">
        <arg name="robot_namespace" value="car1" />
        <arg name="initial_pose_x" value="0.0"/>
        <arg name="initial_pose_y" value="0.5"/>
        <arg name="initial_pose_a" value="0.0"/>
    </include>
    <include file="$(find carbot_multi_navi)/launch/amcl.launch">
        <arg name="robot_namespace" value="car2" />
        <arg name="initial_pose_x" value="0.0"/>
        <arg name="initial_pose_y" value="0.0"/>
        <arg name="initial_pose_a" value="0.0"/>
    </include>
    <include file="$(find carbot_multi_navi)/launch/amcl.launch">
        <arg name="robot_namespace" value="car3" />
        <arg name="initial_pose_x" value="0.0"/>
        <arg name="initial_pose_y" value="-0.5"/>
        <arg name="initial_pose_a" value="0.0"/>
    </include>
    
    <!-- 启动rviz -->
    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find carbot_multi_navi)/rviz/nav_multi.rviz"/>

</launch>

这是总的启动文件,启用了gazebo,map-server,amcl,move_base,rviz。
其中gazebo又分加载世界和加载机器人,世界(即场景模型)只需要加载一个(因此不需要放在命名空间下),机器人在不同命名空间下分别加载一个,加了命名空间的机器人,其内部所订阅、发布的主题都会带命名空间前缀,比如在我们加载的第一个机器人,其命名空间为car1,那么它默认的运动控制主题/cmd_vel就会变成/car1/cmd_vel,它自身带的雷达发布的默认主题/scan也会相应的加上前缀变为/car1/scan
这里举例的机器人其实代表的是节点,在<group ns="xxx">标签内的所有节点都是这样,像joint_state_publisher节点发布的主题就变为/car1/joint_states,搞清楚了这个原理,就会省下很多麻烦。比如三个机器人对应的amcl定位节点的主题该如何指定呢?答案是,将amcl节点放在标签下,然后其主题就不需要再额外指定了,之前单机器人如何,现在就如何。同理,move_base节点的主题也是如此。

搞完上面的这些,兴奋地尝试了下,结果并不如愿。你发现一直在报警告,大概的意思是找不到某base_footprint到map的转换关系,找不到xxx到xxx的转换关系,打开rqt_tf_tree一看,TF树并没有连在一起。有的frame都给加了命名空间前缀(如/car1/base_link),而有些却没有加前缀(如odom),明明所有需要加命名空间的节点都老老实实加了命名空间,为什么有些frame还是不能自动加前缀转换坐标呢。(给我自己问懵了,词穷。。。难以用文字语言解答)
这里直接针对问题给出方案,没有正确转换的坐标frame,我们要进行干预了,手动指定哪些要转换,如何转换。参考之前单机器人导航,我们就到amcl.launch中找到转换关系,发现了global_frame_id,odom_frame_id,base_frame_id这三个坐标系,首先map只有一个,所以global_frame_id是不需要加前缀的,而其余两个每个机器人都是不同的,也就是加上对应命名空间的前缀,

<arg name="odom_frame_id"   value="$(arg robot_namespace)/odom"/>
<arg name="base_frame_id"   value="$(arg robot_namespace)/base_footprint"/>

同样的,在move_base.launch中也如此指定,将所有的frame_id加上对应的命名空间前缀(注意改的是value)。
最后得到完整的launch文件:
amcl.launch:

<launch>
    <arg name="use_map_topic" default="false"/>
    <arg name="robot_namespace" default=""/>
    <arg name="global_frame_id" default="map"/>
    <arg name="scan_topic" value="scan"/>
    <arg name="odom_frame_id"   value="$(arg robot_namespace)/odom"/>
    <arg name="base_frame_id"   value="$(arg robot_namespace)/base_footprint"/>

    <arg name="initial_pose_x" default="0"/>
    <arg name="initial_pose_y" default="0"/>
    <arg name="initial_pose_a" default="0"/>
    <group ns="$(arg robot_namespace)">

      <node pkg="amcl" type="amcl" name="amcl">
          <param name="use_map_topic" value="$(arg use_map_topic)"/>
          <!-- Publish scans from best pose at a max of 10 Hz -->
          <param name="odom_model_type" value="diff"/>
          <param name="odom_alpha5" value="0.05"/>
          <param name="gui_publish_rate" value="10.0"/>
          <param name="laser_max_beams" value="180"/>
          <param name="laser_max_range" value="6.0"/>
          <param name="min_particles" value="300"/>
          <param name="max_particles" value="1000"/>
          <param name="kld_err" value="0.05"/>
          <param name="kld_z" value="0.99"/>
          <param name="odom_alpha1" value="0.05"/>
          <param name="odom_alpha2" value="0.05"/>
          <!-- translation std dev, m -->
          <param name="odom_alpha3" value="0.05"/>
          <param name="odom_alpha4" value="0.05"/>
          <param name="laser_z_hit" value="0.5"/>
          <param name="laser_z_short" value="0.05"/>
          <param name="laser_z_max" value="0.05"/>
          <param name="laser_z_rand" value="0.5"/>
          <param name="laser_sigma_hit" value="0.2"/>
          <param name="laser_lambda_short" value="0.1"/>
          <param name="laser_model_type" value="likelihood_field"/>
          <!-- <param name="laser_model_type" value="beam"/> -->
          <param name="laser_likelihood_max_dist" value="2.0"/>
          <param name="update_min_d" value="0.25"/>
          <param name="update_min_a" value="0.2"/>
          <param name="resample_interval" value="1"/>
          <!-- Increase tolerance because the computer can get quite busy -->
          <param name="transform_tolerance" value="0.5"/>
          <param name="recovery_alpha_slow" value="0.0"/>
          <param name="recovery_alpha_fast" value="0.0"/>

          <param name="global_frame_id"  value="$(arg global_frame_id)"/>
          <param name="odom_frame_id"  value="$(arg odom_frame_id)"/>
          <param name="base_frame_id"  value="$(arg base_frame_id)"/>

          <param name="initial_pose_x" value="$(arg initial_pose_x)"/>
          <param name="initial_pose_y" value="$(arg initial_pose_y)"/>
          <param name="initial_pose_a" value="$(arg initial_pose_a)"/>
          
          <remap from="scan" to="$(arg scan_topic)"/>
          <remap from="static_map" to="/static_map"/>
          <remap from="map" to="/map" />
      </node>  
    </group>
</launch>

move_base.launch:

<launch>
    <arg name="robot_namespace" default=""/>

    <arg name="laser_topic" default="scan" />
    <arg name="odom_topic" default="odom" />
    <arg name="cmd_topic" default="cmd_vel" />
    <arg name="global_frame_id" default="map"/>
    <arg name="odom_frame_id"   value="$(arg robot_namespace)/odom"/>
    <arg name="base_frame_id"   value="$(arg robot_namespace)/base_footprint"/>


    <group ns="$(arg robot_namespace)">
        <node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen">
            <!-- Default configs -->
            <rosparam file="$(find carbot_multi_navi)/config/costmap_common_params.yaml" command="load" ns="global_costmap" />
            <rosparam file="$(find carbot_multi_navi)/config/costmap_common_params.yaml" command="load" ns="local_costmap" />
            <rosparam file="$(find carbot_multi_navi)/config/local_costmap_params.yaml" command="load" />
            <rosparam file="$(find carbot_multi_navi)/config/global_costmap_params.yaml" command="load" />
            <rosparam file="$(find carbot_multi_navi)/config/base_local_planner_params.yaml" command="load" />
    
            <!-- Set tf_prefix for frames explicity, overwriting defaults -->
            <!-- 注意下面sensor_frame的id改为自己机器人实际的传感器id,这里我的是雷达,在rplidar.xacro中为laser_link -->
            <param name="global_costmap/scan/sensor_frame" value="$(arg robot_namespace)/laser_link"/>
            <param name="global_costmap/obstacle_layer/scan/sensor_frame" value="$(arg robot_namespace)/laser_link"/>
            <param name="global_costmap/global_frame" value="$(arg global_frame_id)"/>
            <param name="global_costmap/robot_base_frame" value="$(arg base_frame_id)"/>
            <param name="global_costmap/obstacle_layer/scan/topic" value="$(arg laser_topic)"/>
        
            <param name="local_costmap/scan/sensor_frame" value="$(arg robot_namespace)/laser_link"/>
            <param name="local_costmap/obstacle_layer/scan/sensor_frame" value="$(arg robot_namespace)/laser_link"/>
            <param name="local_costmap/global_frame" value="$(arg odom_frame_id)"/>
            <param name="local_costmap/robot_base_frame" value="$(arg base_frame_id)"/>
            <param name="local_costmap/obstacle_layer/scan/topic" value="$(arg laser_topic)"/>
        
            <!-- 主题重映射 -->
            <remap from="cmd_vel" to="$(arg cmd_topic)"/>
            <remap from="odom" to="$(arg odom_topic)"/>
            <remap from="scan" to="$(arg laser_topic)"/>
            <remap from="map" to="/map"/>
        </node>
      </group>
</launch>

这样应该就没什么大问题了,有问题的继续往下看,也可以评论区留言交流。

四、趟坑指南

1.命名空间值如何传递

<param name="robot_description" command="$(find xacro)/xacro '$(find carbot_multi_navi)/urdf/carbot_gazebo.urdf.xacro' ns:=xxx "/>
最初是不了解命名空间下的节点主题的规则,以为要手动添加前缀,于是想法设法想把ns值传递进urdf.xacro中,就是上面这条语句中的ns:=xxx,最终没能成功,也希望正在尝试的朋友不要这样做了,这在节点启动语句如rosrun node_xx ns:=xxx时确实可以传ns值进节点,但进urdf.xacro我是没成功过。
坑内时间:4小时左右。

2.local_costmap/global_frame

这是move_base.launch中的某个参数param,刚开始因为到处搜教程cv过快,将其设置为 <param name="local_costmap/global_frame" value="$(arg global_frame_id)"/>,也就是map坐标(正确应是odom),到后面TF树死活连不起来,也是运气好吧,最终发现这个地方不对劲,哪里不对劲呢,你看这个参数就明白了<param name="global_costmap/global_frame" value="$(arg global_frame_id)"/>
坑内时间:贯穿始终,一天半。

3.机器人初始位置

在一个机器人导航的时候,我们可以地图选点初始化机器人,但在三个机器人一起导航的情况下,如果还是手动选点的话雷达扫描与实际地图误差会比较大,所以建议还是在amcl.launch中直接指定初始位置(注意第三个参数末尾是a),即

<param name="initial_pose_x" value="$(arg initial_pose_x)"/>
<param name="initial_pose_y" value="$(arg initial_pose_y)"/>
<param name="initial_pose_a" value="$(arg initial_pose_a)"/>

经过再次实验,机器人运动后哪怕初始位置不准,随着它的运动,会逐渐矫正雷达扫描,直到契合实际地图,但与其让它自己矫正为什么不直接给它一个初始定位呢。

4.gazebo仿真差速器

差速器的功能很重要,但它做的事有点过多了,不需要的我们最好关闭。
在这里插入图片描述
这里圈出了四个标签。其中rootnamespace一定要注释掉;publishWheelTF一定要设为false;publishTf不能关闭,必须设置为1或true。否则可能导致以下问题
publishWheelJointState我们有专门的joint_state_publisher节点,所以不需要这里再次发布。

5.rviz插件配置

rviz的配置文件是从别人代码里抄来的,没有注意,在点击2D Nav Goal地图选点后发布的主题带着别人原来预设的命名空间,跟自己的命名空间不同,也就无法正常导航,想在rviz中自己添加却发现找不到地方,最终在rviz配置文件中修改主题(搜索rviz/SetGoal)
在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VSCode+python单步调试库代码

VSCodepython单步调试库代码 随着VSCode版本迭代更新&#xff0c;在最新的1.87.x中&#xff0c;使用Python Debugger扩展进行调试时&#xff0c;扩展的justMyCode默认属性为true&#xff0c;不会进入库中的代码。这对debug而言不太方便&#xff0c;因此需要手动设置一下&#…

在vite(vue)项目中使用mockjs

在vite&#xff08;vue&#xff09;项目中使用mockjs 在开发环境使用 1、首先创建vite项目 yarn create vite选择vue&#xff0c;选择默认的js版本 2、进入项目文件夹中执行yarn安装依赖 yarn add axios mockjs vite-plugin-mock3、安装axios、mockjs及插件 yarn add axio…

【方法封装】时间格式化输出,获取请求设备和IP

目录 时间类 1.1 获取当前时间&#xff0c;以特定格式化形式输出 1.2 自定义时间&#xff0c;以特定格式化输出 1.3 获取当前时间&#xff0c;自定义格式化 1.4 自定义时间&#xff0c;自定义格式化 设备类 根据请求头信息&#xff0c;获取用户发起请求的设备 请求IP类 …

vue3中的文字滚动播报

vue3中的文字滚动播报 之前UI框架一直使用的elementPlus&#xff0c;有个需求&#xff0c;需要在页面上写个滚动播放新闻的功能&#xff0c;发现UI框架居然没有这个组件。花了一下午&#xff0c;在ChatGPT的帮助下&#xff0c;总算写成功了&#xff0c;先看最终展示效果 web页…

Python打印输出Linux中最常用的linux命令之示例

一、Linux中的~/.bash_history文件说明&#xff1a; 该文件保存了linux系统中运行过的命令的历史。使用该文件来获取命令的列表&#xff0c;并统计命令的执行次数。统计时&#xff0c;只统计命令的名称&#xff0c;以不同参数调用相同的命令也视为同一命令。 二、示例代码&am…

npm yarn 一起使用报错

项目记录&#xff0c;具有独特性&#xff0c;仅供参考 项目好好的运行&#xff0c;前一天装个测试工具包&#xff0c; 突然就不行了&#xff0c;卸载重装也不行&#xff0c;所有的项目都安装失败&#xff0c;新起一个项目也不行&#xff0c;有时候某个单独安装一个包可以&…

【爬虫】requests.post请求中的data和json使用区别

请求体是键值对形式&#xff08;无花括号&#xff09;&#xff0c;请求时需要使用data参数处理。 代码&#xff1a; data {...} ret requests.post(url, headersheaders, datadata)请求体是字典形式&#xff08;有花括号&#xff09;&#xff0c;请求时需要使用json参数处理。…

GPT-5:人工智能的下一个前沿即将到来

当我们站在人工智能新时代的门槛上时&#xff0c;GPT-5即将到来的呼声愈发高涨且迫切。作为革命性的GPT-3的继任者&#xff0c;GPT-5承诺将在人工智能领域迈出量子跃迁式的进步&#xff0c;其能力可能重新定义我们与技术的互动方式。 通往GPT-5之路 通往GPT-5的旅程已经标记着…

Markdown编辑器VNote突然让我不知所措 编辑区变小问题

环境&#xff1a;macOS VNote 3.16.0 一直喜欢用VNote因为它的编辑和显示的切换分离及右边的大纲&#xff08;菜单&#xff09;比CSDN在上方的大纲好用很多&#xff01; 但今天在编辑时不知碰到了什么键&#xff0c;编辑界面变成了下面的样子 按了回撤也没有反应&#xff0c…

mineadmin 快速安装部署(docker环境)

前提条件&#xff1a;已安装docker 一、下载dnmp环境包 github地址&#xff1a;https://github.com/tomorrow-sky/dnmp gitee地址&#xff1a; https://gitee.com/chenjianchuan/dnmp 二、看一下dnmp包目录结构 三、打开docker-compose.yml 文件&#xff0c;将不需要…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Web)上篇

提供具有网页显示能力的Web组件&#xff0c;ohos.web.webview提供web控制能力。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。示例效果请以真机运行为准&#xff0c;当前IDE预览器不支持。 需要权…

联想小新电脑出现蓝屏问题解决(通过系统重置解决了)

只看问题描述和文章最后一行字即可 问题描述 电脑出现蓝屏&#xff0c;如下 尝试解决&#xff08;并未解决&#xff09; 搜索FAULTY_HARDWARE_CORRUPTED_PAGE寻找解决方案&#xff0c;找到较为靠谱的文章&#xff1a;记录蓝屏问题FAULTY_HARDWARE_CORRUPTED_PAGE 根据文章提…

深入探讨`g++`与`gcc`:混合编程中的编译链接艺术

深入探讨g与gcc&#xff1a;混合编程中的编译链接艺术 在混合使用C和C进行项目开发时&#xff0c;选择正确的编译器和链接器对项目的成功至关重要。虽然gcc和g都是GNU编译器集合&#xff08;GCC&#xff09;的重要组成部分&#xff0c;它们在处理混合语言项目时展现出了不同的能…

【LeetCode每日一题】2864. 最大二进制奇数

文章目录 [2864. 最大二进制奇数](https://leetcode.cn/problems/maximum-odd-binary-number/)思路&#xff1a;代码1&#xff1a; 2864. 最大二进制奇数 思路&#xff1a; 1.拼贴字符串。 2.遍历字符串s,统计1的个数。 3.如果只有一个1&#xff0c;将1放在末尾&#xff0c;…

AI实战:借助Python与PaddleOCR,实现高精度文本检测与识别

1、引言 欢迎来到今天的教程&#xff1a;“驾驭PaddleOCR&#xff0c;解锁Python文字识别新技能”。在本篇文章中&#xff0c;我们将手把手教你如何安装及使用这款强大的Python库&#xff0c;轻松应对各类图像中的文字识别问题。 2、安装PaddleOCR 首先确保你的环境中已安装…

东胜物联携多款智能网关亮相瑞芯微RK开发者大会

2024年3月7-8日&#xff0c;第八届瑞芯微开发者大会在福州盛大举行&#xff0c;以“AI芯片AI应用AIoT”为主题&#xff0c;近3000名业内企业代表、开发者、院校代表、政府代表共聚一堂。 本次大会为期两天&#xff0c;共设有13大应用场景及46个生态伙伴展区。作为瑞芯微的长期…

WAAP全站防护

近年来&#xff0c;随着移动互联网的快速发展&#xff0c;诞生了APP、H5、小程序等多种应用形式&#xff0c;更多的企业核心业务、交易平台都越来越依赖这些新型应用程序。与此同时&#xff0c;越来越多的第三方API接口被调用&#xff0c;API业务带来的Web敞口风险和风险管控链…

slf4j 打印当前类和方法

spring initializr 自动包含依赖,也可以在 pom.xml 文件中添加 slf4j 的依赖,指定版本 例如我这边默认版本是 1.7.36 通过添加依赖修改版本为 1.7.32<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version…

如何在CentOS7搭建DashDot服务器仪表盘并实现远程监控

文章目录 1. 本地环境检查1.1 安装docker1.2 下载Dashdot镜像 2. 部署DashDot应用3. 本地访问DashDot服务4. 安装cpolar内网穿透5. 固定DashDot公网地址 本篇文章我们将使用Docker在本地部署DashDot服务器仪表盘&#xff0c;并且结合cpolar内网穿透工具可以实现公网实时监测服务…

计算机组成原理练习-计算机工作过程

高级语言与机器语言之间的转换 ------------------------------------------------------------------------------------------------------------------------------- 1.将高级语言源程序转换为机器级目标代码文件的程序是&#xff08;&#xff09;。 A.汇编程序 …
最新文章