
做机器人开发这几年我越发觉得传感器仿真是整个ROS学习曲线里最“回本”的一环。没有仿真你调试一个视觉SLAM或者激光避障算法得先买相机、买雷达、搭车架硬件接线就要折腾一两天参数标定再吃掉你一个周末。而基于ubuntu16.04上的Gazebo传感器仿真配合Rviz做数据显示等于把整套硬件验证流程搬到了电脑里camera、kinect、lidar这些传感器模型在gazebo里跑出来的话题格式、坐标系关系、数据频率和真实传感器几乎一致。这意味着你可以先在上面把算法框架跑通、把bug调完再上真机省下的时间和钱远比想象中多。这篇文章就把我自己的搭建过程完整写出来从环境准备、传感器模型配置到launch启动、Rviz可视化再到我踩过的坑。内容偏向实操每一步都有配置代码和参数说明无论是刚装好ROS还没跑过仿真的新手还是想给现有机器人模型加传感器的老手都能直接对着抄。1. 为什么要在Gazebo里做传感器仿真1.1 仿真能帮你省下几万块硬件钱一个能用的2D激光雷达入门级也要一千多带深度信息的kinect二手也要几百工业相机更不用说了一套下来小几千是打底的。更麻烦的是硬件接线和驱动不同厂家的驱动接口不一样有的用USB串口有的走网口有的还要装厂商SDK光是把传感器数据正常读到ROS里就要折腾半天。在Gazebo里做传感器仿真你只需要在URDF里加几段插件配置启动之后话题里就有数据流而且话题名称、消息类型和真实传感器保持一致。另一个容易被忽略的点是实验可重复性。真实环境里光照变化、行人走动、物体位置偏移都会干扰实验结果但在Gazebo里你可以把环境完全固定下来同一个传感器配置跑一百次结果都一样。做算法对比时这种可控性特别重要。比如我用仿真的laser数据测试代价地图参数跑到参数收敛再换真机直接省掉了反复试错的过程。1.2 这套方案为什么选Ubuntu16.04 ROS Kinetic Gazebo 7很多初学者会问为什么不装最新版ROS2和最新版Gazebo我的回答是ROS1的Kinetic Gazebo 7的组合是历史版本里资料最全、踩坑记录最多的一套搭配。你遇到问题搜索一下几乎都能找到答案因为当年无数人用过相同组合。而ROS2 新版Gazebo虽然功能更新但很多教程还停留在ROS1写法对新手来说迁移成本很高。顺便说一下版本对应关系。Ubuntu 16.04对应ROS Kinetic KameKinetic自带的是Gazebo 7.x。如果你装了ROS Kinetic的desktop-full版本gazebo_ros相关的基础包就已经在里面了不需要额外大动干戈。这套组合稳定性不错Gazebo 7对硬件要求也不高我当年在一台4GB内存的老笔记本上都能跑起来只是加载模型慢了一些。从这个角度说用这套组合做传感器仿真入门几乎是最合适的起点。2. 环境准备从零装起一次跑通2.1 安装ROS Kinetic与Gazebo的完整流程如果你已经装好ROS Kinetic可以直接跳过这一节。如果还没装推荐两种方式。一种是直接按官方Wiki操作配置source.list添加ROS密钥然后执行sudo apt update sudo apt install ros-kinetic-desktop-fulldesktop-full自带Gazebo、Rviz、rqt等常用工具装完这一步你的传感器仿真基础环境就已经有了。另有一种更省心的方式是使用网上流行的一键安装脚本比如“鱼香ROS一键安装”这种社区工具它会帮你把ROS、依赖、环境变量都配好。我个人对这种方式持中立态度如果网络环境不佳或者apt源有问题一键脚本确实能救命但我更推荐手动安装一遍因为配置过程中你对ROS的目录结构、环境变量、依赖关系会有更直观的理解后面出问题排查起来也更有头绪。装完后记得把ROS环境写入bashrcecho source /opt/ros/kinetic/setup.bash ~/.bashrc source ~/.bashrc这一步漏了的话之后每次打开终端ros命令都会提示找不到这是新手最常见的问题没有之一。2.2 获取gazebo_ros_pkgs相关包并验证环境刚才说了desktop-full会带上基本gazebo但为了确保gazebo_ros相关的插件和消息接口是完整的建议再显式安装一遍这几个包sudo apt install ros-kinetic-gazebo-ros ros-kinetic-gazebo-ros-pkgs ros-kinetic-gazebo-msgs ros-kinetic-gazebo-plugins这些包的作用分别是gazebo_ros提供Gazebo与ROS之间的桥接节点包括spawn_model、gzserver、gzclient的启动封装gazebo_ros_pkgs包含常用的传感器插件camera、laser、kinect等gazebo_msgs定义仿真的消息和服务类型。装完之后验证一下。先启动一个空世界roslaunch gazebo_ros empty_world.launch如果此时能看到一个灰色网格的空场景并且终端没有报错说明gazebo_ros的桥接正常。接着再开一个终端运行rostopic list正常情况下你至少能看到/clock和/rosout这两个话题。不要小看这一步很多人后面传感器数据看不到其实就是gazebo_ros没装好导致传感器插件根本没被加载。还有一个常见坑是模型加载缓慢Gazebo启动时会去模型库下载模型文件网络不好时卡在logo界面半天不动可以用清华镜像源或者手动设置GAZEBO_MODEL_PATH环境变量解决这个我在第6节会细说。3. 三种传感器模型配置详解3.1 camera仿真最基础的视觉传感器配置先写最简单的camera传感器。在URDF或者xacro文件里给一个link添加gazebo sensor标签类型写成camera。下面是我实际用过的配置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 far100/far /clip noise typegaussian mean0.0/mean stddev0.001/stddev /noise /camera plugin namecamera_controller filenamelibgazebo_ros_camera.so alwaysOntrue/alwaysOn updateRate0.0/updateRate cameraNamecamera1/cameraName imageTopicNameimage_raw/imageTopicName cameraInfoTopicNamecamera_info/cameraInfoTopicName frameNamecamera_link/frameName /plugin /sensor /gazebo解释几个关键参数。horizontal_fov是水平视场角单位是弧度1.3962634对应约80度这个值决定相机看到的范围宽度如果你要模拟广角相机就调大模拟长焦就调小。image里的width和height是分辨率分辨率越高数据量越大仿真越卡640x480是性能和效果比较折中的选择。clip的near和far是最近和最远可视距离单位米太远或者太近的物体会被裁剪掉。noise里的高斯噪声参数用来模拟真实相机的噪声算法调试初期我建议直接设成0后期再慢慢加否则你会分不清是算法问题还是传感器噪声问题。plugin里的cameraName决定命名空间比如设为camera1那么话题就是/camera1/image_raw和/camera1/camera_info。frameName是图像数据对应的坐标系ID在Rviz里显示点云或者做坐标变换时要用到必须和URDF里的link名称保持一致否则TF树对不上。3.2 kinect仿真深度与点云一步到位kinect和普通camera最大的区别在于它能同时输出RGB图像、深度图像和点云数据。在Gazebo里实现kinect仿真用的是openni_kinect插件我习惯写成这样gazebo referencekinect_link sensor typedepth namekinect_depth update_rate30/update_rate camera namekinect_camera horizontal_fov1.047197551/horizontal_fov image width640/width height480/height formatR8G8B8/format /image clip near0.3/near far10/far /clip /camera plugin namekinect_controller filenamelibgazebo_ros_openni_kinect.so alwaysOntrue/alwaysOn updateRate0.0/updateRate cameraNamekinect/cameraName imageTopicNamergb/image_raw/imageTopicName depthImageTopicNamedepth/image_raw/depthImageTopicName pointCloudTopicNamedepth/points/pointCloudTopicName cameraInfoTopicNamergb/camera_info/cameraInfoTopicName depthImageCameraInfoTopicNamedepth/camera_info/depthImageCameraInfoTopicName frameNamekinect_depth_frame/frameName pointCloudCutoff0.3/pointCloudCutoff pointCloudCutoffMax10.0/pointCloudCutoffMax distortionK10.0/distortionK1 distortionK20.0/distortionK2 distortionT10.0/distortionT1 distortionT20.0/distortionT2 /plugin /sensor /gazebo配置注意点sensor typedepth是必须的用普通camera类型驱动openni插件会报错。pointCloudCutoff和pointCloudCutoffMax控制点云的有效距离范围单位米这个直接影响点云质量太近的点噪声很大太远的点又稀疏。我在实际调试SLAM时通常把近端设到0.3米避免机器人自身结构反射产生的杂点进入点云。depth/points话题输出的是sensor_msgs/PointCloud2格式Rviz里直接添加PointCloud2显示项就能看到三维点云。kinect的坐标系比普通相机复杂一些因为它有RGB相机和深度相机两个光学中心仿真里一般会做简化统一用frameName指定的坐标系发布数据这样省去很多内外参标定的麻烦但也意味着你无法通过仿真去验证标定算法这一点心里要有数。3.3 lidar仿真2D激光雷达扫描配置2D激光雷达在机器人里是最常见的传感器底盘导航、建图、避障都靠它。Gazebo里用ray类型模拟雷达测距gazebo referencelaser_link sensor typeray namelaser pose0 0 0 0 0 0/pose visualizefalse/visualize update_rate10/update_rate ray scan horizontal samples720/samples resolution1/resolution min_angle-1.570796/min_angle max_angle1.570796/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 topicName/scan/topicName frameNamelaser_link/frameName /plugin /sensor /gazebo这里的数值对应真实雷达参数samples是每圈采样点数720表示一圈测720个点对应角度分辨率为0.5度min_angle和max_angle是扫描范围-1.570796到1.570796是弧度制的正负90度也就是180度雷达如果你用的是单线360度雷达就把范围改成-pi到pi。range里的min和max是最小和最大测量距离太小测不了近处物体太远则杂波多。visualize建议设成false因为在Gazebo界面里画射线会严重拖慢仿真速度如果你不想看debug射线的话。插件部分注意libgazebo_ros_laser.so对应2D laser另外还有一个libgazebo_ros_ray.so是更底层的实现如果你看到别人写的配置用的是ray插件别慌两者发布的话题类型是一样的。我在第4节的实际搭建里用的就是libgazebo_ros_laser.so它发布出去的话题类型是sensor_msgs/LaserScan可以直接给gmapping、cartographer这类建图算法用。4. 实战把传感器装进机器人并启动整套仿真4.1 创建工作空间并编写机器人URDF模型这一节我直接带你把整个流程跑一遍。先创建工作空间和功能包mkdir -p ~/robot_sim_ws/src cd ~/robot_sim_ws/src catkin_create_pkg robot_description urdf xacro gazebo_ros roscpp rospy sensor_msgs cd ~/robot_sim_ws catkin_make source devel/setup.bash功能包里依赖的urdf和xacro用于解析模型文件gazebo_ros用于启动仿真节点sensor_msgs提供传感器消息类型。接下来在robot_description包下建一个urdf目录写机器人模型文件。你不需要一个多么复杂的机器人底盘、两个驱动轮、一个支撑轮加上安装传感器的支架就够了。我以xacro为例写一个简化版底盘?xml version1.0? robot namesimple_robot xmlns:xacrohttp://www.ros.org/wiki/xacro link namebase_link visual geometrybox size0.4 0.3 0.1//geometry origin xyz0 0 0.05 rpy0 0 0/ /visual collision geometrybox size0.4 0.3 0.1//geometry /collision inertial mass value2.0/ inertia ixx0.01 ixy0 ixz0 iyy0.01 iyz0 izz0.01/ /inertial /link link namelaser_link visual geometrycylinder radius0.05 length0.04//geometry origin xyz0 0 0.02 rpy0 0 0/ /visual /link link namecamera_link visual geometrybox size0.03 0.05 0.05//geometry origin xyz0 0 0 rpy0 0 0/ /visual /link joint namelaser_joint typefixed parent linkbase_link/ child linklaser_link/ origin xyz0 0 0.12 rpy0 0 0/ /joint joint namecamera_joint typefixed parent linkbase_link/ child linkcamera_link/ origin xyz0.2 0 0.12 rpy0 -0.2 0/ /joint /robot这个模型里base_link是底盘主体laser_link是雷达安装点camera_link是相机安装点通过固定关节把它们连起来。关节的origin非常关键它决定了传感器在机器人上的实际位置。camera装在前面偏上雷达装在正上方这是最常见的布局既能让雷达看到全向障碍物又不会被机器人自身遮挡太多。不过实际运行时模型还要加入gazebo物理属性纯visual和collision不够。你需要为每个link补充inertial信息并添加gazebo reference标签来定义传感器插件。我在实际测试中还发现carrot的底座高度如果太高雷达就看不全近处障碍物高度设在10到20厘米之间导航效果最好。你可以在这一步就把上节写的三种传感器配置直接追加到对应link的gazebo标签中不要放到后面再改因为启动后修改模型要重启仿真非常浪费时间。4.2 编写launch文件一键启动Gazebo与传感器launch文件是整个流程的“总开关”。我的习惯是分成两个文件一个负责加载模型和启动仿真一个负责启动Rviz。先看第一个我在robot_description/launch下建一个gazebo_sensor.launchlaunch !-- 启动空世界 -- include file$(find gazebo_ros)/launch/empty_world.launch arg namepaused valuefalse/ arg nameuse_sim_time valuetrue/ arg namegui valuetrue/ arg nameheadless valuefalse/ arg namedebug valuefalse/ /include !-- 将机器人模型加载到参数服务器 -- param namerobot_description command$(find xacro)/xacro.py $(find robot_description)/urdf/robot.xacro/ !-- 在Gazebo中生成机器人模型 -- node namespawn_model pkggazebo_ros typespawn_model args-urdf -param robot_description -model robot -x 0 -y 0 -z 0.1/ !-- 启动joint状态发布器否则TF树缺少关节信息 -- node namejoint_state_publisher pkgjoint_state_publisher typejoint_state_publisher param name/use_sim_time valuetrue/ /node node namerobot_state_publisher pkgrobot_state_publisher typerobot_state_publisher param name/use_sim_time valuetrue/ /node /launch几点说明。use_sim_time设为true让ROS使用Gazebo的仿真时间这是仿真和真机最大的区别之一如果不设置很多传感器数据的时间戳会对不上。spawn_model的-model robot是模型在Gazebo里的名字不要和URDF的root link搞混可以随意起。-x -y -z是模型出生的位置z设0.1是为了让底盘离开地面一点点否则落地瞬间会抖一下。joint_state_publisher和robot_state_publisher负责发布TF变换。如果你有可动关节joint_state_publisher会读取joint_states话题来发布关节位置如果全是固定关节它也能工作。robot_state_publisher则读取robot_description和joint_states计算出每个link之间的坐标系变换。传感器数据到Rviz里能不能正常显示很大程度上依赖TF树是否完整所以这两个节点一定不能漏。4.3 用rostopic验证数据链路是否打通启动完launch后先别急着开Rviz先检查话题。运行rostopic list你会看到如下几类话题/clock为仿真时钟/gazebo/link_states为仿真状态/scan应该是激光话题/camera1/image_raw和/camera1/camera_info是相机话题/kinect/depth/points是点云话题。如果/scan没出现大概率是插件没被加载成功去回头看URDF里sensor标签有没有写错或者plugin的so文件名有没有拼错。确认话题存在之后再查看数据内容。比如激光rostopic echo -n1 /scan正常会看到一长串ranges数组数值是每个角度测到的障碍物距离单位米。相机话题可以这样看rqt_image_view /camera1/image_raw会弹出一个窗口显示仿真相机的画面。第一次看到这个画面的时候说明你的传感器仿真已经基本成功了这一步值得多停留一会儿。另外别忘了用rostopic hz /scan检查发布频率正常情况下接近update_rate设定的频率如果频率特别低说明你的仿真跑得太卡需要降低分辨率或者采样点数。5. Rviz可视化让传感器数据变成看得懂的图形5.1 Fixed Frame与坐标系设置在Rviz里的重要性启动Rviz可以简单粗暴地在终端敲rviz但我更推荐写一个独立的launch因为还要顺带加载保存好的显示配置launch node namerviz pkgrviz typerviz args-d $(find robot_description)/rviz/sensor_display.rviz/ /launch打开Rviz后第一件事就是设置左侧Displays面板里的Global Options——Fixed Frame。这个值一定要设成机器人模型里的一个link最常用的是base_link或odom千万别用map或者地球坐标系。很多新手打开Rviz看到点云和激光数据在屏幕上乱飞、闪来闪去第一反应是传感器坏了其实是Fixed Frame没设对。激光数据是以laser_link坐标系发布的Rviz要把这些坐标变换到Fixed Frame下显示如果TF树里没有对应的变换数据就会显示不出来或者出现在一个奇怪的位置。正确做法是先把Fixed Frame设成base_link此时激光话题和点云话题应该能在机器人模型周围正常显示然后再切换到odom或者map坐标系验证整个TF链。如果显示正常说明TF没问题如果数据消失去查4.2节的发布器是否在运行。5.2 三步可视化点云、图像与激光数据我整理了一套固定的显示配置直接照着加就行。第一步点云可视化。点击左下的Add按钮选择By topic标签页展开/kinect/depth/points话题选择PointCloud2点击OK。在PointCloud2的显示属性里把Size改成0.01这样点会变成小圆点而不是一个像素看起来更清晰。Color Transformer默认是Intensity如果没有强度信息就改成RGB8否则全是纯色点。点云是判断kinect传感器是否成功的关键能看到三维的桌子、墙壁轮廓说明深度数据链路是通的。第二步图像可视化。点云旁边的/camera1/image_raw话题选择Image添加Rviz会开一个子窗口实时显示图像。如果你想把图像和点云放在同一个布局里方便观察可以右键该显示项选择Change View Type把视图改成2D这样图像会铺满整个视图。图像显示正常后你可以用Rviz顶部的Measure工具测量画面里物体的像素大小为后续开发一个简单的视觉引导程序做参考。第三步激光可视化。Add添加/scan话题选择LaserScan。这里有个容易忽略的点Size和Style参数。激光每个点很小建议把Style从Points改成SquaresSize调整到0.05颜色选成随机的或按距离渐变。色带默认是按Intensity但雷达没有强度信息最好改成Distance这样近处红色、远处紫色一眼就能看出障碍物远近。若能正常看到一圈激光点云分布在机器人周围并且和Gazebo里的墙体轮廓吻合整套仿真链路就完整了。6. 常见坑与排查技巧速查6.1 RViz打不开、闪退或黑屏这个问题出现的频率非常高尤其是虚拟机里跑ROS的新手。Rviz本质是一个OpenGL程序虚拟机默认的3D加速支持很差直接导致窗口打不开、黑屏或者闪退。解决方法有两个一个是先安装mesa软件渲染库sudo apt install mesa-utils libgl1-mesa-glx libgl1-mesa-dri然后设置环境变量强制软件渲染export LIBGL_ALWAYS_SOFTWARE1 rviz第二个是给虚拟机开启3D加速。如果你用的是VirtualBox在设置-显示-屏幕里勾选“启用3D加速”显存调到128MB以上再重启虚拟机。实测下来软件渲染能解决90%的启动问题但性能会差一些如果模型复杂旋转视角会卡。如果硬件支持尽量用双系统跑仿真性能差距不是一点半点。6.2 传感器话题不存在或插件启动失败话题不存在90%是插件加载失败。排查思路按这个顺序来第一确认包的依赖是否完整检查有没有安装ros-kinetic-gazebo-ros-pkgs缺插件库的话Gazebo控制台会直接打印Failed to load plugin错误。第二确认URDF语法sensor标签必须闭合plugin的filename要写绝对路径或标准文件名比如libgazebo_ros_camera.so别写错中间少了一个下划线都会加载失败。第三确认模型是否真的出现在Gazebo世界里如果spawn_model之后Gazebo界面里什么都没有那传感器插件就算在URDF里写了也没用。排查的时候重点看启动Gazebo的那个终端输出。Gazebo会把插件加载日志打印在那里只要搜索Error或者Failed关键字基本能定位到问题。还有一种情况是模型加载到一半卡住原因是Gazebo要联网下载模型库网络不好就会卡死可以先下载gazebo model库压缩包解压放到~/.gazebo/models离线加载就好多了。6.3 仿真卡顿、数据频率上不去Gazebo仿真吃的是CPU传感器越多、分辨率越高、采样点越多CPU负担越大。如果你的雷达设了720个采样点相机又是640x48030fps再叠加多传感器四核CPU基本就满载了。优化优先级是这样的第一雷达的samples降到360或者180导航建图对分辨率的要求没那么苛刻第二调整相机update_rate从30降到15或者分辨率降到320x240第三给Gazebo开启GPU加速在启动参数里加arg namegui valuetrue配合export GAZEBO_GPU1部分显卡能明显减轻CPU负担。实测下来把雷达采样降到360、相机降到320x240之后仿真流畅度提升非常明显数据发布频率也能稳定在设定值附近。6.4 传感器数据坐标系对不上这个坑很隐蔽但一旦遇到就非常折磨人。表现是Rviz里激光点云和相机点云重叠不到一起明明在同一个位置却错位了。原因基本是frameName和URDF里的link名称不一致。比如URDF里雷达的link叫laser_link但插件里frameName写成laserTF树里就找不到对应的坐标系Rviz会把数据丢弃或者放到原点。解决办法是统一命名插件里的frameName严格等于URDF里的link名字或者通过static_transform_publisher额外发布一个虚拟坐标变换来解决。检查的时候用这条命令rosrun tf tf_echo base_link laser_link如果打印出来的transform一直在跳或者没有输出说明TF链路断了优先检查joint连接和state_publisher节点。7. 一些实际心得仿真跑通之后确实能明显感受到ROS开发的节奏快了起来。改模型、换传感器、调参数全部在代码层面完成不用去动一堆物理硬件。但我也想提醒一点仿真永远是仿真的它帮你验证的是逻辑不是物理。真机上摄像头曝光、雷达多径反射、轮子打滑这些问题仿真里很难完全复刻。所以我的建议是用仿真做算法开发和流程验证等一切稳定后再上真机做小范围测试两边配合起来才是最高效的方式。另外如果你跟我一样是自学的强烈建议把这一节的三种传感器全部跑一遍然后试着给它们加噪声、改参数、混合使用。比如把camera和laser数据做融合做目标检测或者用kinect点云做简单的障碍物聚类。这些实践看起来不起眼但能帮你把ROS的消息通信、TF坐标变换、可视化调试这些核心概念串联起来远比照着一本书从头背到尾管用。学完这套之后你再去看ROS2和最新版Gazebo的文档会发现很多东西是相通的迁移起来并不难。