☰
Gazebo中为宇树GO2 Pro添加Livox Mid360雷达仿真:从URDF到点云全攻略
2026/10/6 1:32:59 网站建设 项目流程

做宇树GO2仿真的人,十有八九会遇到同一个坎:手里拿着带Livox Mid360雷达的GO2 Pro真机,想在Gazebo里先把感知算法跑通,结果翻遍模型文件发现,官方给的URDF里根本没有Mid360这颗雷达,顶多给你留一个空荡荡的导航link。更折磨人的是,真把雷达加进模型之后,还会有插件不输出点云、TF对不上、点云穿地等一系列问题。我前前后后折腾了两周,才把这套流程完整走通。这篇就把整个配置过程和踩过的坑好好捋一遍,从URDF接线到Gazebo插件,从launch启动到RViz验证,一次性讲清楚。

1. 先搞清楚模型缺口:官方GO2描述文件为什么不能直接跑

1.1 GO2 Pro和GO2 Air的传感器配置差异

宇树GO2这个系列,不同版本的传感器配置差别挺大。GO2 Air出厂通常是Intel RealSense D435i深度相机,没有激光雷达;GO2 Pro才把Livox Mid-360作为标配传感器,装在机器狗背部支架上。问题就出在这里:很多开源社区里流传的GO2模型描述文件,往往是从Air版改过来的,或者是一个简化过的通用模型,它压根没有Mid360。

你打开模型文件的xacro,大概率会看到类似d435i_link、camera_link这些定义,但就是找不到livox_link或者mid360_link。如果你找到的模型是用unitree_ros2仓库里的go2_description去启动的,里面也许有laser的占位frame,但那个frame通常没有挂任何Gazebo传感器插件,也不会发布点云。说白了,模型骨架到位了,传感器完全没接。

所以第一步不是急着写代码,而是先确认你的模型里到底有没有雷达link,以及这个link有没有对应的sensor plugin。最简单的方式是:

ros2 launch go2_description display.launch.py

然后在RViz里看看TF树,是不是只有一个孤零零的laserframe,没有任何点云话题。或者直接看模型文件里有没有<gazebo>标签包裹的sensor type="ray"或sensor type="gpu_ray",没有的话就得自己加。

1.2 从URDF到Gazebo,中间缺了哪几步

很多人会把URDF和Gazebo仿真混为一谈,其实模型能在RViz里显示,和能在Gazebo里正常感知,中间还差了不少东西:

  • URDF只解决运动学和可视化,它告诉ROS这个机器人有哪些link、joint,以及TF树怎么连。
  • Gazebo需要的是带有物理属性的模型,每个link得有collision和inertial,不然机器人一放进世界就乱飞或者没有重力。
  • 传感器的模拟靠的是Gazebo插件,URDF里的sensor标签只是描述信息,真正输出点云/LaserScan的是sensor plugin。

所以你要做的其实有三件事:给模型加一个mid360的link和joint;给这个link补上collision、inertial和visual;挂上能发布PointCloud2的Gazebo插件。任何一步漏了,后面都会出幺蛾子。

1.3 我最终选定的组件列表

我在实际项目里用的是下面这一套组合,实测可以稳定跑通:

组件版本/名称说明
操作系统Ubuntu 22.04长期支持,资料最多
ROS发行版ROS 2 Humble与Ubuntu 22.04匹配
GazeboGazebo Classic 11gazebo_ros_pkgs支持最好
机器人描述unitree_ros2 / go2_description可自行编译或apt获取依赖
雷达插件libgazebo_ros_ray_sensor.so官方ray sensor插件,输出PointCloud2
仿真时间use_sim_time: true必须开启,否则TF和点云对不上
可视化RViz2验证点云和TF树

这套组合的优势是,所有核心包都是ROS 2 Humble官方仓库里能直接apt安装的,不依赖那些没人维护的第三方专用插件,后续出问题也好排查。

2. 环境准备:版本组合决定你后面少踩一半的坑

2.1 验证过的环境组合

我一开始也犹豫过要不要用新出的Gazebo Ignition/Garden,毕竟Ignition在功能上更现代。但现实很骨感:宇树GO2相关的开源仿真资料,绝大多数还是基于Gazebo Classic,特别是gazebo_ros_pkgs这个ROS 2桥接包,对Classic的支持最成熟。在Ubuntu 22.04 + ROS 2 Humble环境里,直接安装Gazebo Classic 11是开箱即用的。

安装命令给出来,大家可以直接抄:

sudo apt update sudo apt install ros-humble-desktop sudo apt install ros-humble-gazebo-ros-pkgs sudo apt install ros-humble-robot-state-publisher sudo apt install ros-humble-xacro sudo apt install ros-humble-rviz2

安装完之后验证一下:

ros2 pkg list | grep gazebo

如果能看到gazebo_ros、gazebo_ros_pkgs这些包,基本就绪了。

2.2 怎么处理unitree_ros2的依赖问题

如果直接从GitHub拉取unitree_ros2编译,可能会遇到一些依赖缺失的问题。我的建议是先用rosdep install处理依赖,不要手动一个个装:

cd src/unitree_ros2 rosdep install --from-paths . --ignore-src -r -y colcon build --symlink-install source install/setup.bash

编译时如果报controller_manager、joint_trajectory_controller之类的错误,先补装:

sudo apt install ros-humble-controller-manager ros-humble-joint-trajectory-controller ros-humble-gazebo-ros2-control

GO2的12个关节在Gazebo里需要用gazebo_ros2_control插件来驱动,否则就算界面里能看到机器人,关节也是僵硬的,雷达配得再好也没意义。

2.3 关于老Gazebo和新Ignition怎么选

说实话,如果你的项目不是从零开始,只是想在现有GO2仿真里加一颗雷达,我强烈建议留在Gazebo Classic。原因是:

  • 新Ignition/Garden的传感器插件写法和Classic完全不同,配置里大量用的是sdf和<plugin>标签,gazebo_ros_pkgs对它的支持还在持续完善中,不少版本之间不兼容。
  • 你搜索“GO2 Gazebo”时找到的绝大多数经验帖、模型文件、launch脚本,都是针对Classic的。
  • Classic虽然年代久,但稳定,CPU ray sensor这类基础功能非常可靠。

等到你真的需要多机器人分布式仿真、更高级的渲染效果时,再考虑迁移到Ignition,没必要在第一步就给自己增加难度。

3. 给GO2装上Mid360:URDF/Xacro层面的精确接线

3.1 雷达link和joint的定义

在动手加雷达之前,先想清楚Mid360在GO2 Pro上真实的安装位置。真机上,它通常装在机器狗背部的支架上,大致位于身体中轴线上,稍微靠前,雷达上表面是水平的,而且雷达坐标系Z轴朝上。

在URDF里,我建议把雷达单独做成一个link,然后通过fixed joint挂到base_link上。下面是我用的参考定义:

<link name="livox_link"> <inertial> <mass value="0.3" /> <origin xyz="0 0 0" /> <inertia ixx="0.001" ixy="0.0" ixz="0.0" iyy="0.001" iyz="0.0" izz="0.001" /> </inertial> <visual> <origin xyz="0 0 0" rpy="0 0 0" /> <geometry> <box size="0.06 0.06 0.06" /> </geometry> <material name="black" /> </visual> <collision> <origin xyz="0 0 0" rpy="0 0 0" /> <geometry> <box size="0.06 0.06 0.06" /> </geometry> </collision> </link> <joint name="livox_joint" type="fixed"> <parent link="base_link" /> <child link="livox_link" /> <origin xyz="0.02 0.0 0.32" rpy="0 0 0" /> </joint>

这里的xyz="0.02 0.0 0.32"表示雷达中心相对于base_link原点,向前偏移2厘米,向上偏移32厘米。这个值要根据你自己的GO2模型尺寸微调,最好的办法是在RViz里用Fixed Frame设为base_link,然后看Radar link有没有埋进身体里。

3.2 Gazebo传感器插件配置:把ray sensor变成Mid360

雷达link有了,接下来是关键中的关键——挂Gazebo传感器插件。我采用的方案是基于官方的gazebo_ros_ray_sensor插件,而不是找某个针对Livox的专用仿真插件。原因很简单:官方插件稳定,支持直接输出sensor_msgs/msg/PointCloud2,而很多第三方Livox仿真插件年久失修,跑在Humble上全是坑。

在包含livox_link的URDF文件末尾,加上这一段:

<gazebo reference="livox_link"> <sensor name="mid360" type="ray"> <pose>0 0 0 0 0 0</pose> <always_on>true</always_on> <update_rate>10</update_rate> <visualize>false</visualize> <ray> <scan> <horizontal> <samples>360</samples> <resolution>1</resolution> <min_angle>0.0</min_angle> <max_angle>6.283185307</max_angle> </horizontal> <vertical> <samples>18</samples> <resolution>1</resolution> <min_angle>-0.122173048</min_angle> <max_angle>0.907571211</max_angle> </vertical> </scan> <range> <min>0.1</min> <max>40.0</max> <resolution>0.01</resolution> </range> </ray> <plugin name="gazebo_ros_ray_sensor" filename="libgazebo_ros_ray_sensor.so"> <ros> <namespace>mid360</namespace> <remapping>~/out:=pointcloud</remapping> </ros> <output_type>sensor_msgs/msg/PointCloud2</output_type> <frame_name>livox_frame</frame_name> </plugin> </sensor> </gazebo>

这里有几个点要特别解释一下:

  • 水平角度0到2π:Mid360是360度环扫,所以水平方向必须覆盖整个圆周。
  • 垂直角度从-7度到52度:这是Mid360的真实垂直视场角。负值代表向下,正值代表向上。把-0.122173048和0.907571211代入弧度换算,正好是-7 * pi / 180和52 * pi / 180。很多人在这一步偷懒,随便填一个-1.57到1.57,结果雷达视场和真机完全不是一回事。
  • samples数量:水平360个、垂直18个,组合起来每帧6480个点,10Hz更新,每秒也就是6.5万个点。真实Mid360的点频大约20万点/秒,仿真相当于降采样了,但做导航和感知闭环验证够用。如果电脑性能强,可以适当提高samples。
  • frame_name:我填的是livox_frame,并不一定要和link名字一样。这里强烈建议让插件的frame_name和URDF里实际的TF frame保持一致,否则点云的header frame和TF树脱节,RViz里点云会乱飞。

3.3 为什么不直接用livox_ros_driver2跑Gazebo

你可能会问,真机驱动不是有livox_ros_driver2吗,Gazebo里能不能直接跑?答案是:不行。

livox_ros_driver2是给真实硬件用的,它通过UDP/串口和雷达通信,仿真环境里没有硬件设备,驱动节点起来以后只会报一堆连接超时。还有一个方案是找Livox官方的仿真插件,但我测试下来,要么是ROS 1时代的代码,要么依赖特定版本的Gazebo,想无缝嵌进ROS 2 Humble + GO2这套系统,成本相当高。

用官方的ray_sensor插件,本质上是放弃模拟Mid360的底层扫描方式,只模拟“从这里发射射线、得到距离、形成点云”这个结果。对于绝大多数SLAM、避障、目标检测算法的验证来说,这个结果已经足够可信了。

4. 启动流程与闭环验证:从启动launch到RViz里看到点云

4.1 完整的launch文件骨架

我习惯把所有启动逻辑装进一个launch.py里,这样可复现性最强。下面是一个最简可用的骨架:

import os from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, SetEnvironmentVariable from launch.substitutions import Command, LaunchConfiguration from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.actions import Node from ament_index_python.packages import get_package_share_directory def generate_launch_description(): pkg_gazebo_ros = get_package_share_directory('gazebo_ros') pkg_go2 = get_package_share_directory('go2_description') robot_description = Command(['xacro ', os.path.join(pkg_go2, 'urdf', 'go2.urdf.xacro')]) return LaunchDescription([ SetEnvironmentVariable('RCUTILS_COLORIZED_OUTPUT', '1'), DeclareLaunchArgument('use_sim_time', default_value='true'), IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join(pkg_gazebo_ros, 'launch', 'gazebo.launch.py') ), launch_arguments={'world': '', 'verbose': 'true'}.items(), ), Node( package='robot_state_publisher', executable='robot_state_publisher', parameters=[{'robot_description': robot_description}], output='screen', ), Node( package='gazebo_ros', executable='spawn_entity.py', arguments=['-topic', 'robot_description', '-entity', 'go2'], output='screen', ), Node( package='rviz2', executable='rviz2', output='screen', ), ])

启动:

ros2 launch go2_sim go2_sim.launch.py

启动后先别急着看点云,先等Gazebo加载完模型,然后在Gazebo界面里确认机器人没有陷进地面、没有乱弹。

4.2 使用sim_time:一个直接影响点云和TF对不上的开关

这是整个配置流程里最容易被忽略、但影响最致命的一步。Gazebo里所有传感器的时间戳都是仿真时间,不是你的系统时间。如果你不把use_sim_time这个参数打开,robot_state_publisher发布的TF用的是系统时间,mid360点云的header.stamp用的是仿真时间,两边就对不上。

最典型的现象是:RViz里能看到TF树,但是一添加PointCloud2话题,点云就在画面里“闪烁”或者整体偏移,控制台还会刷红色警告,提示TF_OLD_DATA。

解决办法就是在launch里给所有和仿真相关的节点,包括robot_state_publisher、rviz2,都显式传入use_sim_time: true。如果使用参数文件,也要单独写清楚:

robot_state_publisher: ros__parameters: use_sim_time: true

RViz2也要在命令行里给参数:

ros2 run rviz2 rviz2 --ros-args -p use_sim_time:=true

这一步做完,点云和TF的同步问题能解决掉80%。

4.3 在RViz里检查点云的三个关键指标

看到点云之后别急着开心,先确认这三个指标,缺一个都不算真正跑通:

  • Frame ID:在RViz的PointCloud2属性里,看Fixed Frame和点云的header frame。我配置里雷达点云的frame是livox_frame,如果Fixed Frame设成了odom,正常情况应该能看到机器人周围的点云随着模型姿态实时变化。
  • 点云数量:用下面的命令打印一下话题频率和消息大小:
ros2 topic hz /mid360/pointcloud ros2 topic echo /mid360/pointcloud --once

10Hz更新率、每帧6000多个点,这是健康的。如果一帧只有几百个点,八成是samples配少了或者射线穿透了。

  • 点云与TF树的关系:在RViz左侧勾选Tf,如果看到livox_link到base_link这条连接线是绿色的,说明坐标系关系正常。如果TF树里没有livox_link,马上检查xacro里的joint是否真的被加载了。

5. 避坑清单:我实际踩过的九个坑和排查方法

5.1 点云穿透墙面和地面

这是频率最高的问题。Gazebo的ray sensor做碰撞检测时,只认每个模型的collision几何体,不认visual。如果你的环境模型是网上下载的,很多作者只给了好看的visual mesh,collision根本没建,射线就会直接穿墙,雷达“透视”。

解决思路:

  • 检查环境SDF/URDF,给墙体、地面补上collision。
  • 如果只是测试,可以用Gazebo自带的world里的墙体,那些是带collision的。
  • 还有一种特殊情况:雷达link过高/过低或者被模型本身遮挡,点云会扫到自己的机械腿。真机上也会扫到狗腿,但仿真里如果你不想要这些点,可以在后续算法里做距离过滤。

5.2 点云帧率忽高忽低

如果你把samples调得很高,比如水平900、垂直64,然后又开了visualize,Gazebo的CPU ray sensor会直接把Gazebo跑成PPT。这个插件非常吃CPU,因为每根射线都要做一次物理碰撞检测。

我的实测经验是:水平360、垂直18、10Hz,是性能和效果比较平衡的组合。如果你的机器有独立NVIDIA显卡,可以考虑改用GPU ray sensor,但我更推荐先降低采样,而不是盲目追求点云密度。毕竟仿真验证的是算法流程,不是雷达点云的美观程度。

5.3 TF树里找不到livox_link

这种情况常常出现在你改完URDF但忘了重新生成robot_description。Xacro文件修改后,必须重新source,然后重启robot_state_publisher。尤其是用colcon build --symlink-install的时候,别忘记重新sourceinstall/setup.bash。

排查命令:

ros2 run tf2_ros tf2_echo base_link livox_link

如果提示Could not transform,就说明TF树没有这个frame。先看robot_state_publisher的日志,再检查URDF里joint的父子关系。注意,如果你的GO2模型根节点不是base_link而是base_footprint,要把joint的parent相应改掉。

5.4 点云整体乱飞或漂移

前面提到过,很大概率是use_sim_time没开。还有一个隐蔽场景:你同时启动了多个RViz或者之前残留的节点,这些节点可能有不同的time source,导致TF缓存错乱。我的习惯是每次改完配置,把终端里所有ROS相关进程清干净再启动:

pkill -f ros2 pkill -f gzserver pkill -f rviz2

5.5 雷达模型遮挡和自扫描

如果你给livox_link加了一个很大的collision盒子,比如box size="0.2 0.2 0.2",射线从雷达坐标原点发出时,很可能会先打到自己的collision盒,产生一圈密不透风的近距点云,严重干扰SLAM。

解决办法是把雷达link的collision做得尽量小,或者直接不给雷达加collision。在仿真里,传感器link的collision只是为了物理碰撞,而雷达本身又不会参与碰撞,所以去掉问题不大。如果你确实需要一个安装支架,那就把支架的collision做细,别做成大块实体。

5.6 CPU ray sensor和GPU ray sensor的选择

我在调试时也试过GPU ray sensor,优点是快,缺点是对显卡驱动敏感,而且在没有NVIDIA驱动的服务器上可能完全跑不起来。如果你用GPU版本遇到点云不输出、黑屏这类问题,不要急着怀疑URDF,先换回CPU版本试试。

网上很多老帖子里会提到用libgazebo_ros_gpu_laser.so或libgazebo_ros_ray_sensor.so的旧版ROS 1写法,ROS 2 Humble环境下这些接口大多不生效,直接用我上面的官方插件写法最稳妥。

5.7 雷达垂直FOV方向搞反

Mid360的垂直方向不是对称的,向下只有7度,向上有52度。如果你把垂直samples设置成负角度到正角度对称,比如-52度到52度,雷达视场会和真机差很多,仿真出来的点云特征也不对。

具体到Gazebo ray sensor的坐标定义:vertical的负角度表示向下,正角度表示向上。所以Mid360的正确配置区间应该是从-0.1222弧度到0.9076弧度。如果你的机器人需要雷达避开障碍物、看到脚下台阶,那安装时雷达本身可以稍微俯仰,但这属于机械安装角度问题,不是传感器FOV配置问题。

5.8 点云话题有数据但RViz里不显示

这个坑很气人。通常原因是RViz里的Fixed Frame和点云frame不在同一个TF树上,或者点云frame的TF没有发布。你可以用下面命令看看是否有数据:

ros2 topic echo /mid360/pointcloud --once | head -50

有数据但RViz不显示,就去看RViz左下角的错误提示。如果提示Fixed Frame [odom] does not exist,那就说明你还没发布odom到base_link的TF,得先跑一个静态TF或者把Fixed Frame改成base_link,等后面有了定位模块再切到odom。

5.9 常见问题速查表

现象根因快速解决办法
点云穿透墙壁环境模型缺少collision给墙体、地面补collision
点云闪烁/TF报错未开启use_sim_time所有节点加use_sim_time: true
帧率极低samples过多/update_rate太高水平360,垂直18,10Hz
TF树缺少雷达framexacro未重新加载重新source,重启robot_state_publisher
雷达扫到自己一堆点radar link collison过大减小或去掉雷达collision
点云有数据但RViz不显示Fixed Frame错误/TF缺失先设Fixed Frame为base_link
点云数量太少vertical samples太少增大到18~32
雷达安装位置不对未根据模型调整xyz在RViz里目测后微调joint origin

6. 把Mid360仿真点云接入下游SLAM的一点建议

6.1 怎么适配Point-LIO/FAST-LIO这类Livox专用算法

这里必须泼一盆冷水:很多针对Livox雷达优化过的SLAM,比如Point-LIO、部分版本的FAST-LIO,订阅的是Livox自定义消息类型,而不是标准sensor_msgs/msg/PointCloud2。真实Mid360的ROS 2驱动通常发布的是Livox CustomMsg,里面除了xyz、intensity之外,还有tag、line这些字段,算法会靠这些字段做点云预处理。

而Gazebo的ray sensor只能给你标准的PointCloud2,这两者之间有一条明显的鸿沟。我的建议是二选一:

  • 改算法接口:找一个以PointCloud2为输入的SLAM版本,比如FAST-LIO的PointCloud2接口,或者LIO-SAM,这类算法可以直接吃标准点云。
  • 自己写转换节点:订阅/mid360/pointcloud,把它转成Livox CustomMsg,字段照格式填。转换时要注意时间戳不要丢掉,line字段可以用来模拟扫描线号。

对大多数做自主避障、导航、全局状态机验证的场景来说,直接选PointCloud2友好的算法链路最省事。

6.2 仿真点云的精度边界:哪些能信,哪些不能信

配置完成之后,要清楚知道这套仿真的边界在哪里。Gazebo里的ray sensor是理想化的距离测量,没有真实Mid360的非重复扫描残影,没有距离噪声,也没有运动畸变。所以:

  • 能信:坐标系变换、传感器安装位置、视场角覆盖、点云是否存在遮挡、避障逻辑的决策结果。
  • 不能信:点云的测距噪声分布、建图精度、回环检测的稳定性。

如果你要做建图算法的性能测试,建议在真实环境里录制bag,或者给Gazebo点云人为叠加高斯噪声,不然很容易被仿真的“完美点云”误导。

我在实际调试中还有一个小习惯:先把雷达点云话题和TF树的情况用ros2 bag record录一段,然后用ros2 bag play重放,这样每次取参、调整参数都不用反复重启Gazebo,效率高很多。完整跑通这套流程后,你就有了一台“虚拟GO2 Pro”,后续再调SLAM、调避障,都在这台虚拟狗上做,不用每次都在真机上折腾。

最后再分享一个经验:如果你在配置过程中突然找不到某个frame,先不要怀疑代码,打开TF树看一眼,大多数问题都是URDF里joint的父子关系写错了。从URDF到Gazebo的链路本身不难,难的是每一步都要确认清楚,千万别跳步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询