Skip to content

传感器

注意: 本教程是 移动机器人教程 的延续。

本教程介绍如何向机器人和世界中的其他模型添加传感器,涉及三种传感器: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)以四元数形式输出机器人的 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 的数据,在一个终端中运行世界,然后按下播放按钮:

gz sim sensor_tutorial.sdf

在另一个终端中,运行:

gz topic -e -t /imu

最后一条命令监听通过 /imu 话题发送的消息。IMU 数据即上文提到的 orientation、angular_velocity 和 linear_acceleration,输出格式如下:

IMU 消息

使用键盘上键向前移动机器人。注意先启用 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} 使机器人停止。

与其让机器人接触墙壁(可能造成损坏),不如使用激光雷达来替代接触传感器。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 节点。完整的演示节点可从 此处 下载。

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/ 目录:

Terminal window
mkdir build
cd build

运行 cmake 并构建代码:

Terminal window
cmake ..
make lidar_node

在终端 1 中运行节点:

Terminal window
./build/lidar_node

在终端 2 中运行世界:

Terminal window
gz sim sensor_tutorial.sdf

可以看到机器人向前移动。接近墙壁时开始左转,直到安全后再继续前进(请务必点击左下角的播放按钮让机器人开始移动)。

与其在两个不同终端中分别运行两个任务,不如创建一个启动文件,同时运行 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,然后使用以下命令运行:

Terminal window
gz launch sensor_launch.gzlaunch

点击播放按钮启动仿真。此时机器人会移动并自动避开墙壁。

要为仿真添加更多内容,请阅读 下一个教程,了解如何向世界添加角色(Actor)。

本教程的视频演示可在 YouTube 频道观看:Gazebo tutorials: Sensors