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

本文围绕ROS多机器人在同一地图的导航展开。介绍了实验环境,包括Ubuntu、ROS等。重点阐述在单机器人导航基础上引入命名空间区分多机器人属性。还给出launch文件配置方法,分享趟坑指南,如命名空间值传递、坐标参数设置等,最后指出小车导航相撞的缺陷待改善。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

一、实验环境

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

二、目录结构

在这里插入图片描述

相比单机器人导航,改动主要集中在launch目录下,gazebo差速器仿真部分也需要修改,rviz下有少量插件配置变动不是学习重点。
 

三、launch文件

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

carbot_group_navigation.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)。
 

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我是没成功过。
 

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)
在这里插入图片描述
在这里插入图片描述

五、缺陷

实测在小车导航到同一位置时会出现相撞,可能是小车模型同高,雷达监测不到的原因,待验证和改善。

### 设置和使用ROS Melodic工作空间 #### 创建Catkin工作空间 为了在树莓派或其他Linux设备上创建并初始化一个用于ROS Melodic的工作空间,可以按照如下方式操作: 首先,在终端中输入命令来为ROS Melodic源设置脚本: ```bash source /opt/ros/melodic/setup.bash ``` 接着,通过以下命令创建一个新的目录作为个人的工作区,并进入此目录内完成后续的操作[^1]: ```bash mkdir -p ~/catkin_ws/src cd ~/catkin_ws/ ``` 之后,可以通过`catkin_make`工具编译这个空的工作空间,这一步骤会自动生成必要的构建文件夹以及链接到系统的安装路径下的依赖项: ```bash catkin_make ``` 当上述过程完成后,还需要更新当前shell实例中的环境变量以便能够访问新建立起来的workspace内的资源。为此需运行下面这条指令: ```bash source devel/setup.bash ``` 以上步骤即完成了基本的Catkin工作空间设定。 对于那些希望利用Anaconda环境中Python3版本来进行开发的人来说,则需要注意一些特殊的配置事项。由于默认情况下ROS是以系统自带的Python解释器为基础构建出来的,所以在切换至其他版本之前可能要先做一些准备工作,比如调整CMakeLists.txt文件里的某些参数以适应新的Python库位置等[^3]。 另外,如果打算集成特定功能包如bebop_autonomy的话,考虑到其特殊需求(例如针对不同操作系统版本的支持情况),则应参照专门文档说明做进一步适配处理[^4]。
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值