传感器
注意: 本教程是 移动机器人教程 的延续。
本教程介绍如何向机器人和世界中的其他模型添加传感器,涉及三种传感器:IMU 传感器、接触传感器和激光雷达传感器,同时介绍如何使用 gz launch 仅用一个文件启动多个任务。
本教程涉及的所有插件的最终世界文件可在 此处 查看。gz-sim/examples/worlds 目录中还有大量包含各类传感器和执行器的世界示例。完整的传感器集合可在 gz-sensors 库 中查看。如使用 ROS,可在 此处 查看这些示例的启动和桥接配置。
如果 SDF 文件之前没有包含任何插件,直接添加新插件时默认插件不会被加载。在添加传感器之前,请确保在世界中添加一些常用的默认插件,以便正常使用 GZ GUI:
<sdf version='1.9'> <world name='demo'> <plugin filename="gz-sim-physics-system" name="gz::sim::systems::Physics"> </plugin> <plugin filename="gz-sim-scene-broadcaster-system" name="gz::sim::systems::SceneBroadcaster"> </plugin>
<!-- ... -->IMU 传感器
Section titled “IMU 传感器”惯性测量单元(IMU)以四元数形式输出机器人的 orientation(方向),三个轴(X, Y, Z)上的 angular_velocity(角速度),以及三个轴上的 linear_acceleration(线性加速度)。下面基于 moving_robot.sdf 世界进行修改:创建一个新文件 sensor_tutorial.sdf,将 moving_robot.sdf 中的内容复制进去。要定义 IMU 传感器,在 <world> 标签下添加以下代码:
<plugin filename="gz-sim-imu-system" name="gz::sim::systems::Imu"></plugin>此代码定义了世界中使用的 IMU 传感器插件。接下来,按如下方式将 IMU 传感器添加到机器人上:
<sensor name="imu_sensor" type="imu"> <always_on>1</always_on> <update_rate>1</update_rate> <visualize>true</visualize> <topic>imu</topic></sensor>传感器通常添加到模型的某个 link 上,这里将其添加到 chassis link 下。
各标签含义如下:
<always_on>:若为 true,传感器将始终按照更新速率更新。<update_rate>:生成传感器数据的频率。<visualize>:若为 true,传感器将在 GUI 中可视化。<topic>:发布数据的话题名称。
注意: 并非所有标签都被所有传感器支持。
读取 IMU 数据
Section titled “读取 IMU 数据”要读取 IMU 的数据,在一个终端中运行世界,然后按下播放按钮:
gz sim sensor_tutorial.sdf
在另一个终端中,运行:
gz topic -e -t /imu
最后一条命令监听通过 /imu 话题发送的消息。IMU 数据即上文提到的 orientation、angular_velocity 和 linear_acceleration,输出格式如下:

使用键盘上键向前移动机器人。注意先启用 Key Publisher 插件,移动后应能看到传感器值发生变化。
下面介绍另一种传感器:接触传感器。顾名思义,该传感器在接触到其他物体时会发出信号。接下来构建一个障碍物(墙壁)并为其添加接触传感器,这样当机器人撞到障碍物时会立即停止,避免损坏。首先构建障碍物:
<model name='wall'> <static>true</static> <pose>5 0 0 0 0 0</pose><!--pose relative to the world--> <link name='box'> <visual name='visual'> <geometry> <box> <size>0.5 10.0 2.0</size> </box> </geometry> <!--let's add color to our link--> <material> <ambient>0.0 0.0 1.0 1</ambient> <diffuse>0.0 0.0 1.0 1</diffuse> <specular>0.0 0.0 1.0 1</specular> </material> </visual> <collision name='collision'> <geometry> <box> <size>0.5 10.0 2.0</size> </box> </geometry> </collision> </link></model>这只是一个带有一个盒形 link 的简单模型。可参考 搭建机器人教程 学习如何构建模型。
现在运行世界,确保墙壁在仿真中正确出现:

接下来为墙壁添加接触传感器。与 IMU 传感器一样,首先定义 Contact 传感器插件:
<plugin filename="gz-sim-contact-system" name="gz::sim::systems::Contact"></plugin>现在可以将 contact 传感器添加到 wall 模型的 box link 上:
<sensor name='sensor_contact' type='contact'> <contact> <collision>collision</collision> </contact></sensor><sensor> 的定义很简单,只需指定传感器的 name 和 type。在 collision 标签中指定 box link 的碰撞体名称,即 collision。
还需要在 wall 模型下添加 TouchPlugin:
<plugin filename="gz-sim-touchplugin-system" name="gz::sim::systems::TouchPlugin"> <target>vehicle_blue</target> <namespace>wall</namespace> <time>0.001</time> <enabled>true</enabled></plugin>当墙壁被触碰时,TouchPlugin 会发布一条消息。插件的标签含义如下:
<target>:将与墙壁发生接触的目标物体,本例中是vehicle_blue。<namespace>:指定话题的命名空间。当机器人撞到墙壁时,会向/wall/touched话题发送消息。
在一个终端中运行世界:
gz sim sensor_tutorial.sdf
在另一个终端中,监听 /wall/touched 话题:
gz topic -e -t /wall/touched
使用键盘方向键控制机器人向前驶向墙壁。确保已点击播放按钮启动仿真,然后点击插件下拉列表(竖排省略号图标)并选择“Key Publisher”来启用 Key Publisher 插件。
当撞到墙壁时,在运行 gz topic -e -t /wall/touched 的终端中应能看到 data: true 消息。
接下来使用 TriggeredPublisher 插件让机器人在撞到墙壁时停止:
<plugin filename="gz-sim-triggered-publisher-system" name="gz::sim::systems::TriggeredPublisher"> <input type="gz.msgs.Boolean" topic="/wall/touched"> <match>data: true</match> </input> <output type="gz.msgs.Twist" topic="/cmd_vel"> linear: {x: 0.0}, angular: {z: 0.0} </output></plugin>如 移动机器人教程 所述,可以根据收到的输入发布输出。因此,当在 /wall/touched 话题上收到 data: true 时,就发布 linear: {x: 0.0}, angular: {z: 0.0} 使机器人停止。
激光雷达传感器
Section titled “激光雷达传感器”与其让机器人接触墙壁(可能造成损坏),不如使用激光雷达来替代接触传感器。Lidar 是“light detection and ranging”(光探测与测距)的缩写,可帮助检测机器人周围的障碍物。这里用它来测量机器人与墙壁之间的距离。
首先,创建一个坐标系(frame)来固定激光雷达。该代码应添加在 vehicle_blue 的 <model> 标签内,因为激光雷达坐标系连接到机器人的 chassis 上:
<frame name="lidar_frame" attached_to='chassis'> <pose>0.8 0 0.5 0 0 0</pose></frame>然后在 <world> 标签下添加此插件,以启用激光雷达传感器:
<plugin filename="gz-sim-sensors-system" name="gz::sim::systems::Sensors"> <render_engine>ogre2</render_engine> </plugin>在 chassis link 下添加激光雷达传感器:
<sensor name='gpu_lidar' type='gpu_lidar'>" <pose relative_to='lidar_frame'>0 0 0 0 0 0</pose> <topic>lidar</topic> <update_rate>10</update_rate> <ray> <scan> <horizontal> <samples>640</samples> <resolution>1</resolution> <min_angle>-1.396263</min_angle> <max_angle>1.396263</max_angle> </horizontal> <vertical> <samples>1</samples> <resolution>0.01</resolution> <min_angle>0</min_angle> <max_angle>0</max_angle> </vertical> </scan> <range> <min>0.08</min> <max>10.0</max> <resolution>0.01</resolution> </range> </ray> <always_on>1</always_on> <visualize>true</visualize></sensor>- 首先定义传感器的
name和type,然后定义其相对于lidar_frame的<pose>。 - 在
<topic>中指定发布激光雷达数据的话题。 <update_rate>是生成激光雷达数据的频率,本例中为10 Hz,即每0.1 秒一次。- 在
<horizontal>和<vertical>标签下,分别定义水平和垂直激光射线的属性。 <samples>是每次完整激光扫描周期生成的模拟激光射线数量。<resolution>:该数字乘以samples即可确定距离数据点的数量。<min_angle>和<max_angle>是生成射线的角度范围。- 在
<range>下,定义每条模拟射线的测距属性。<min>和<max>定义每条激光射线的最小和最大测距距离。- 这里的
<resolution>标签定义每条激光射线的线性分辨率。
<always_on>:若为 true,传感器将始终根据<update_rate>更新。<visualize>:若为 true,传感器将在 GUI 中可视化。
现在运行世界,点击左下角的播放按钮:
gz sim sensor_tutorial.sdf
查看 /lidar 话题上的激光雷达消息,特别是 ranges 数据:
gz topic -e -t /lidar
激光雷达消息具有以下属性:
message LaserScan{ Header header = 1;
string frame = 2; Pose world_pose = 3; double angle_min = 4; double angle_max = 5; double angle_step = 6; double range_min = 7; double range_max = 8; uint32 count = 9; double vertical_angle_min = 10; double vertical_angle_max = 11; double vertical_angle_step = 12; uint32 vertical_count = 13;
repeated double ranges = 14; repeated double intensities = 15;}现在机器人上已安装激光雷达,可以利用 ranges 数据让机器人避免撞墙。为此,需要编写一个简短的 C++ 程序,监听传感器数据并向机器人发送速度命令。该程序称为节点(node)。下面构建一个订阅 /lidar 话题并读取其数据的节点。请参考这个 教程 了解如何构建 publisher 和 subscriber 节点。完整的演示节点可从 此处 下载。
lidar_node 节点
Section titled “lidar_node 节点”std::string topic_pub = "/cmd_vel";gz::transport::Node node;auto pub = node.Advertise<gz::msgs::Twist>(topic_pub);声明一个向 cmd_vel 话题发布消息的 node,然后在该节点上注册发布者(advertise)。
void cb(const gz::msgs::LaserScan &_msg){ gz::msgs::Twist data;
bool allMore = true; for (int i = 0; i < _msg.ranges_size(); i++) { if (_msg.ranges(i) < 1.0) { allMore = false; break; } } if (allMore) //if all bigger than one { data.mutable_linear()->set_x(0.5); data.mutable_angular()->set_z(0.0); } else { data.mutable_linear()->set_x(0.0); data.mutable_angular()->set_z(0.5); } pub.Publish(data);}在回调函数内部,检查所有射线的测距值是否都大于 1.0。若是,则发布消息让机器人向前移动;否则让机器人旋转。
int main(int argc, char **argv){ std::string topic_sub = "/lidar"; // Subscribe to a topic by registering a callback. if (!node.Subscribe(topic_sub, cb)) { std::cerr << "Error subscribing to topic [" << topic_sub << "]" << std::endl; return -1; }
// Zzzzzz. gz::transport::waitForShutdown();
return 0;}在 main 函数中,订阅 lidar 话题,然后等待节点关闭。
下载 CMakeLists.txt,在 lidar_node 所在的同一目录下创建 build/ 目录:
mkdir buildcd build运行 cmake 并构建代码:
cmake ..make lidar_node在终端 1 中运行节点:
./build/lidar_node在终端 2 中运行世界:
gz sim sensor_tutorial.sdf可以看到机器人向前移动。接近墙壁时开始左转,直到安全后再继续前进(请务必点击左下角的播放按钮让机器人开始移动)。
Gazebo 启动文件
Section titled “Gazebo 启动文件”与其在两个不同终端中分别运行两个任务,不如创建一个启动文件,同时运行 sensor_world 和 lidar_node。打开文本编辑器,添加以下代码:
<?xml version='1.0'?><gz version='1.0'> <executable name='sensor-world'> <command>gz sim sensor_tutorial.sdf</command> </executable>
<executable name='lidar_node'> <command>./build/lidar_node</command> </executable>
</gz>启动文件是一个 XML 文件,只需在 <executable> 标签下定义要运行的命令。
第一个命令是 gz sim sensor_tutorial.sdf,用于启动世界。
第二个命令是 ./build/lidar_node,用于运行 lidar_node。
将文件保存为 sensor_launch.gzlaunch,然后使用以下命令运行:
gz launch sensor_launch.gzlaunch点击播放按钮启动仿真。此时机器人会移动并自动避开墙壁。
要为仿真添加更多内容,请阅读 下一个教程,了解如何向世界添加角色(Actor)。
本教程的视频演示可在 YouTube 频道观看:Gazebo tutorials: Sensors