3、Gazebo世界构建:创建包含静态障碍物(墙壁、箱子)的仿真世界,学习.world文件格式
各位同学,欢迎来到第三章。
上一章我们把机器人模型搭好了,但光有机器人不行啊,你得给它一个舞台。这个舞台就是Gazebo里的仿真世界。说白了,就是告诉Gazebo:地面长什么样,墙在哪,箱子放哪。
我个人习惯,在写代码之前,先想清楚这个世界要长什么样。你想想看,如果连障碍物位置都没想好,后面调避障参数就是瞎调。
3.1 什么是.world文件?
.world文件,就是Gazebo世界的描述文件。它用SDF(Simulation Description Format)格式写成,本质上就是XML。
我刚开始接触Gazebo时,以为.world文件很复杂。后来发现,它其实就是几大块拼在一起:
- 光照:太阳光、环境光,让世界亮起来
- 地面:机器人跑的地方
- 物理参数:重力、摩擦力这些
- 模型:静态障碍物、动态物体
嗯,就这么简单。但简单归简单,坑可不少。我曾经在一个项目里,因为.world文件里少写了一个标签,导致机器人直接穿透地面掉下去。那叫一个尴尬。
3.2 从零开始:写一个最简单的.world文件
我们先写一个最基础的版本,包含地面和光照。
<?xml version="1.0" ?>
<sdf version="1.6">
<world name="my_world">
<!-- 光照 -->
<include>
<uri>model://sun</uri>
</include>
<!-- 地面 -->
<include>
<uri>model://ground_plane</uri>
</include>
</world>
</sdf>
保存成 empty_world.world,然后用 gazebo empty_world.world 启动。你会看到一个空荡荡的世界,只有地面和阳光。
3.3 添加静态障碍物:墙壁
空世界没意思,我们得加墙。在Gazebo里,静态障碍物用 <model> 标签定义,并且要设置 <static>true</static>。
为什么是静态?因为墙壁不会动,你也不希望机器人一撞就把墙撞飞了吧?
<model name="wall_01">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<box>
<size>0.2 3.0 1.0</size>
</box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box>
<size>0.2 3.0 1.0</size>
</box>
</geometry>
<material>
<ambient>0.8 0.2 0.2 1.0</ambient>
</material>
</visual>
</link>
<pose>1.5 0.0 0.5 0 0 0</pose>
</model>
这段代码创建了一面红色的墙,长3米、高1米、厚0.2米,放在(1.5, 0.0)的位置。
注意看,我同时定义了 <collision> 和 <visual>。这是Gazebo的一个关键点:碰撞模型和视觉模型可以不一样。比如你可以用一个简单的盒子做碰撞检测,但显示一个复杂的3D模型。不过对于墙壁,两者保持一致就行。
3.4 添加箱子:更复杂的障碍物
箱子比墙壁小,但用法一样。我们加几个不同颜色的箱子,模拟室内场景。
<model name="box_01">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<box>
<size>0.5 0.5 0.5</size>
</box>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box>
<size>0.5 0.5 0.5</size>
</box>
</geometry>
<material>
<ambient>0.2 0.8 0.2 1.0</ambient>
</material>
</visual>
</link>
<pose>-1.0 1.5 0.25 0 0 0</pose>
</model>
这是一个绿色箱子,边长0.5米,放在(-1.0, 1.5)的位置。高度0.25是因为箱子中心在0.25米处,底部刚好贴地。
你可以复制粘贴,改改颜色和位置,多放几个箱子。我个人习惯用不同颜色区分障碍物的重要性——红色是必须避开的,绿色是次要的。
3.5 完整的世界布局
下面是一个完整的示例,包含两面墙和三个箱子。这个布局我经常用在避障测试中。
<?xml version="1.0" ?>
<sdf version="1.6">
<world name="obstacle_world">
<include>
<uri>model://sun</uri>
</include>
<include>
<uri>model://ground_plane</uri>
</include>
<!-- 墙壁1:左侧长墙 -->
<model name="wall_left">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry><box><size>0.2 5.0 1.0</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>0.2 5.0 1.0</size></box></geometry>
<material><ambient>0.8 0.2 0.2 1.0</ambient></material>
</visual>
</link>
<pose>-2.5 0.0 0.5 0 0 0</pose>
</model>
<!-- 墙壁2:前方短墙 -->
<model name="wall_front">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry><box><size>3.0 0.2 1.0</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>3.0 0.2 1.0</size></box></geometry>
<material><ambient>0.8 0.2 0.2 1.0</ambient></material>
</visual>
</link>
<pose>0.0 2.5 0.5 0 0 0</pose>
</model>
<!-- 箱子1 -->
<model name="box_01">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry><box><size>0.6 0.6 0.6</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>0.6 0.6 0.6</size></box></geometry>
<material><ambient>0.2 0.8 0.2 1.0</ambient></material>
</visual>
</link>
<pose>1.0 1.0 0.3 0 0 0</pose>
</model>
<!-- 箱子2 -->
<model name="box_02">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry><box><size>0.4 0.4 0.4</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>0.4 0.4 0.4</size></box></geometry>
<material><ambient>0.2 0.2 0.8 1.0</ambient></material>
</visual>
</link>
<pose>-1.0 -1.5 0.2 0 0 0</pose>
</model>
<!-- 箱子3 -->
<model name="box_03">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry><box><size>0.8 0.3 0.5</size></box></geometry>
</collision>
<visual name="visual">
<geometry><box><size>0.8 0.3 0.5</size></box></geometry>
<material><ambient>0.8 0.8 0.2 1.0</ambient></material>
</visual>
</link>
<pose>1.5 -1.0 0.25 0 0 0.3</pose>
</model>
</world>
</sdf>
这个世界的布局大致如下:
| 物体 | 位置 (x, y) | 尺寸 (长, 宽, 高) | 颜色 |
|---|---|---|---|
| 左墙 | (-2.5, 0.0) | 0.2 x 5.0 x 1.0 | 红色 |
| 前墙 | (0.0, 2.5) | 3.0 x 0.2 x 1.0 | 红色 |
| 箱子1 | (1.0, 1.0) | 0.6 x 0.6 x 0.6 | 绿色 |
| 箱子2 | (-1.0, -1.5) | 0.4 x 0.4 x 0.4 | 蓝色 |
| 箱子3 | (1.5, -1.0) | 0.8 x 0.3 x 0.5 | 黄色 |
注意箱子3的朝向:pose 最后一项是0.3弧度,大约17度。这意味着箱子是斜着放的。为什么?因为实际场景中,障碍物不会总是整整齐齐的。斜着放更能考验避障算法的鲁棒性。
3.6 世界结构图
下面这张图展示了.world文件的核心结构,以及各个元素之间的关系。我建议你把它保存下来,写文件时对照着看。
3.7 调试技巧:如何验证世界文件
写完了.world文件,怎么知道对不对?我一般用三步验证法:
- 语法检查:用
gazebo --verbose my_world.world启动,看终端有没有报错。如果有红色错误信息,通常是XML标签没闭合。 - 视觉检查:启动后,用鼠标旋转视角,从各个方向看障碍物位置是否合理。我习惯从正上方俯视,确认布局和设计一致。
- 碰撞检查:手动拖一个机器人模型进世界,让它撞一下墙。如果穿模了,说明碰撞模型有问题。
好了,这一章的内容就到这里。你现在应该能自己写一个包含墙壁和箱子的.world文件了。下一章我们会把机器人放进这个世界,开始真正的避障仿真。
可可精品资料库