
如果你和我一样是在 Ubuntu 16.04 上开始折腾 ROS 的那你大概率经历过这种场景装好了 ROS Kinetic打开了 Gazebo看着空荡荡的世界不知道该往里面放什么更不知道怎么把传感器接进来然后用 Rviz 看到数据。这篇文章我打算完整走一遍这个流程在 Ubuntu 16.04 ROS Kinetic 的环境下用 Gazebo 搭建一个带 Camera、Kinect、Lidar 三种传感器的机器人仿真环境然后通过 Rviz 把图像、点云、激光数据全部显示出来。先说清楚这篇内容适合谁。如果你正准备做 SLAM、路径规划、目标检测又暂时没有真机或者你已经在 turtlebot3、panda 这类现成模型上跑过 demo但不知道传感器是怎么挂上去的那这篇就是给你准备的。我会把从环境安装到传感器配置再到 Rviz 显示的完整链路都走一遍每一步都给出可复制的写法尽量做到你照着敲就能跑起来。1. 环境准备Ubuntu 16.04 上的 ROS Kinetic 与 Gazebo1.1 版本选型为什么是 Ubuntu 16.04 Kinetic很多刚入门的同学有个误区以为 ROS 是独立运行的软件随便找个 Linux 发行版装上就行。实际上 ROS 和 Ubuntu 版本有严格的对应关系官方只保证特定组合的稳定性。Ubuntu 16.04 对应的是 ROS Kinetic Kame这是 2016 年发布的版本虽然已经 EOL但你搜到的教程、旧项目、论文代码绝大多数都跑在这个组合上。另外ROS Kinetic 的 desktop-full 版本会自带 Gazebo 7这个细节很关键。很多教程让你单独装 Gazebo结果装出来一个高版本和 ros_control、gazebo_ros_pkgs 之间出现兼容问题。我的建议是既然要用 Gazebo 做传感器仿真就直接装 desktop-full把 ROS 主包、Gazebo、Rviz 一次到位省掉后面一堆依赖冲突的麻烦。还有一个热门问题“Ubuntu 22.04 能不能跑这些旧教程”虽然新环境有 gazebo harmonic、ros2 jazzy 这些新组合但老教程里的很多配置细节已经不适用了。所以我一直建议新手尤其是刚接触传感器仿真的人先老老实实复现一遍 Ubuntu 16.04 Kinetic 这套经典组合。等搞清楚 Gazebo 传感器插件的工作原理再迁移到新版本也不迟。1.2 安装步骤与常见姿势严格来说安装 ROS Kinetic 的官方方式是添加源、添加密钥、更新、安装。但说实话这个过程在国内网络环境下很容易卡住尤其是官方源速度惨不忍睹。我的实际经验是直接用社区开发者维护的一键安装脚本也就是大家常说的“鱼香ROS一键安装”在终端里一条命令就能把 ROS Kinetic 装好。wget http://fishros.com/install -O fishros . fishros脚本运行后会弹出选项菜单让你选择安装 ROS 版本、桌面版还是基础版跟着提示选就行。我实测下来整个过程会自动配置源、处理依赖比手动一条条敲 apt 命令省心得多。装完之后顺手确认一下核心组件是否正常roscore # 另开一个终端 gazebo --version rosrun rviz rviz如果这三个都能正常启动说明基础环境没问题。这里还差一个关键包——gazebo_ros_pkgs它是 Gazebo 和 ROS 之间的桥梁负责把 Gazebo 里的传感器数据发布成 ROS 话题。desktop-full 一般已经带上了但为了保险建议手动再装一次sudo apt install ros-kinetic-gazebo-ros-pkgs ros-kinetic-gazebo-ros-control这一步装完你的仿真地基就打好了。从下一节开始进入正题怎么给机器人挂上传感器。2. 传感器仿真整体思路与机器人模型构建2.1 Gazebo 传感器插件的工作原理在搞清楚怎么写代码之前先理解 Gazebo 传感器仿真的底层逻辑这个比背参数重要得多。很多人照着教程写了一大堆 URDF一旦换个传感器就不会了根本原因就是没理解插件机制。简单说机器人的外观和碰撞属性由 URDF/Xacro 描述这部分负责“长什么样”而传感器的数据采集完全由 Gazebo 插件负责插件就是“传感器驱动程序”。Gazebo 底层通过gazebo标签找到你指定的 link在这个 link 上挂一个sensor然后指定一个 plugin 插件插件在仿真循环中不断读取仿真数据再通过 ROS 话题发出去。我打个比方。URDF 里的 link 就好比墙上打好的安装孔sensor是你买的摄像头硬件plugin 则是摄像头附带的 USB 驱动。只有把驱动装上并正确配置电脑才能收到摄像头的数据。这里有个初学者经常搞混的点URDF 里的visual只是画个形状给你看真正的数据仿真靠的是gazebo标签内的sensor和plugin。你完全可以在 URDF 里不画任何传感器的外形只在gazebo里挂传感器仿真依旧有效只是看不到传感器长什么样。2.2 用 Xacro 搭建带传感器的机器人模型既然要挂三种传感器我推荐用 Xacro 而不是纯 URDF。Xacro 支持宏定义、属性声明、数学表达式比如你可以定义一个属性控制传感器安装高度改一个数字整条机器人模型跟着变调试效率高得多。先创建自己的工作空间和功能包mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_create_pkg my_robot_description roscpp rospy urdf xacro gazebo_ros gazebo_plugins cd ~/catkin_ws catkin_make source devel/setup.bash然后在my_robot_description/urdf目录下建robot.xacro文件。一个最简单的带底座的机器人长这样?xml version1.0? robot namesensor_robot xmlns:xacrohttp://www.ros.org/wiki/xacro link namebase_link visual geometry box size0.4 0.3 0.1/ /geometry /visual collision geometry box size0.4 0.3 0.1/ /geometry /collision inertial mass value1.0/ inertia ixx0.01 ixy0.0 ixz0.0 iyy0.01 iyz0.0 izz0.01/ /inertial /link gazebo referencebase_link materialGazebo/Blue/material /gazebo /robot注意几个细节inertial是必须的否则 Gazebo 在计算动力学时会报惯性矩阵异常。collision必须给虽然仿真中不必然碰撞但空碰撞体也可能导致物理引擎警告。写完后用check_urdf检查一下rosrun urdfdom check_urdf robot.xacro验证通过后再用 xacro 命令生成纯 URDF 看一眼整体结构rosrun xacro xacro robot.xacro robot.urdf到这里你拥有的只是一个空壳机器人下一节我给这个壳装上三种传感器这才是整篇博文的重头戏。3. 三大传感器仿真配置与参数详解3.1 摄像头 Camera 仿真libgazebo_ros_cameraCamera 是最容易上手的传感器也是我建议你第一个调试的。原因很简单它的配置链路短插件少出现问题容易定位。在robot.xacro的robot标签内添加摄像头 link 和 jointlink namecamera_link visual geometry box size0.05 0.05 0.05/ /geometry origin xyz0 0 0 rpy0 0 0/ /visual /link joint namecamera_joint typefixed parent linkbase_link/ child linkcamera_link/ origin xyz0.2 0 0.15 rpy0 0 0/ /joint然后是核心部分在gazebo标签中挂 camera sensor 和插件gazebo referencecamera_link sensor typecamera namecamera1 update_rate30.0/update_rate camera namehead horizontal_fov1.3962634/horizontal_fov image width640/width height480/height formatR8G8B8/format /image clip near0.02/near far300/far /clip /camera plugin namecamera_controller filenamelibgazebo_ros_camera.so alwaysOntrue/alwaysOn updateRate0.0/updateRate cameraNamecamera/cameraName imageTopicNameimage_raw/imageTopicName cameraInfoTopicNamecamera_info/cameraInfoTopicName frameNamecamera_link/frameName /plugin /sensor /gazebo逐个说下这些参数的含义。update_rate是传感器刷新频率单位 Hz30Hz 是常规视频刷新率跑 SLAM 时如果嫌 CPU 压力大可以降到 15。horizontal_fov是水平视场角单位弧度1.3962634 约等于 80 度640x480 分辨率下接近普通摄像头视角。clip里的 near 和 far 是渲染裁剪平面摄像头只能看到这个范围内的物体near 设太大容易把靠近的物体裁剪掉设太小又可能出现 z-fighting。插件参数中frameName尤为重要它决定了图像数据所在的坐标系。如果这个名称和 URDF 里的 link 名对不上Rviz 里虽然能收到图像但图像无法和机器人模型在同一坐标系下对齐点云投影时也会错位。我建议frameName永远和 link 名称保持一致。启动后你可以用命令检验话题rostopic list | grep camera rostopic echo -n1 /camera/image_raw如果话题有数据输出Camera 仿真就算跑通了。3.2 Kinect 深度相机仿真openni_kinect 插件Kinect 比普通 Camera 复杂一些。它同时输出 RGB 图、深度图、点云。在 Gazebo 里早期专门为它做了libgazebo_ros_openni_kinect.so插件尽管现在已经用libgazebo_ros_depth_camera.so替代但 Ubuntu 16.04 Kinetic 生态里 openni_kinect 依然是最稳的选择我接下来就用它演示。照例先加 link 和 jointlink namekinect_link visual geometry box size0.08 0.08 0.08/ /geometry /visual /link joint namekinect_joint typefixed parent linkbase_link/ child linkkinect_link/ origin xyz0.2 0 0.18 rpy0 0 0/ /joint然后加 sensor 和插件gazebo referencekinect_link sensor typedepth namekinect update_rate30.0/update_rate camera namekinect_rgb horizontal_fov1.3962634/horizontal_fov image width640/width height480/height formatR8G8B8/format /image clip near0.02/near far300/far /clip /camera plugin namekinect_controller filenamelibgazebo_ros_openni_kinect.so alwaysOntrue/alwaysOn updateRate10.0/updateRate cameraNamekinect/cameraName imageTopicNamergb/image_raw/imageTopicName depthImageTopicNamedepth/image_raw/depthImageTopicName pointCloudTopicNamepoints/pointCloudTopicName cameraInfoTopicNamergb/camera_info/cameraInfoTopicName depthImageCameraInfoTopicNamedepth/camera_info/depthImageCameraInfoTopicName frameNamekinect_depth_frame/frameName pointCloudCutoff0.4/pointCloudCutoff pointCloudCutoffMax8.0/pointCloudCutoffMax /plugin /sensor /gazebo注意几个容易踩坑的地方。sensor typedepth不是camera这是深度相机的专有类型。如果你写成typecamera插件根本无法正常工作。updateRate这里虽然 sensor 层设了 30插件层又设了 10实际运行时以插件层的为准因为插件层面 specified 的 updateRate 会覆盖 sensor 的配置。pointCloudCutoff和pointCloudCutoffMax控制点云的有效距离范围太近的点会因为深度相机基线限制而出错太远的点噪声大这两个值要根据你的实际场景调整。启动后检查这三个话题是否都有数据rostopic list | grep kinect rostopic hz /kinect/rgb/image_raw rostopic hz /kinect/depth/image_raw rostopic hz /kinect/points有个经验之谈如果你发现/kinect/points话题不存在大概率是插件没加载成功原因可能是 sensor 类型写错也可能是libgazebo_ros_openni_kinect.so这个库找不到。用下面命令确认库文件是否存在ls /opt/ros/kinetic/lib/libgazebo_ros_openni_kinect.so如果文件不存在说明你缺 gazebo_plugins 包重新安装ros-kinetic-gazebo-plugins即可。3.3 激光雷达 Lidar 仿真libgazebo_ros_laser / gpu_rayLidar 仿真和图像传感器完全是另一个套路。图像传感器是模拟渲染管线Lidar 用的是射线探测。Gazebo 里 2D 激光雷达核心 sensor 类型是ray插件是libgazebo_ros_laser.so。先加 link 和 jointlink namelaser_link visual geometry cylinder radius0.05 length0.05/ /geometry /visual /link joint namelaser_joint typefixed parent linkbase_link/ child linklaser_link/ origin xyz0 0 0.12 rpy0 0 0/ /joint然后是核心部分gazebo referencelaser_link sensor typeray namelaser pose0 0 0 0 0 0/pose visualizefalse/visualize update_rate10/update_rate ray scan horizontal samples360/samples resolution1/resolution min_angle-3.14159/min_angle max_angle3.14159/max_angle /horizontal /scan range min0.10/min max30.0/max resolution0.01/resolution /range noise typegaussian/type mean0.0/mean stddev0.01/stddev /noise /ray plugin namelaser_controller filenamelibgazebo_ros_laser.so topicNamescan/topicName frameNamelaser_link/frameName /plugin /sensor /gazebo这里面的参数得仔细讲。samples是扫描点数360 就是一圈 360 个点相当于角度分辨率 1 度实际雷达通常在 360 到 1080 之间选择点数越高 cpu 开销越大。min_angle和max_angle是扫描范围-π 到 π 就是 360 度扫描如果要模拟前向雷达就改成 -π/2 到 π/2。range里的 min 和 max 是雷达量程0.1 到 30 米是两个常见值。noise是高斯噪声设置。这个参数很多人忽略但如果你想在仿真里验证算法对噪声的鲁棒性必须加上。stddev 是标准差0.01 表示约 1cm 的测距误差实际雷达的噪声水平会更高一些。特别提醒一个我踩过的坑如果需要 3D 激光雷达Gazebo 提供了gpu_ray类型它把射线检测放到 GPU 上计算性能明显提升但前提是机器有可用的显卡驱动。配置基本和ray一样只需要把sensor typegpu_ray插件换成libgazebo_ros_gpu_laser.so。没有 GPU 的环境强行用 gpu_ray 反而更慢甚至闪退这个注意一下。启动后确认rostopic hz /scan如果频率稳定在 10Hz 左右说明 Lidar 仿真正常。4. Rviz 数据显示与仿真系统联调4.1 launch 一键启动整个仿真流程机器人和传感器模型都写好了接下来要做的就是让整个系统跑起来。我强烈建议把所有启动项写进一个 launch 文件否则每次都要手动开五六个终端太影响效率。在功能包下创建launch/sensor_demo.launch?xml version1.0? launch param namerobot_description command$(find xacro)/xacro $(find my_robot_description)/urdf/robot.xacro / node namerobot_state_publisher pkgrobot_state_publisher typerobot_state_publisher param namepublish_frequency value50.0/ /node node namejoint_state_publisher pkgjoint_state_publisher typejoint_state_publisher param nameuse_gui valuefalse/ /node include file$(find gazebo_ros)/launch/empty_world.launch arg namepaused valuefalse/ arg nameuse_sim_time valuetrue/ arg namegui valuetrue/ /include node namespawn_model pkggazebo_ros typespawn_model args-urdf -param robot_description -model sensor_robot -x 0 -y 0 -z 0.05/ node namerviz pkgrviz typerviz / /launch这里面有几个必须要设置的关键参数。use_sim_time是最容易忽略的设为 true 表示所有节点使用 Gazebo 的仿真时间而不是系统真实时间。如果这个不设Rviz 和传感器数据会出现时间戳不同步的问题表现就是图像显示会一卡一卡或者闪一下就没。paused设为 false 让仿真自动启动否则 Gazebo 打开后是暂停状态需要手动点 play新手经常在这里卡住看半天数据都不动其实是仿真根本没跑。joint_state_publisher是为了发布关节状态。虽然我们所有关节都是 fixed但 robot_state_publisher 仍然依赖关节状态来计算 tf。如果只有 fixed joint可以不开 joint_state_publisher但保持开着更稳妥以后加了活动关节也不用重新改 launch。4.2 Rviz 中显示 Camera 图像、LaserScan、PointCloud2launch 启动后系统会依次打开 Gazebo、Rviz。Rviz 一开始必然是什么都没有的需要手动加上要显示的 Data。第一步设置全局坐标系。在 Rviz 左上角 Displays 面板里找到 Global Options把 Fixed Frame 设为base_link。这也是几乎所有显示问题的根源Fixed Frame 设错、或者 tf 树不完整会导致后面的数据要么显示不出来要么模型飞出去十万八千里。然后添加显示项添加 Image点击 Add - By topic找到/camera/image_raw选择 Camera 显示类型。这样就能看到摄像头画面。如果画面是花的或者黑的先检查是否已经向 Rviz 添加了robot_model显示 3D 机器人模型有时候 RGB 图黑屏是因为场景里没有物体把机器人本体也显示出来就有画面了。添加 LaserScanAdd - By topic找到/scan选择 LaserScan 显示类型。这里有个视觉细节默认激光点颜色是粉色把 Color 改成 Fixed然后在 Color 里设置成某个亮度值这样地图、障碍物边缘的明暗对比会清晰很多。Size 设为 0.05 左右Pose 的 Tolerance 设置为 0.1 秒之内过于严格要求会导致频繁丢帧。添加 PointCloud2Add - By topic找到/kinect/points选择 PointCloud2 显示类型。若点云显示为一条直线或者没有深度感把 Size (Pixels) 调大比如 3并且在 Color Transformer 里选择 AxisColor 或 RGB8。Kinect 点云的 RGB 是随着图像话题一起打包的如果选 FlatColor 就只能看到一个颜色的点不直观。4.3 TF 坐标系与显示对不上的排查你可能会遇到这种情况图像有话題、激光有话題但 Rviz 里机器人模型和点云位置对不上或者 LaserScan 雷达扫描线不在车体上而是在原点附近绕着小圈。这类问题九成出在 TF 树上。传感器数据的话题本身不携带位置信息Rviz 显示时根据 Fixed Frame 和传感器数据的 frame 查找 TF 变换找到才能正确渲染。如果中间断链数据就会显示在坐标原点。排查步骤建议这样rosrun rqt_tf_tree rqt_tf_tree # 或者 rosrun tf view_frames evince frames.pdf重点看两个地方。一是base_link到camera_link、kinect_link、laser_link之间是否有完整的父子链二是传感器插件里的frameName是否和 URDF 里的 link 名称一一对应。还有一个常见坑laser 的 link 如果直接挂在base_link下它的坐标原点取决于 link 的 origin。如果你在 URDF 里把 laser_link 放在车体上方 0.12 米处但插件里的frameName写成了base_link那 Rviz 会直接把 scan 画在车体中心高度看起来就像雷达被装进地底了。检查 frame 名称是否完全一致多一个斜杠、大小写不一致都不行。5. 常见问题与避坑清单5.1 Gazebo 黑屏、模型加载失败Gazebo 启动后长时间黑屏或者卡在“Loading world”界面这是仿真老玩家都会遇到的问题。最常见的原因是模型数据库文件缺失。Gazebo 启动时要下载gazebo_models模型库网络不稳定时下载失败就会卡住。解决方法是提前手动下载模型库并设置环境变量git clone https://github.com/osrf/gazebo_models.git mkdir -p ~/.gazebo/models cp -r gazebo_models/* ~/.gazebo/models/ echo export GAZEBO_MODEL_PATH~/.gazebo/models:$GAZEBO_MODEL_PATH ~/.bashrc source ~/.bashrc如果黑屏还伴随着图形渲染问题比如窗口一片灰色没有托盘场景可能是显卡 OpenGL 兼容问题。试试强制软件渲染export LIBGL_ALWAYS_SOFTWARE1 gazebo这个方法会牺牲部分渲染性能但能救回大部分兼容性故障。注意这个环境变量只在当前终端生效不需要写到 bashrc 里否则 Gazebo 长期慢速运行会拖累整个仿真。5.2 Rviz 打不开或闪退“Rviz 打不开”是 Rviz 用户的高频问题在 Ubuntu 16.04 上尤其常见。表现形式是点击后没有任何窗口弹出或者弹出一瞬间就闪退。先检查是不是缺库问题用命令行方式启动看看报错信息rosrun rviz rviz如果提示Xlib: extension NV-GLX missing或者 OpenGL 相关的段错误通常和显卡驱动有关。能切换到独显环境就切换试试不想折腾就用软件渲染export LIBGL_ALWAYS_SOFTWARE1 rosrun rviz rviz如果这一步能打开 Rviz就把这行环境变量固定下来写进~/.bashrc。另外还有一个低级错误在同一个终端里先启动了多窗口的 Rviz 崩溃之后残留在进程列表里再次启动时会因为资源冲突失败。测试前执行pkill -f rviz是一个好习惯。5.3 传感器话题无数据、点云显示异常传感器话题完全无数据排查思路按下面的顺序来# 第一步查看当前所有话题 rostopic list # 第二步是否包含目标话题 rostopic echo /kinect/points # 第三步数据频率 rostopic hz /kinect/points如果话题根本不存在问题出在模型加载阶段先看 Gazebo 启动终端的输出搜一下 ERROR 关键字。Gazebo 在无图形界面或模型加载失败时经常吞掉 ERROR 日志新的终端里运行gzserver反而能看到更明显的报错信息。如果话题存在但频率为零或者 hz 输出极低可能是插件 updateRate 太低。把插件里updateRate从 10 调到 30。如果 CPU 算力较弱导致 Gazebo 实时率上不去表现也是话题频率不稳定可以把机器人的传感器 update_rate 适当降低这不算偷工减料仿真精度够用就行。点云显示异常的常见情况是点云只在原点附近的一小块区域分布检查 Fixed Frame 是否改成了kinect_depth_frame以及 frameName 是否与 TF 树中的坐标系一致。5.4 问题速查表现象可能原因解决办法Gazebo 长时间黑屏模型库下载失败、环境变量未设置手动克隆 gazebo_models 到 ~/.gazebo/modelsGazebo 一打开就崩溃显卡驱动 / OpenGL 兼容问题export LIBGL_ALWAYS_SOFTWARE1Rviz 闪退无法打开显卡驱动 / 残留进程软件渲染或先 pkill -f rviz图像话题存在但黑屏场景中没有物体、相机的 clip near 过大添加机器人模型显示、检查 near 参数/kinect/points 话题不存在sensor 类型写错、插件库缺失检查 typedepth、确认库文件存在LaserScan 显示在原点frameName 与 link 名不一致检查插件 frameName 是否等于 laser_link显示数据时模型剧烈抖动tf 树不完整、Fixed Frame 设置错误用 rqt_tf_tree 查看完整 TF 树仿真时间与真实时间不同步未设置 use_sim_timelaunch 中加arg nameuse_sim_time valuetrue/CPU 占用过高、仿真卡顿传感器采样率过高降低 update_rate 或 samples 数量写到这里Gazebo 传感器仿真 Rviz 显示的完整链路已经走通。我自己的体会是Gazebo 传感器仿真最大的价值不是给你一个花哨的三维画面而是让你在不需要真机的情况下把数据链路、坐标变换、话题通信这些底层逻辑彻底搞清楚。上真机之前先用仿真把 Rviz 显示、数据格式、tf 树这些环节过一遍真机调试时你会省下大量排错时间。最后分享一个小技巧如果你后续要做 SLAM这套环境不用做大改动只需要给base_link加两个驱动轮再把joint_state_publisher换成joint_state_publisher_gui手动控制轮子转动就能跑起 gmapping 或 cartographer。我当年就是在这个仿真环境里第一条 scan 数据被正确显示出来的那一刻才真正理解了 ROS 里“话题”和“坐标系”这两个概念的威力。你照着跑通一次也会对 Gazebo、Rviz 这套工具链的理解上一个台阶。