ROS 2 に関して筆者は以下の本で学びました。ROS1 との比較を含めかなりわかりやすく ROS 2 について書かれており、おすすめの一冊です。
前回の記事で、自分で作成した map 上でロボットを動かすシミュレーションに成功しました。
ただ、この方法の問題点として、作成した map によっては .world ファイルの容量が重くなってしまい(試しに作成した .world ファイルは 10,000 行程度)、 Gazebo で開くのに時間がかかることが挙げられます。この問題を解消するため、.pgm ファイルを .stl ファイルに変換し、.stl ファイルを Gazebo で開けるようにします。
過去記事
- Nav2 講座 1: docker 上に ROS 2 環境を構築し TurtleBot simulation を動かす
- Nav2 講座 2: Rust で ROS 2 ノードを作る: footprint の動的変更
- Nav2 講座 3: nav2_bringup の設定をカスタマイズする準備
- Nav2 講座 4: 自分で作成したマップを使って TurtleBot シミュレーションをする
PC 環境
- Thinkpad X1 Intel Core i7
- Ubuntu 24.04
とりあえず実行してみたい人向け
とりあえず動かしてみたい方は以下を実行してみてください。
事前準備
事前準備として以下を実行してください。
$ git clone git@github.com:remmaTech12/ros2_nav2.git
$ cd ros2_nav2
$ git checkout article_8211
$ docker compose up -d --build
$ xhost +
$ docker exec -it ros2_nav2 bash
/workspace$ export TURTLEBOT3_MODEL=waffle
/workspace$ export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:/opt/ros/$ROS_DISTRO/share/turtlebot3_gazebo/models
/workspace$ ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py ## Takes about 5 min.
実行
以下により、.stl ファイルをベースとした map 上でのシミュレーションができます。
$ docker exec -it ros2_nav2 bash
/workspace$ export TURTLEBOT3_MODEL=waffle
/workspace$ export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:/opt/ros/$ROS_DISTRO/share/turtlebot3_gazebo/models
/workspace$ colcon build
/workspace$ source install/setup.bash
/workspace$ ros2 launch custom_nav2_bringup tb3_simulation_launch.py headless:=False

.pgm ファイルから .stl ファイルへの変換
.pgm ファイルから .stl ファイルに変換する python スクリプト convert_pgm_to_stl.py
を ChatGPT に書いてもらいました。
import numpy as np
from PIL import Image
import trimesh
from trimesh.creation import box
from trimesh.scene import Scene
# Parameters
input_file = "maze.pgm"
output_file = "maze.stl"
resolution = 0.05
wall_height = 1.0
# Load image
image = Image.open(input_file).convert("L")
image = np.array(image, dtype=np.uint8)
height, width = image.shape
scene = Scene()
# Threshold for black (wall)
black_threshold = 128
for y in range(height):
for x in range(width):
if image[y, x] < black_threshold:
px = (x - width / 2) * resolution
py = (height / 2 - y) * resolution
pz = wall_height / 2
wall = box(extents=[resolution, resolution, wall_height])
wall.apply_translation([px, py, pz])
scene.add_geometry(wall)
scene.export(output_file)
print(output_file + " created!")
スクリプトの簡単な説明
入力・出力ファイルを変更する場合は、こちらを変更してください。
# Parameters
input_file = "maze.pgm"
output_file = "maze.stl"
画素が黒い場合のみ壁を作ります。
if image[y, x] < black_threshold:
以下は画像座標系から Gazebo 座標系(メートル単位)への座標変換です。
px = (x - width / 2) * resolution
py = (height / 2 - y) * resolution
変換実行
python スクリプトを実行し、.stl ファイルを生成します。
$ python3 convert_pgm_to_stl.py
maze.stl created!
変換後の .stl ファイルは FreeCAD で表示できます。
.world ファイル
.world ファイルは以下です。
<?xml version="1.0" ?>
<sdf version="1.6">
<world name="maze">
<include>
<uri>model://ground_plane</uri>
</include>
<include>
<uri>model://sun</uri>
</include>
<gui fullscreen="0">
<camera name="user_camera">
<pose frame="">0 0 10 0 1.570796 0</pose>
<view_controller>orbit</view_controller>
<projection_type>perspective</projection_type>
</camera>
</gui>
<model name="maze">
<static>true</static>
<pose>0 0 0 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<mesh>
<uri>file:///workspace/src/custom_nav2_bringup/maps/maze.stl</uri>
</mesh>
</geometry>
</collision>
<visual name="visual">
<geometry>
<mesh>
<uri>file:///workspace/src/custom_nav2_bringup/maps/maze.stl</uri>
</mesh>
</geometry>
</visual>
</link>
</model>
</world>
</sdf>
前回記事でもともと 10,000 行くらいあったファイルが 43 行になり、とてもすっきりしました。
別の .stl ファイルを使いたい場合は以下の部分を変更してください。collision(物理シミュレーション用)と visual(見た目描画用)の両方を変更することに注意してください。
<uri>file:///workspace/src/custom_nav2_bringup/maps/maze.stl</uri>
.launch ファイル
.launch ファイルの変更は非常にシンプルで、今回は maze.world
に代わって maze_stl.world
という .world ファイルを使用するので一行変更するだけです。
- default_value=os.path.join(bringup_dir, 'worlds', 'maze.world'),
+ default_value=os.path.join(bringup_dir, 'worlds', 'maze_stl.world'),
実行コマンド
実行する場合はこちらを参照ください。
まとめ
.stl ファイルを使うことで .world ファイルの記述がとてもすっきりしました!!
コメント