← Aung Kaung Myat/Walkthroughsプロジェクト解説
ROS 2 Humble · workspaceワークスペース moveit_pickplace_demo

Panda Pick-and-Place Pipeline

A ROS 2 Humble workspace in which a Franka Panda clears seven red balls into a box using only what a simulated camera saw. An OpenCV node finds the balls, a C++ node drops what the arm cannot reach, and a state machine grasps only what survived — 7 of 7 placed, on mock hardware.

ROS 2 Humbleのワークスペースで、Franka Pandaが模擬カメラの見たものだけを頼りに赤いボール7個を箱へ片付けます。OpenCVのnodeがボールを検出し、C++のnodeが届かない対象を除外し、state machineは残ったものだけを掴みます(mock hardware上で7個中7個を配置)。

ROS 2 HumbleMoveIt 2Python 3.10 C++17OpenCVros2_control (mock) RViz2OMPL / RRTConnect
Packagesパッケージ数
2 (Python + C++)
Nodes launched起動node数
11
Hardware needed必要なハードウェア
None — mock不要(mock)
Balls per run1回あたりのボール
7 of 7 placed7個中7個を配置
Source linesソース行数
≈1 600
00

At a glance要点

The whole chain in one picture — what each node does, which topic carries the data, and the result — before any detail.全体の流れを1枚で。各nodeの役割、データを運ぶtopic、そして結果。詳細はその後で。

Perception drives motion — five nodes, one feedback loop認識が動作を決める — 5つのnodeと1つのフィードバックループ
Each lane is one node; the labels on the arrows are the ROS 2 topics between them. The state machine subscribes to the validator's output, never to the ground truth.各レーンが1つのnode。矢印のラベルはnode間のROS 2 topicです。state machineがsubscribeするのはvalidatorの出力であって、真値ではありません。
cameraカメラ python · simulatedpython・模擬 camera_simulator (python): renders a 640×480 BGR top-down frame at 10 Hz from the ground-truth ball layout camera_simulator 640×480 BGR @ 10 Hz · draws the truth640×480 BGR・10 Hz・真値を描画 /camera/image_raw · Image — /camera/camera_info · CameraInfo /camera/image_raw · Image/camera/camera_info · CameraInfo OpenCV python vision_detector (python): HSV red mask (two hue windows), contours ≥ 400 px², moment centroid, pinhole back-projection onto the table plane z = 0 vision_detector HSV red mask → contours → centroid → z = 0HSV赤マスク → 輪郭 → 重心 → z = 0 /detected_objects · MarkerArray — one sphere marker per red blob /detected_objects · MarkerArray one sphere marker per red blob赤い領域1つにつき球marker 1つ reach filter到達判定 c++17 workspace_validator (C++17): keeps detections inside the reachable cylinder (r 0.18–0.82 m, z −0.05–0.98 m), rejects the singularity column r < 0.08 m workspace_validator cylinder r 0.18–0.82 m · drops r < 0.08 m円筒 r 0.18–0.82 m・r < 0.08 m は除外 /validated_targets · PoseArray — survivors only, rejections logged /validated_targets · PoseArray survivors only, rejections logged通過したもののみ・除外はログに記録 state machine python smart_pick_place (python): IDLE → GRASPING → PLACING → DONE; subscribes only to /validated_targets, never to the truth. 7 of 7 balls placed smart_pick_place nearest validated target → grasp → place最寄りの検証済み対象 → 把持 → 配置 /compute_cartesian_path (service) — /move_action (action, fallback) /compute_cartesian_path (service) /move_action (action, fallback)/move_action(action・fallback) MoveIt 2 OMPL · ros2_controlOMPL・ros2_control move_group (MoveIt 2): Cartesian path accepted at fraction ≥ 0.95, otherwise OMPL RRTConnect; executes on ros2_control mock_components move_group Cartesian ≥ 0.95 → OMPL fallback · mock HWCartesian ≥ 0.95 → OMPL fallback・mock駆動 /scene_object_updates · String — remove:ball_n after each grasp; the camera stops drawing that ball, so the next frame has one ball fewer on grasp: remove:ball_n把持後: remove:ball_n /scene_object_updates 7 / 7 balls placed · 11 nodes · 2 packages (Python + C++) 7個中7個を配置・node 11個・パッケージ2個(Python + C++) ≈1 600 source lines · mock ros2_control, no hardware ソース約1 600行・ros2_controlはmock、実機なし
  • 01Perception drives motion — 7 of 7 balls placed. The state machine subscribes only to /validated_targets; the ground-truth layout exists only to draw the 640×480 camera frame at 10 Hz.認識が動作を決める — 7個中7個を配置。state machineは/validated_targetsだけをsubscribeし、真値の配置は640×480のカメラ画像を10 Hzで描くためだけに存在します。
  • 02Two packages, 11 nodes, ≈1 600 lines. Python for the camera, the HSV detector and the state machine; C++17 for the per-frame reachability filter (cylinder r 0.18–0.82 m, singularity column r < 0.08 m rejected).パッケージ2個、node 11個、約1 600行。カメラ・HSV検出器・state machineはPython、フレームごとの到達可能性フィルタはC++17(円筒 r 0.18–0.82 m、特異点の柱 r < 0.08 m は除外)。
  • 03Straight-line motion first, OMPL as fallback. A Cartesian path is accepted at fraction ≥ 0.95, otherwise RRTConnect; the Cartesian trajectory is retimed by hand (scale 0.5) because Humble's service has no velocity scaling.まず直線動作、OMPLはfallback。Cartesian pathはfraction ≥ 0.95で採用し、それ以外はRRTConnect。Humbleのserviceに速度スケーリングがないため、Cartesian軌道は自前でretiming(scale 0.5)。
  • 04Every number in two YAML files, 15 contract tests on the one file that replaces cv_bridge: exact round-trip, padded and zero stride, truncated buffer, wrong encoding.全数値は2つのYAMLに、契約テストは15個。対象はcv_bridgeを置き換えた唯一のファイルで、完全な往復変換、パディング付き・0のストライド、途中で切れたバッファ、誤ったエンコーディングを確認。
  • 05No hardware, no sensor realism, no depth. Images are drawn, not captured; positions come from a z = 0 plane assumption; mock_components returns the commanded joints. HSV thresholding works because the image is perfect.実機なし、センサーのリアリティなし、深度なし。画像は撮影ではなく描画、位置はz = 0の平面仮定、mock_componentsは指令値をそのまま返します。HSVの閾値処理が動くのは画像が完璧だからです。
01

In plain words概要

What the system does, and the one design choice that makes it more than a scripted animation.

システムが何をするのか、そして単なる台本通りのアニメーションで終わらせないための設計判断です。

Imagine a robot arm bolted to a table. Seven red balls are scattered in front of it and there is an orange box off to one side. The job is: pick up every ball and put it in the box, without a human telling the arm where anything is.

テーブルに固定されたロボットアームを想像してください。目の前に赤いボールが7個散らばり、横にはオレンジ色の箱があります。 課題は、どこに何があるかを人がアームに教えることなく、すべてのボールを拾って箱に入れることです。

The naive way to build this is to write the seven ball positions into the code and command the arm to those coordinates. That would work, and it would prove nothing — the arm would perform the same dance whether the balls were there or not. This project deliberately does not do that.

素朴な作り方は、7個のボールの位置をコードに書き込み、その座標へアームを動かすことです。それでも動きはしますが、何の証明にもなりません。 ボールがあってもなくても、アームは同じ動きを繰り返すだけだからです。このプロジェクトでは、あえてその方法を取っていません。

Instead the positions travel a full loop. A fake camera draws a picture of the table. A separate program looks at that picture, finds the red blobs, and converts pixel coordinates back into metres. A third program (written in C++) checks whether the arm's joints can physically reach each of those metre coordinates and drops the ones that fail. Only what comes out of the far end of that chain is ever grasped. The arm literally does not know where the balls "really" are.

代わりに、位置情報はループを一周します。疑似カメラがテーブルの画像を描画します。別のプログラムがその画像から赤い領域を見つけ、 ピクセル座標をメートル単位に変換します。3つ目のプログラム(C++製)が、アームの関節でその各座標に物理的に届くかを確認し、届かないものを除外します。 掴む対象は、この流れの最後まで残ったものだけです。アームはボールの「本当の」位置を文字どおり知りません。

The one sentence that describes the architectureアーキテクチャを一文で表すと

Perception drives motion — the ground-truth ball positions exist only to draw the picture, and the state machine subscribes to the validator's output, never to the truth.

認識が動作を決めます。ボールの真の位置は画像を描くためだけに存在し、state machineがsubscribeするのはvalidatorの出力であって、真値ではありません。

Why a "fake" camera counts「偽物」のカメラでも意味がある理由

The camera node is not a real driver, it is an OpenCV drawing routine: grey background, grid lines, an orange rectangle for the box, red filled circles for the balls. But it publishes on the exact same ROS 2 topic type a real camera driver uses (sensor_msgs/Image) with real intrinsics on /camera/camera_info. So every node downstream of it is the code you would ship. Replacing the simulator with a RealSense driver means changing one line in the launch file and nothing else.

カメラnodeは実際のドライバではなく、OpenCVの描画処理です。灰色の背景、グリッド線、箱を表すオレンジの矩形、ボールを表す赤い塗りつぶし円を描きます。 ただし、実カメラのドライバと全く同じROS 2のtopic型(sensor_msgs/Image)でpublishし、/camera/camera_infoには実際の内部パラメータを流します。 そのため、下流のnodeはすべて、そのまま製品に載せられるコードです。シミュレータをRealSenseドライバに置き換える場合も、launchファイルを1行変えるだけで済みます。

What this does not give you is robustness. There is no sensor noise, no motion blur, no lighting variation, no shadow. Detection here is HSV thresholding on a synthetic frame — it works perfectly because the image is perfect. That limitation is stated in the repository's own README rather than glossed over, and it is worth repeating here.

一方で、ロバスト性は得られません。センサーノイズも、モーションブラーも、照明の変化も、影もありません。 ここでの検出は合成画像に対するHSVの閾値処理であり、画像が完璧だから完璧に動いているだけです。この制約はリポジトリのREADME自体にごまかさずに書かれており、ここでも改めて明記しておきます。

Terms used on this pageこのページの用語

nodeOne running program. A ROS 2 system is many small programs, each with one job.実行中の1つのプログラム。ROS 2のシステムは、それぞれ1つの役割を持つ小さなプログラムの集まりです。
topicA named channel. One node publishes messages onto it; any number of nodes subscribe.名前付きの通信路。あるnodeがメッセージをpublishし、任意の数のnodeがsubscribeします。
MoveIt 2The motion-planning library. Given a target pose it returns a joint trajectory that avoids obstacles.動作計画ライブラリ。目標poseを与えると、障害物を避ける関節軌道を返します。
URDF / SRDFURDF lists the robot's links and joints. SRDF adds joint groups and which link pairs can never collide.URDFはロボットのリンクと関節を記述します。SRDFは関節グループと、決して衝突しないリンクの組を追加します。
planning sceneMoveIt's model of the world: the robot plus every obstacle it has to plan around.MoveItが持つ世界モデル。ロボット本体と、計画時に避けるべきすべての障害物です。
ros2_control (mock)The layer between a trajectory and motors. In mock mode it reports the commanded position back as if the motors had moved.軌道とモーターの間の層。mockモードでは、モーターが動いたものとして指令位置をそのまま返します。
OMPL / RRTConnectThe sampling-based planner MoveIt calls. It grows two random trees, one from the start and one from the goal, until they meet.MoveItが呼び出すサンプリングベースのプランナ。始点とゴールから1本ずつランダムな木を伸ばし、両者がつながるまで探索します。
back-projectionTurning a pixel back into a world coordinate. Possible here because the height of the table is known in advance.ピクセルをワールド座標に戻す処理。ここではテーブルの高さが事前にわかっているため可能です。
TFThe transform tree — the running record of where every coordinate frame sits relative to every other.座標変換のツリー。すべての座標系が互いにどの位置関係にあるかを常に記録しています。
poseA position (x, y, z) plus an orientation, stored as a quaternion.位置(x, y, z)と姿勢の組。姿勢はquaternionで保持します。
02

How data moves through the systemシステム内のデータの流れ

The five nodes, the topics between them, and the feedback loop that closes when a ball is removed.

5つのnode、その間をつなぐtopic、そしてボールが取り除かれたときに閉じるフィードバックループです。

Six programs run at once and talk over ROS 2 topics. Read the strip left to right — each box is one node, and the labels underneath are the topics carrying data between them.

6つのプログラムが同時に動き、ROS 2のtopicで通信します。下の図は左から右へ読んでください。 各ボックスが1つのnodeで、その下のラベルがnode間でデータを運ぶtopicです。

pythonscene_manager Owns the truth: spawns collision balls, the table and the box into MoveIt's planning scene, and publishes the ball layout once. 真値を管理します。衝突判定用のボール、テーブル、箱をMoveItのplanning sceneに配置し、ボール配置を1回だけpublishします。
pythoncamera_simulator Draws a 640×480 top-down BGR image of whatever balls it was told about, at 10 Hz. 知らされたボールを真上から見た640×480のBGR画像を10 Hzで描画します。
pythonvision_detector HSV red mask → contours → centroid → pinhole back-projection onto the table plane. HSVの赤マスク → 輪郭抽出 → 重心 → ピンホールモデルでテーブル平面へback-projection。
c++workspace_validator Rejects any detection outside the Panda's reachable cylinder. Publishes only survivors. Pandaの到達可能な円筒の外にある検出結果を除外し、残ったものだけをpublishします。
pythonsmart_pick_place The state machine. Picks the nearest validated target, asks MoveIt for a path, executes, repeats. state machine本体。検証済みの最も近い対象を選び、MoveItに経路を求め、実行し、これを繰り返します。
/scene_balls · PoseArray · latched /camera/image_raw · Image /camera/camera_info · CameraInfo /detected_objects · MarkerArray /validated_targets · PoseArray /scene_object_updates · String /pick_place_status · String /detection_image · Image
The loop that closes閉じるループ

When a ball is grasped, the state machine publishes remove:ball_3 on /scene_object_updates. The camera simulator receives that and stops drawing ball 3. On the next frame the detector sees six balls instead of seven, the validator forwards six, and the state machine has one fewer target. That is what makes it a loop rather than a list — the picture changes because the arm acted on it.

ボールを掴むと、state machineが/scene_object_updatesにremove:ball_3をpublishします。 カメラシミュレータはそれを受け取り、ボール3の描画をやめます。次のフレームでは検出器が7個ではなく6個を検出し、validatorも6個を転送し、state machineの対象が1つ減ります。 アームが動いた結果として画像が変わる。これが、単なるリストではなくループである理由です。

table plane, z = 0 panda_link0 camera_link · 0.40, 0.00, 0.80 m h = 0.80 m ball wₛ = cₛ + (u − cₓ)·h / fₓ wₓ = cₓ − (v − cₔ)·h / fₔ one pixel → one world point, because z is assumed to be 0
The whole depth problem, solved by an assumption. A single camera cannot measure distance — a pixel corresponds to a ray, not a point. Assuming every ball sits on the table (z = 0) turns that ray into exactly one intersection. Honest for a flat table, wrong the moment something is stacked.
奥行きの問題全体を、1つの仮定で解いています。単眼カメラは距離を測れません。1つのピクセルは点ではなく光線に対応します。 すべてのボールがテーブル上(z = 0)にあると仮定すれば、その光線との交点がちょうど1つに決まります。平らなテーブルなら正しく、何かが積み重なった時点で誤りになります。
03

Every file, and what it is for全ファイルとその役割

Every source file in both packages, how big it is, and the single job it owns.

2つのパッケージに含まれる全ソースファイルの規模と、それぞれが担う1つの役割です。

A ROS 2 workspace has a fixed shape: source packages under src/, and build/, install/, log/ generated by the build tool (colcon). Only src/ is written by hand.

ROS 2のワークスペースは決まった構成を持ちます。ソースパッケージはsrc/以下に置き、 build/、install/、log/はビルドツール(colcon)が生成します。手で書くのはsrc/だけです。

Package 1パッケージ1 — simple_moveit_demo (Python, ament_python)

FileファイルLines行数What it does役割
simple_moveit_demo/camera_simulator.py149 The synthetic camera. Subscribes to /scene_balls (a latched topic, so it gets the layout even if it started late) and to removal messages. Every 100 ms it composes a 640×480 image: grey base, 40 px grid, the orange box outline, then one filled red circle per visible ball, and publishes it together with a CameraInfo carrying fx=fy=520, cx=320, cy=240. A threading.Lock guards the ball list because subscription callbacks and the timer run on different threads.合成カメラです。/scene_balls(latched topicなので、遅れて起動しても配置を受け取れます)と削除メッセージをsubscribeします。 100 msごとに640×480の画像を生成します。灰色の下地、40 pxのグリッド、オレンジの箱の輪郭を描き、見えている各ボールを赤い塗りつぶし円で描いたうえで、 fx=fy=520、cx=320、cy=240を持つCameraInfoと一緒にpublishします。 subscriptionのcallbackとtimerは別スレッドで動くため、ボールのリストはthreading.Lockで保護しています。
simple_moveit_demo/vision_detector.py144 The perception node. Converts the image to HSV, builds a red mask from two hue windows (red wraps around the hue axis at 0/180, so one window cannot cover it), morphologically opens then closes to remove speckle and fill holes, finds external contours, drops anything under 400 px², takes the image moments to get a sub-pixel centroid, and back-projects that centroid onto the table plane. Publishes a MarkerArray of spheres plus an annotated debug image.認識nodeです。画像をHSVに変換し、2つの色相範囲から赤マスクを作ります(赤は色相軸の0/180で折り返すため、1つの範囲では覆えません)。 モルフォロジーのopen、closeの順で小さなノイズを除去して穴を埋め、外側の輪郭を抽出し、400 px²未満を捨てます。 画像モーメントからサブピクセル精度の重心を求め、テーブル平面へback-projectionします。球のMarkerArrayと注釈付きのデバッグ画像をpublishします。
simple_moveit_demo/smart_pick_place.py346 The controller and the biggest file. Declares all 28 parameters, builds the grasp planner and scene manager, waits for MoveIt's action server, then runs the pick loop on a background thread. Contains both motion strategies (Cartesian and OMPL), the trajectory retimer, and a generic action-goal helper that turns ROS 2's callback-based action API into a blocking call.コントローラで、最大のファイルです。28個のパラメータをすべて宣言し、grasp plannerとscene managerを構築し、MoveItのaction serverを待ってから、バックグラウンドスレッドでpickループを回します。 2つの動作方式(CartesianとOMPL)、軌道のretimer、そしてROS 2のcallbackベースのaction APIをブロッキング呼び出しに変える汎用ヘルパーを含みます。
simple_moveit_demo/scene_manager.py190 Not a node — a plain class owned by the controller. Publishes CollisionObject messages so MoveIt knows the table, the seven ball spheres and the five box panels are solid and must be planned around. Also colours them via PlanningScene diffs, maps a detected (x, y) back to a collision-object id by nearest neighbour within 8 cm, and re-adds a ball inside the box after placement using a 2×2 slot grid that stacks into a second layer past four balls.nodeではなく、コントローラが持つ普通のクラスです。CollisionObjectメッセージをpublishし、テーブル、7個のボール球、箱の5枚のパネルが実体のある障害物で、避けて計画すべきものだとMoveItに伝えます。 また、PlanningSceneの差分で色を付け、検出した(x, y)を8 cm以内の最近傍でcollision objectのidに対応付けます。 配置後は2×2のスロットに箱の中のボールを再追加し、4個を超えると2段目に積みます。
simple_moveit_demo/grasp_planner.py67 Pure geometry, no ROS. Given an object at (x, y), returns three stamped poses: pre-grasp 20 cm above, grasp at 4 cm above (floored at 16 cm so the wrist never dives into the table), and lift at 28 cm. Every pose carries roll = 180° so the gripper points straight down. Includes a hand-written Euler-to-quaternion conversion rather than pulling in a transform library for four lines of algebra.ROSに依存しない純粋な幾何計算です。(x, y)にある物体に対し、3つのstamped poseを返します。20 cm上のpre-grasp、4 cm上のgrasp(手首がテーブルに突っ込まないよう下限16 cm)、28 cmのliftです。 どのposeもroll = 180°で、グリッパーは真下を向きます。わずか4行の計算のために変換ライブラリを入れず、オイラー角からquaternionへの変換は手書きしています。
simple_moveit_demo/image_utils.py61 A 40-line replacement for cv_bridge, the standard image conversion library. Converts a NumPy array to sensor_msgs/Image and back for the single encoding this package uses (bgr8). Handles the row-stride subtlety — publishers may pad rows, and some leave step at 0 — and raises loudly on any other encoding.標準の画像変換ライブラリcv_bridgeを置き換える40行のコードです。このパッケージが使う唯一のエンコーディング(bgr8)について、NumPy配列とsensor_msgs/Imageを相互変換します。 行ストライドの細かい問題(publisherが行をパディングすることがあり、stepを0のままにするものもある)に対応し、それ以外のエンコーディングでは明示的に例外を出します。
launch/smart_pick_place.launch.py198 Starts all eleven nodes in the right order with the right parameters. Described in full below.11個のnodeすべてを、正しい順序と正しいパラメータで起動します。詳細は後述します。
config/demo_params.yaml53 Ball layout, motion tuning, grasp geometry, box position, and the validator's reach bounds.ボール配置、動作の調整値、把持の幾何、箱の位置、validatorの到達範囲。
config/camera.yaml35 Camera intrinsics, mount pose, ball render radius, and the two HSV red windows.カメラの内部パラメータ、取り付けpose、ボールの描画半径、HSVの2つの赤範囲。
test/test_image_utils.py92 Fifteen contract tests for the one file that reimplements a standard library: exact round-trip, padded stride, zero stride, truncated buffer, wrong encoding. No node, no camera, pure NumPy.標準ライブラリを再実装した唯一のファイルに対する15個の契約テストです。完全な往復変換、パディングされたストライド、ストライド0、途中で切れたバッファ、誤ったエンコーディングを確認します。nodeもカメラも使わない、純粋なNumPyのテストです。
rviz/vision_moveit.rviz— Saved RViz layout so the visualiser opens with the right displays already enabled.保存済みのRVizレイアウト。必要な表示が有効な状態で可視化ツールが開きます。
setup.py · package.xml36 · 48 Build metadata. setup.py declares the three console entry points that become ros2 run executables and installs launch/config/rviz files into the share directory; package.xml declares the dependency list that rosdep resolves.ビルド用のメタデータです。setup.pyはros2 runの実行ファイルになる3つのconsole entry pointを宣言し、launch/config/rvizファイルをshareディレクトリへインストールします。 package.xmlはrosdepが解決する依存関係の一覧を宣言します。

Package 2パッケージ2 — moveit_grasp_utils (C++, ament_cmake)

FileファイルWhat it does役割
include/…/workspace_validator.hpp A header-only class with no ROS dependency at all — just <cmath> and <string>. Holds four bounds in a Params struct and exposes is_reachable(x, y, z) (marked noexcept) plus rejection_reason(...) which returns a human-readable string for the log. Separating the geometry from the node means the rule can be unit-tested or reused without spinning ROS.ROSに一切依存しないheader-onlyのクラスで、使うのは<cmath>と<string>だけです。 4つの境界値をParams構造体に持ち、is_reachable(x, y, z)(noexcept付き)と、ログ用に人が読める文字列を返すrejection_reason(...)を公開します。 幾何計算をnodeから分離しているため、ROSを動かさずに単体テストや再利用ができます。
src/workspace_validator_node.cpp The ROS wrapper. Declares the four bounds as parameters, subscribes to /detected_objects, filters, publishes /validated_targets, and on a 2-second timer draws two translucent cylinders in RViz so you can see the reachable shell and the excluded inner column.ROS側のラッパーです。4つの境界値をパラメータとして宣言し、/detected_objectsをsubscribeしてフィルタし、/validated_targetsをpublishします。 2秒周期のtimerでRVizに半透明の円筒を2つ描き、到達可能な領域と除外される内側の柱を目で確認できるようにしています。
CMakeLists.txt · package.xml C++17, -Wall -Wextra -Wpedantic, one executable, install rules for the binary and the public header, and the ament linters wired into BUILD_TESTING.C++17、-Wall -Wextra -Wpedantic、実行ファイル1つ、バイナリと公開ヘッダのinstallルール、そしてBUILD_TESTINGに組み込んだament linter。

Workspace rootワークスペース直下

README.md
The accurate, current description of the system — architecture diagram, decision table, parameters, and the honest-simulation note.
システムの正確な最新の説明です。アーキテクチャ図、設計判断の表、パラメータ、そしてシミュレーションの限界についての注記を含みます。
CONTEXT.md
A running work log: current status, verified run, and a newest-first list of solved problems. Useful because it records what was removed, not just what was added.
作業ログです。現在の状況、動作確認済みの実行、解決した問題の一覧(新しい順)を記録しています。追加したものだけでなく、削除したものも記録している点が役立ちます。
build/ install/ log/
Generated by colcon. install/setup.bash is the file you source to make the packages visible to ros2 run and ros2 launch.
colconが生成します。install/setup.bashをsourceすると、ros2 runとros2 launchからパッケージが見えるようになります。
src/simple_moveit_demo/README.md
Stale. It documents an older, larger version of this package — YOLOv8, ArUco markers, a depth estimator, colour-sorted bins, a seven-state machine. All of that was deliberately deleted (see the log in CONTEXT.md). Read the root README instead.
内容が古くなっています。このパッケージの以前の、より大きな版(YOLOv8、ArUcoマーカー、深度推定器、色別の仕分け箱、7状態のstate machine)を説明したものです。これらはすべて意図的に削除しました(CONTEXT.mdのログを参照)。代わりにルートのREADMEを読んでください。
04

The vision half, step by step画像処理側の詳細

From a drawn image to a metre coordinate — HSV masking, contour filtering, and the pinhole back-projection.

描画された画像からメートル座標まで。HSVマスク、輪郭のフィルタリング、ピンホールモデルによるback-projectionです。

Why red is detected with two hue ranges, not one赤の検出に色相範囲を2つ使う理由

HSV separates colour (hue) from brightness and vividness, which is why it is used for colour thresholding instead of raw RGB — a red ball in shadow and the same ball in light have very different RGB values but nearly the same hue. OpenCV stores hue as 0–179. Red sits at the seam: pure red is hue 0, and slightly-purple red is hue 179. A single window [0, 10] would miss half of it. So the node builds two masks and ORs them together:

HSVは色(色相)を明るさや鮮やかさから分離します。そのため、色の閾値処理には生のRGBではなくHSVを使います。影の中の赤いボールと光の当たった同じボールでは、RGB値は大きく違っても色相はほぼ同じだからです。 OpenCVは色相を0〜179で表します。赤はその継ぎ目にあり、純粋な赤は色相0、やや紫寄りの赤は色相179です。[0, 10]の1範囲だけでは半分を取りこぼします。そこでnodeは2つのマスクを作り、ORで結合します。

hsv_red_lo1: [0,   120, 80]     hsv_red_hi1: [10,  255, 255]
hsv_red_lo2: [170, 120, 80]     hsv_red_hi2: [180, 255, 255]

The second and third numbers matter as much as the hue: saturation ≥ 120 excludes washed-out greys that happen to lean red, and value ≥ 80 excludes near-black pixels where hue is meaningless noise.

2番目と3番目の値も色相と同じくらい重要です。彩度 ≥ 120は、たまたま赤寄りになった色あせた灰色を除外し、明度 ≥ 80は、色相が意味のないノイズになる黒に近いピクセルを除外します。

Open, then closeopenしてからclose

The raw mask is noisy at the edges. Two morphological operations clean it with a 7×7 elliptical kernel:

生のマスクは境界付近にノイズがあります。7×7の楕円カーネルを使った2つのモルフォロジー演算で整えます。

MORPH_OPEN
Erode then dilate. Removes isolated specks smaller than the kernel without shrinking the real blobs.
収縮してから膨張。本物の領域を縮めずに、カーネルより小さい孤立したノイズを取り除きます。
MORPH_CLOSE
Dilate then erode. Fills small holes inside a blob — such as the white outline stroke drawn around each rendered ball.
膨張してから収縮。描画された各ボールの周りの白い輪郭線のような、領域内部の小さな穴を埋めます。

Order matters. Closing first would fuse two nearby specks into one fake object before opening had a chance to delete them.

順序が重要です。先にcloseすると、openで消す前に、近くにある2つのノイズが1つの偽物体に融合してしまいます。

Centroid from image moments, not from the bounding box重心はbounding boxではなく画像モーメントから

Once a contour survives the 400 px² area filter, the centre is computed from moments: u = m10/m00, v = m01/m00. That is the area-weighted average pixel position — a sub-pixel value, and it is robust to a ragged edge in a way that the centre of a bounding rectangle is not. For a circular ball the difference is small; for a partly-clipped blob it is not.

400 px²の面積フィルタを通過した輪郭は、モーメントから中心を計算します(u = m10/m00、v = m01/m00)。 これは面積で重み付けしたピクセル位置の平均で、サブピクセル精度の値になります。外接矩形の中心と違い、ギザギザした境界にも強い手法です。 円形のボールでは差は小さいものの、一部が欠けた領域では大きな差になります。

Back-projection, and where the intrinsics come fromback-projectionと内部パラメータの出どころ

The two lines that turn pixels into metres are, in the code:

ピクセルをメートルに変換する2行は、コード上では次のとおりです。

wy = cam_y + (u - cx) * cam_h / fx
wx = cam_x - (v - cy) * cam_h / fy
wz = 0.0        # DETECT_Z — the table plane assumption

Note the sign flip and the axis swap. Image u grows to the right and maps to the robot's y; image v grows downward and maps to the robot's x with a minus sign. Getting this wrong produces a system that runs cleanly and reaches for the mirror image of every ball, which is exactly the class of bug that never raises an exception.

符号の反転と軸の入れ替えに注意してください。画像のuは右方向に増え、ロボットのyに対応します。画像のvは下方向に増え、マイナス符号付きでロボットのxに対応します。 ここを間違えると、システムはエラーなく動いたまま、すべてのボールの鏡像の位置へ手を伸ばします。まさに例外が一切出ない種類のバグです。

The detector starts with hard-coded defaults for fx, fy, cx, cy but overwrites them the moment a CameraInfo message arrives on /camera/camera_info. That is how a real driver publishes calibration, so the node reads it the way it would read a real one. Only the extrinsics — where the camera is mounted — come from the YAML, and they are shared with the simulator so both ends agree by construction.

検出器はfx, fy, cx, cyのハードコードされた初期値で起動しますが、/camera/camera_infoにCameraInfoメッセージが届いた時点で上書きします。 実際のドライバもこの方法でcalibrationをpublishするため、nodeは実機と同じやり方で読み込みます。YAMLから読むのはカメラの取り付け位置である外部パラメータだけで、これはシミュレータと共有しているため、両者は構造的に一致します。

Why cv_bridge was replaced by 40 linescv_bridgeを40行のコードに置き換えた理由

cv_bridge is the standard ROS library for converting between OpenCV arrays and sensor_msgs/Image. It exists to handle roughly twenty pixel encodings from C++, and it ships as a compiled extension linked against a specific NumPy major version. In this package both ends of the image path are internal and both are bgr8, so the conversion is a reshape and a copy.

cv_bridgeは、OpenCVの配列とsensor_msgs/Imageを相互変換するROSの標準ライブラリです。C++から約20種類のピクセルエンコーディングを扱うためのもので、特定のNumPyメジャーバージョンにリンクしたコンパイル済み拡張として配布されています。 このパッケージでは画像経路の両端がどちらも内部実装で、どちらもbgr8なので、変換はreshapeとコピーだけで済みます。

The tricky part is step, the row stride in bytes. A publisher may pad rows, so step is not always width × 3, and some publishers leave it at 0. The code treats 0 as unpadded rather than dividing by it — dividing would reshape to a zero-width image and fail much later with an error about something else entirely. It then validates that the stride is a whole number of pixels and that the buffer is long enough, slices off the padding, and copies so OpenCV receives a writable contiguous array.

難しいのは、バイト単位の行ストライドであるstepです。publisherは行をパディングすることがあるため、stepが常にwidth × 3とは限らず、0のままにするpublisherもあります。 コードは0で割らず、0を「パディングなし」として扱います。割り算をすると幅0の画像にreshapeされ、ずっと後でまったく別の原因に見えるエラーで失敗するからです。 続いて、ストライドがピクセル数の整数倍であることとバッファ長が足りていることを検証し、パディングを切り落とし、OpenCVが書き込み可能な連続配列を受け取れるようにコピーします。

The reasoning, stated honestly in the sourceソースコードに正直に書かれた理由

The trigger was a NumPy 2 incompatibility, but that error will not appear on a stock Humble install, whose system NumPy is 1.x — so it is not the justification. The justification is that the workspace no longer carries a compiled dependency, or a workspace-wide version pin, that it does not need. The file's own docstring says cv_bridge should come back the moment a real camera does, because most drivers publish rgb8 and RealSense depth is 16UC1.

きっかけはNumPy 2との非互換でした。ただし、システムのNumPyが1.xである標準のHumble環境ではこのエラーは出ないため、それは正当な理由になりません。 本当の理由は、不要なコンパイル済み依存や、ワークスペース全体のバージョン固定を持たずに済むことです。ファイル自身のdocstringにも、実カメラを使う時点でcv_bridgeに戻すべきだと書かれています。多くのドライバはrgb8でpublishし、RealSenseの深度は16UC1だからです。

The C++ reachability filterC++の到達可能性フィルタ

A Franka Panda cannot reach everywhere. The validator approximates its workspace as a cylinder and applies four bounds plus one extra rule:

Franka Pandaはどこにでも届くわけではありません。validatorは作業領域を円筒で近似し、4つの境界値と1つの追加ルールを適用します。

Test判定Bound範囲Reason理由
height高さ−0.05 … 0.98 mBelow the table surface or above the arm's vertical span.テーブル面より下、またはアームの上下方向の可動範囲より上。
horizontal radius水平半径0.18 … 0.82 m√(x² + y²) from the base axis. Too close is folded onto itself; too far is simply out of reach.ベース軸からの√(x² + y²)。近すぎるとアームが折りたたまれ、遠すぎると単純に届きません。
singularity column特異点の柱r < 0.08 mDirectly above the base the wrist axes line up and inverse kinematics becomes ill-conditioned — many joint solutions map to nearly the same pose, and the planner produces wild motions.ベースの真上では手首の軸が一直線に並び、逆運動学が悪条件になります。多数の関節解がほぼ同じposeに対応し、プランナが激しい動きを生成してしまいます。

Rejections are logged with a reason string rather than silently dropped, and the surviving points are republished as a PoseArray with identity orientation — the validator has an opinion about where, not about how. The two RViz cylinders it draws every two seconds are the same bounds made visible, so an out-of-reach ball is obvious on screen before you read a log line.

除外した対象は黙って捨てず、理由の文字列とともにログに残します。残った点は、姿勢を単位回転にしたPoseArrayとして再publishします。validatorが判断するのはどこかであって、どう掴むかではありません。 2秒ごとにRVizへ描く2つの円筒は同じ境界値を可視化したものなので、届かないボールはログを読む前に画面上で一目でわかります。

This node exists in C++ rather than Python for a stated reason: it runs on every camera frame at 10 Hz and must never become the reason the controller waits. It is also the natural place to demonstrate a multi-language workspace, which is what real ROS 2 systems are.

このnodeをPythonではなくC++で書いたのには明確な理由があります。10 Hzのカメラフレームごとに実行されるため、コントローラを待たせる原因になってはならないからです。 また、実際のROS 2システムがそうであるように、複数言語が混在するワークスペースを示すのにも自然な場所です。

05

The motion half, step by step動作計画側の詳細

How a validated coordinate becomes a grasp: pose construction, Cartesian planning, the fallback, and the state machine.

検証済みの座標が把持動作になるまで。poseの構築、Cartesian計画、fallback、そしてstate machineです。

The state machine状態遷移(state machine)

IDLE→ GRASPING→ PLACING→ next ball…次のボール…→ DONE on failure →失敗時 → HOMING · ERROR

Five real states plus a fatal one. The loop lives in a while inside _run(), not in recursion — a recursive state machine that runs for seven balls and several retries would grow a stack frame per transition for no reason.

実質的な状態が5つと、致命的エラーの状態が1つです。ループは再帰ではなく_run()内のwhileで回しています。再帰で書いたstate machineを7個のボールと数回のリトライ分走らせると、遷移のたびに無意味にスタックフレームが積み上がるためです。

Each cycle: wait for a validated target that maps to a ball still on the table (with an eight-second grace period, after which any remaining unreachable balls are reported and the run ends cleanly), build the three grasp poses, show a "ghost" marker, remove the collision ball, move, place, repeat.

1サイクルの流れは次のとおりです。まだテーブル上にあるボールに対応する検証済みの対象を待ちます(8秒の猶予があり、それを過ぎると残った届かないボールを報告して正常終了します)。 次に3つのgrasp poseを作り、「ゴースト」markerを表示し、衝突判定用のボールを削除し、移動し、配置し、これを繰り返します。

The ghost-ball trickゴーストボールの工夫

There is a genuine conflict here. MoveIt refuses to plan a path that ends inside a collision object — and the grasp pose is, by definition, at the ball. Attaching the ball to the gripper (AttachedCollisionObject, the textbook answer) was tried and caused self-collision failures against panda_link7 plus a green sphere artefact stuck to the gripper.

ここには本質的な矛盾があります。MoveItはcollision objectの内部で終わる経路を計画しません。しかしgrasp poseは、定義上ボールの位置にあります。 教科書どおりの答えであるボールをグリッパーに取り付ける方法(AttachedCollisionObject)も試しましたが、panda_link7との自己干渉で失敗し、さらにグリッパーに緑の球が貼り付いて表示される不具合も出ました。

The solution splits the ball into two things. The collision ball is removed just before the approach so the planner has a clear goal. A visual ball — an RViz Marker, which has no physics and no collision meaning — is shown in the same place so the operator still sees it during the approach, then deleted the instant the gripper arrives. After the place motion succeeds, a fresh collision sphere is added inside the box. The result on screen is what a viewer expects, and the planner never fights an obstacle that is about to be picked up.

解決策は、ボールを2つに分けることです。衝突判定用のボールはアプローチ直前に削除し、プランナが障害物のないゴールを得られるようにします。 同じ位置には表示用のボール(物理も衝突判定も持たないRVizのMarker)を出しておき、オペレータがアプローチ中もボールを確認できるようにし、グリッパーが到着した瞬間に削除します。 配置動作が成功したら、箱の中に新しい衝突判定用の球を追加します。画面上は見る人の期待どおりになり、プランナがこれから拾う障害物と競合することもありません。

Cartesian first, OMPL secondまずCartesian、次にOMPL

Two ways to get the gripper from A to B:

グリッパーをAからBへ動かす方法は2つあります。

Cartesian path
Service /compute_cartesian_path. Interpolates the end-effector along a straight line in 1 cm steps and solves inverse kinematics at each one. Predictable, human-legible motion — glide over the ball, straight down; straight up, across to the box. Returns a fraction: how much of the requested path it actually managed. Below 0.95 the result is rejected.
service /compute_cartesian_path。エンドエフェクタを1 cm刻みで直線補間し、各点で逆運動学を解きます。 ボールの上まで滑らかに移動して真下へ、真上に上げて箱へ、と予測しやすく人が読み取れる動きになります。要求した経路のうち実際に達成できた割合をfractionとして返し、0.95未満なら結果を棄却します。
OMPL RRTConnect
Action /move_action. A sampling-based planner that finds some collision-free path through joint space. It will always find one if it exists, but the path can include large, alarming wrist excursions because nothing in the objective asks for a tidy route. Used only as a fallback.
action /move_action。関節空間内で衝突のない経路を何かしら見つけるサンプリングベースのプランナです。経路が存在すれば必ず見つけますが、目的関数に整った経路を求める項がないため、手首が大きく危なっかしく振れる経路になることがあります。fallback専用です。

The controller tries Cartesian, and on failure falls back with the same target expressed as a constrained OMPL goal. That ordering is why the demo looks deliberate rather than chaotic.

コントローラはまずCartesianを試し、失敗したら同じ対象を制約付きのOMPLゴールとして表現してfallbackします。デモの動きが無秩序ではなく意図的に見えるのは、この順序のおかげです。

Retiming the trajectory afterwards計画後の軌道のretiming

The OMPL path lets you request max_velocity_scaling_factor. Humble's GetCartesianPath service has no such field — it returns a trajectory timed at full speed. So the code stretches the timing after planning:

OMPLの経路ではmax_velocity_scaling_factorを指定できます。しかしHumbleのGetCartesianPath serviceにはこのフィールドがなく、全速の時間配分で軌道を返します。 そこで、計画後にコード側で時間を引き伸ばします。

t_new       = t_old / scale
velocities  ×= scale
accelerations ×= scale²

With cartesian_speed_scale = 0.5 everything takes twice as long. The acceleration term is squared because acceleration has units of distance per time squared — scaling only the velocities would hand the controller a trajectory whose numbers contradict each other, and a well-behaved controller will reject it.

cartesian_speed_scale = 0.5なら、すべての動作に2倍の時間がかかります。加速度の項が2乗になるのは、加速度の単位が距離÷時間の2乗だからです。 速度だけをスケールすると、数値同士が矛盾した軌道をコントローラに渡すことになり、まともなコントローラはそれを拒否します。

The goal constraintsゴールの制約

An OMPL goal is not a pose, it is a set of constraints. The controller builds two:

OMPLのゴールはposeではなく、制約の集合です。コントローラは2つの制約を作ります。

  • Position — a box-shaped region 4 cm on a side for a grasp, 6 cm for a place, centred on the target, attached to link panda_link8. A tolerance region rather than a point, because demanding an exact pose from a 7-axis arm makes the IK solver work far harder for no benefit.
  • Orientation — tilt tolerance 0.25 rad on the x and y axes so the gripper stays pointing down, but 3.14 rad on z, which is to say free. A sphere looks identical from every angle of wrist rotation, so constraining that rotation only removes solutions the planner could have used.
  • 位置:対象を中心とする箱形の領域で、一辺は把持時4 cm、配置時6 cm。リンクpanda_link8に対して設定します。 点ではなく許容領域にしているのは、7軸アームに厳密なposeを要求すると、IKソルバの負担が大きく増えるだけで利点がないためです。
  • 姿勢:グリッパーが下を向いたままになるよう、x軸とy軸の傾き許容値は0.25 radですが、z軸は3.14 rad、つまり自由です。 球は手首をどの角度に回しても同じに見えるため、その回転を制約しても、プランナが使えたはずの解を減らすだけです。

All grasps use yaw = 0° so the wrist keeps the same orientation from grasp to place to the next grasp, which gives OMPL simpler joint-space paths to find.

すべての把持でyaw = 0°を使うため、把持から配置、次の把持まで手首の向きが変わりません。そのぶん、OMPLが探す関節空間の経路も単純になります。

Making an asynchronous action look synchronous非同期のactionを同期的に見せる

ROS 2 actions are two-stage and callback-driven: send a goal, get a handle telling you whether it was accepted, then register another callback for the result. Written literally, a five-step pick sequence becomes five levels of nested callbacks. _send_goal() collapses that using a threading.Event and a one-element list to smuggle the result out of the closure, then blocks with a 60-second timeout. The state machine reads as straight-line code.

ROS 2のactionは2段階のcallback駆動です。goalを送ると受理されたかを示すhandleが返り、さらに結果用のcallbackを登録します。 そのまま書くと、5ステップのpick処理は5段にネストしたcallbackになります。_send_goal()はthreading.Eventと要素1つのリストを使ってclosureの外へ結果を持ち出し、60秒のタイムアウト付きでブロックすることで、これを平坦にしています。 その結果、state machineは上から順に読めるコードになります。

This only works because the node spins on a MultiThreadedExecutor with the action clients in a ReentrantCallbackGroup, and because the pick loop runs on its own thread. On a single-threaded executor the blocking wait would prevent the very callback it is waiting for from ever running — a classic ROS 2 deadlock.

これが成り立つのは、nodeをMultiThreadedExecutorでspinし、action clientをReentrantCallbackGroupに入れ、さらにpickループを専用スレッドで回しているからです。 シングルスレッドのexecutorでは、ブロッキング待ちが、待っているcallbackそのものの実行を妨げてしまいます。ROS 2の典型的なデッドロックです。

06

The launch file, line by linelaunchファイルの逐行解説

What the launch file starts, in what order, and why each delay exists.

launchファイルが何を、どの順序で起動するか、そして各遅延がなぜ必要かを説明します。

One command starts eleven processes. A ROS 2 launch file is Python that returns a LaunchDescription — a list of things to start, in order, with parameters.

1つのコマンドで11個のプロセスが起動します。ROS 2のlaunchファイルは、LaunchDescriptionを返すPythonコードです。起動するものを、順序とパラメータ付きで並べたリストです。

1 — Domain isolation, first最初にドメインを分離

domain_id_arg = DeclareLaunchArgument('domain_id', default_value='42')
set_domain_id = SetEnvironmentVariable('ROS_DOMAIN_ID', LaunchConfiguration('domain_id'))

ROS 2 nodes discover each other over the network automatically. A simulation left on the default domain will find — and be found by — a real robot on the same network. Domain 42 walls this off. It must come before every Node action in the returned list, or nodes launch before the variable is set. To inspect topics from another terminal you need the same domain: ROS_DOMAIN_ID=42 ros2 topic list.

ROS 2のnodeは、ネットワーク上で互いを自動的に発見します。シミュレーションをデフォルトのドメインのままにすると、同じネットワーク上の実機ロボットを発見し、逆に実機からも発見されます。 ドメイン42はこれを遮断します。返すリストの中ですべてのNode actionより前に置く必要があります。そうしないと、環境変数が設定される前にnodeが起動してしまいます。 別のターミナルからtopicを確認するときも、同じドメインを指定します(ROS_DOMAIN_ID=42 ros2 topic list)。

2 — Building the MoveIt configurationMoveItの設定を組み立てる

MoveItConfigsBuilder("moveit_resources_panda",
    package_name="moveit_resources_panda_moveit_config")
  .robot_description(file_path="config/panda.urdf.xacro",
      mappings={"ros2_control_hardware_type": "mock_components"})
  .robot_description_semantic(file_path="config/panda.srdf")
  .robot_description_kinematics()
  .trajectory_execution(file_path="config/moveit_controllers.yaml")
  .planning_pipelines(pipelines=["ompl"])
  .joint_limits()
  .to_moveit_configs()

MoveIt needs roughly a dozen separate parameter sets. MoveItConfigsBuilder assembles them from the community Panda configuration package. Two pieces are worth naming:

MoveItには十数個の独立したパラメータセットが必要です。MoveItConfigsBuilderは、コミュニティ製のPanda設定パッケージからそれらを組み立てます。特に挙げておくべき要素は次のとおりです。

URDF
The physical robot: links, joints, limits, meshes.
物理的なロボットの記述。リンク、関節、可動範囲、メッシュ。
SRDF
The semantic layer: which joints form the group named panda_arm, which link pairs can never collide, what named poses exist. Planning needs both.
意味の層。panda_armというグループをどの関節が構成するか、どのリンクの組が決して衝突しないか、どんな名前付きposeがあるかを定義します。計画にはURDFとSRDFの両方が必要です。
mock_components
A ros2_control hardware plugin that reports back whatever position it was commanded, instantly. The controller stack, the action interfaces and the state publishing are all real; only the physics is absent. Swapping this string for a Gazebo or a real hardware interface changes nothing above it.
指令された位置を即座にそのまま返すros2_controlのハードウェアプラグインです。コントローラ群、actionインターフェース、状態のpublishはすべて本物で、欠けているのは物理だけです。 この文字列をGazeboや実機のハードウェアインターフェースに差し替えても、その上の層は何も変わりません。

3 — The eleven nodes11個のnode

NodeDelay遅延Role役割
move_group0 sThe MoveIt planning server. Holds the planning scene, runs OMPL, serves the Cartesian-path service, executes trajectories. The heaviest process — takes 5–10 s to load.MoveItの計画サーバ。planning sceneを保持し、OMPLを実行し、Cartesian path serviceを提供し、軌道を実行します。最も重いプロセスで、読み込みに5〜10 sかかります。
robot_state_publisher0 sReads /joint_states and publishes the resulting TF transform tree so every node agrees where each link is./joint_statesを読み、そこから得たTFの変換ツリーをpublishします。これで全nodeが各リンクの位置について一致します。
static_transform_publisher0 sTies world to panda_link0 at the identity transform.worldとpanda_link0を恒等変換で結びます。
camera_tf0 sA second static transform placing camera_link at (0.40, 0, 0.80) with roll = π, i.e. looking straight down. Matches camera.yaml exactly.2つ目の静的変換。camera_linkを(0.40, 0, 0.80)にroll = πで配置します。つまり真下を向いています。camera.yamlと完全に一致させています。
ros2_control_node0 sThe controller manager. Loads the mock hardware and hosts the controllers.controller manager。mock hardwareを読み込み、コントローラをホストします。
joint_state_broadcaster2 sPublishes /joint_states from the hardware interface.ハードウェアインターフェースから/joint_statesをpublishします。
panda_arm_controller3 sThe joint-trajectory controller that MoveIt sends executions to.MoveItが軌道の実行を送るjoint-trajectoryコントローラ。
rviz20 sVisualisation, loaded with the saved layout.可視化。保存済みのレイアウトで起動します。
camera_simulator0 sParameters from camera.yaml.パラメータはcamera.yamlから読み込みます。
vision_detector0 sParameters from camera.yaml — same file as the simulator, so intrinsics and mount cannot drift apart.パラメータはcamera.yamlから読み込みます。シミュレータと同じファイルなので、内部パラメータと取り付け位置がずれることはありません。
workspace_validator0 sParameters from demo_params.yaml (the workspace_validator: block).パラメータはdemo_params.yaml(workspace_validator:ブロック)から読み込みます。
smart_pick_place4 sParameters from demo_params.yaml. Delayed so MoveIt and the controllers have a head start; it also polls for the action server every second regardless, so the delay is an optimisation, not the safety mechanism.パラメータはdemo_params.yamlから読み込みます。MoveItとコントローラが先に立ち上がるよう遅らせています。ただし遅延の有無にかかわらず毎秒action serverを確認するため、この遅延は最適化であり、安全機構ではありません。
Why the delays are marked "do not touch"遅延が「変更禁止」とされている理由

The 2 / 3 / 4 second sequence is not arbitrary — a spawner that runs before its controller manager exists fails permanently rather than retrying, and both spawners must be up before the controller can accept a trajectory. The repository's own notes flag this ordering as tested and not to be adjusted casually.

2 / 3 / 4秒という順序は適当に決めたものではありません。controller managerが存在する前に実行されたspawnerは、リトライせずにそのまま失敗します。また、コントローラが軌道を受け付けるには、両方のspawnerが立ち上がっている必要があります。 リポジトリ自身のメモにも、この順序はテスト済みであり、安易に変更しないよう明記されています。

07

Every tunable number調整可能な全数値

The two YAML files that hold every parameter, with what each one changes if you touch it.

全パラメータを保持する2つのYAMLファイルと、各値を変えたときに何が変わるかです。

Nothing numeric is hard-coded in the nodes. Every value below is declared as a ROS 2 parameter with a default and loaded from YAML at launch, which means it can also be overridden on the command line without a rebuild.

node内に数値はハードコードしていません。以下の値はすべてデフォルト値付きのROS 2パラメータとして宣言し、起動時にYAMLから読み込みます。そのため、再ビルドなしでコマンドラインから上書きすることもできます。

Motion動作 — demo_params.yaml

ParameterパラメータValue値Meaning意味
planning_attempts20OMPL is randomised; 20 independent tries, best one kept.OMPLはランダム性があるため、独立に20回試行して最良の結果を採用します。
planning_time_s10.0Per-segment budget.区間ごとの計画時間の上限。
velocity_scaling / acceleration_scaling0.2 / 0.220 % of the arm's limits on OMPL paths.OMPLの経路で、アームの上限値の20 %。
cartesian_eef_step0.01 mInterpolation step along the straight line.直線に沿った補間の刻み幅。
cartesian_min_fraction0.95Below this the Cartesian result is discarded and OMPL takes over.これを下回るとCartesianの結果を捨て、OMPLに切り替えます。
cartesian_speed_scale0.5Post-planning time stretch (see retiming, above).計画後の時間の引き伸ばし(前述のretimingを参照)。
grasp_pos_tol / place_pos_tol0.04 / 0.06 mSide length of the goal tolerance box.ゴールの許容領域(箱形)の一辺の長さ。
orient_tol_rad0.25Tilt tolerance ≈ 14°, keeps the gripper down.傾きの許容値 ≈ 14°。グリッパーを下向きに保ちます。
orient_yaw_tol_rad3.14Wrist spin left completely free.手首の回転は完全に自由。

Geometry幾何 — demo_params.yaml

ParameterパラメータValue値Meaning意味
ball_x / ball_y7 pairs7組Ground-truth layout, all inside reach, inside the camera view, clear of the box.真値の配置。すべて到達範囲内かつカメラの視野内で、箱とは重なりません。
ball_radius / ball_z0.03 / 0.025 mSphere size and resting height.球の大きさと静止時の高さ。
approach_clearance0.20 mHeight of the pre-grasp hover.pre-graspで待機する高さ。
grasp_clearance / min_grasp_z0.04 / 0.16 mGripper stops 4 cm above the object, never below 16 cm absolute.グリッパーは物体の4 cm上で止まり、絶対高さ16 cmより下には行きません。
lift_height0.28 mStraight-up retreat before crossing to the box.箱へ移動する前に真上へ退避する高さ。
box_x / box_y / box_drop_z0.55 / 0.25 / 0.30 mContainer position and release height.箱の位置とリリース高さ。
home_x/y/z0.3 / 0.0 / 0.6 mRecovery pose, used only after a planning failure.復帰用のpose。計画失敗後にのみ使います。

Cameraカメラ — camera.yaml

ParameterパラメータValue値Meaning意味
publish_rate10 HzFrame rate of the synthetic camera.合成カメラのフレームレート。
image_width / image_height640 × 480Frame size.フレームサイズ。
fx / fy520 / 520Focal length in pixels. Equal, so pixels are square.ピクセル単位の焦点距離。値が等しいので、ピクセルは正方形です。
cx / cy320 / 240Principal point — exactly the image centre, no lens offset.主点。画像のちょうど中心で、レンズのオフセットはありません。
cam_x / cam_y / cam_h0.40 / 0.00 / 0.80 mMount pose in the robot base frame. Shared by simulator and detector.ロボットのベース座標系での取り付けpose。シミュレータと検出器で共有します。
min_contour_area_px400Anything smaller is noise, not a ball.これより小さいものはボールではなくノイズとみなします。
08

Decisions worth defending説明できる設計判断

Places where a different choice was available, and the reason this one was taken.

別の選択肢があった箇所と、この方法を選んだ理由です。

Decision判断Reasoning理由
Reachability filter in C++, everything else Python到達可能性フィルタはC++、それ以外はPython It runs per frame and sits between perception and the controller, so it must never be the thing that stalls. Python is the right language for orchestration and for OpenCV work where the heavy lifting is already in C.フレームごとに実行され、認識とコントローラの間に位置するため、処理が詰まる原因になってはなりません。全体の制御や、重い処理がすでにC側にあるOpenCVの処理には、Pythonが適しています。
The state machine subscribes to /validated_targetsstate machineが/validated_targetsをsubscribeする An earlier version published that topic and never read it — the arm used ground truth while a validator ran decoratively alongside. Closing that loop is the difference between a system and a demonstration of one.以前の版はこのtopicをpublishするだけで、誰も読んでいませんでした。アームは真値を使い、validatorは飾りとして横で動いていただけです。このループを閉じることが、「システム」と「システムの見せかけ」の違いです。
Ghost marker instead of AttachedCollisionObjectAttachedCollisionObjectの代わりにゴーストmarker The textbook approach produced panda_link7 self-collision failures and a visual artefact. Separating "what the planner must avoid" from "what the operator sees" solved both.教科書どおりの方法では、panda_link7の自己干渉による失敗と表示上の不具合が出ました。「プランナが避けるべきもの」と「オペレータに見せるもの」を分けることで、両方を解決しました。
Cartesian first, OMPL as fallbackまずCartesian、OMPLはfallback Straight-line end-effector motion is predictable and inspectable. RRTConnect is a safety net, not the default.エンドエフェクタの直線動作は予測しやすく、確認もしやすいためです。RRTConnectはセーフティネットであり、標準の手段ではありません。
Yaw unconstrained at the goalゴールでyawを制約しない A sphere has no observable orientation. Constraining wrist spin removes valid IK solutions and buys nothing.球には観測できる向きがありません。手首の回転を制約しても、有効なIK解が減るだけで得るものはありません。
Retiming after planning計画後のretiming Works around a genuine gap in Humble's Cartesian-path service rather than accepting full-speed motion.全速の動作を受け入れるのではなく、HumbleのCartesian path serviceに実際にある機能の欠落を回避しています。
All values in YAMLすべての値をYAMLに集約 Retuning the demo is a text edit and a relaunch, not a rebuild. It also makes the tuning legible in one place.デモの再調整はテキストの編集と再起動だけで済み、再ビルドは不要です。調整値を1か所で見渡せる利点もあります。
ROS_DOMAIN_ID=42 in the launch filelaunchファイルでROS_DOMAIN_ID=42を設定 A simulation on a shared network must not discover, or be discovered by, real hardware.共有ネットワーク上のシミュレーションが実機を発見したり、実機から発見されたりしてはならないためです。
Fifteen tests on image_utils.py onlyテスト15個はimage_utils.pyのみ It is the one file that reimplements a standard library, so it is the one file that has to prove it round-trips and fails loudly on anything unsupported.標準ライブラリを再実装した唯一のファイルなので、往復変換が正しいことと、非対応の入力で明示的に失敗することを証明すべきなのはこのファイルだけだからです。
09

What this does not demonstrateこのプロジェクトが示していないこと

The limits of a mock-hardware, synthetic-image system, stated plainly.

mock hardwareと合成画像で構成したシステムの限界を、はっきり書いておきます。

Stating the boundary is part of the work. The following are true and are not hidden in the repository either.

限界を明記することも仕事のうちです。以下はすべて事実であり、リポジトリでも隠していません。

  • センサーのリアリティがない。画像は撮影ではなく描画したものです。ノイズ、ブラー、露出の変化、影、レンズ歪みはありません。 ここでHSVの閾値処理が成功するのは、意図的に問題を簡単にしたからです。実カメラでは真っ先に破綻する部分です。
  • 深度がない。位置は計測ではなく平面の仮定から求めています。ボールを2個積むと、上のボールは誤った位置に、確信を持って報告されます。
  • 物理がない。mock_componentsは指令された値をそのまま返します。重力も接触もグリッパーの把持力もありません。 「把持」はアームが到達するposeにすぎず、ボールはscene managerによって箱の中へ瞬間移動させています。
  • 学習ベースの要素がない。閾値処理、輪郭、モーメント、ピンホール幾何、サンプリングベースの計画と、すべて古典的な手法です。 無地のテーブル上の赤い球を検出するには、それが工学的に正しい選択です。ただし、このプロジェクト自体は学習済みモデルを示すものではありません。
  • 到達可能性のモデルは円筒です。Pandaの実際の作業領域は、姿勢によって境界が変わる複雑な形状です。円筒は保守的な近似であり、近似であることを明記しています。
  • パッケージ内のREADMEは古いままで、削除済みの版のシステムを説明しています。実際にビルドされるものを反映しているのは、ルートのREADMEだけです。
  • No sensor realism. The images are drawn, not captured. No noise, no blur, no exposure variation, no shadows, no lens distortion. HSV thresholding succeeds here because the problem was made easy on purpose; on a real camera it would be the first thing to break.
  • No depth. Positions come from a plane assumption, not from measurement. Stack two balls and the upper one is reported at the wrong place with full confidence.
  • No physics. mock_components reports back whatever it is commanded. There is no gravity, no contact, no gripper force — the "grasp" is a pose the arm reaches, and the ball is teleported into the box by the scene manager.
  • No learned component. Everything is classical: thresholding, contours, moments, pinhole geometry, sampling-based planning. That is the correct engineering choice for detecting red spheres on a plain table, and it means this project does not itself demonstrate a trained model.
  • The reachability model is a cylinder, not the Panda's actual workspace, which is a complicated shape with orientation-dependent boundaries. It is a conservative approximation and is labelled as one.
  • The inner package README is out of date and describes a deleted version of the system. Only the root README tracks what is actually built.
The stated next steps明記している次のステップ

Swap mock_components for Gazebo Ignition with a Panda plugin — the perception and planning stack needs no changes. Then add a learned component where the problem is genuinely hard — grasp pose and orientation prediction for non-spherical objects — and export it to ONNX and TensorRT for a Jetson. Detection stays HSV; a neural network for red spheres would be the wrong tool.

mock_componentsを、Pandaプラグイン付きのGazebo Ignitionに置き換えます。認識と計画の部分は変更不要です。 次に、本当に難しい問題である非球形物体のgrasp poseと姿勢の予測に学習ベースの要素を加え、Jetson向けにONNXとTensorRTへexportします。 検出はHSVのままにします。赤い球の検出にニューラルネットワークを使うのは、道具の選び方として誤りです。

10

Building and running itビルドと実行

Dependencies, the build command, and what a correct run looks like on screen.

依存関係、ビルドコマンド、そして正しく動いたときの画面の様子です。

System dependenciesシステムの依存関係

sudo apt install ros-humble-moveit ros-humble-ros2-control \
    ros-humble-ros2-controllers ros-humble-moveit-ros-move-group \
    ros-humble-moveit-planners-ompl

Buildビルド

mkdir -p ~/moveit_project_ws/src
cd ~/moveit_project_ws/src
git clone https://github.com/AungKaung1928/moveit_pickplace_demo.git .

cd ~/moveit_project_ws
rosdep install --from-paths src --ignore-src -r -y
colcon build --symlink-install
source install/setup.bash

Run実行

ros2 launch simple_moveit_demo smart_pick_place.launch.py

--symlink-install links Python sources into the install space instead of copying them, so editing a node does not require a rebuild. rosdep reads every package.xml and installs the declared dependencies.

--symlink-installはPythonのソースをinstall空間にコピーせずリンクするため、nodeを編集しても再ビルドは不要です。rosdepはすべてのpackage.xmlを読み、宣言された依存関係をインストールします。

What you should see正しく動いたときの様子

  • RViz opens immediately; the arm starts moving after 5–10 seconds of MoveIt warm-up.
  • Seven red balls on a brown table, one orange box.
  • The ball stays visible while the arm approaches, and disappears the moment the gripper arrives.
  • Straight-line motion: glide over the ball → straight down; straight up → across to the box. No return to a home pose in between.
  • The ball reappears inside the box in a 2×2 grid, stacking into a second layer for balls 5 to 7.
  • Two translucent cylinders showing the reachable workspace, refreshed every two seconds.
  • RVizはすぐに開き、MoveItのウォームアップの5〜10秒後にアームが動き始めます。
  • 茶色のテーブルに赤いボールが7個、オレンジの箱が1つあります。
  • アームが近づく間もボールは表示されたままで、グリッパーが到着した瞬間に消えます。
  • 直線的な動き:ボールの上へ滑らかに移動 → 真下へ、真上へ → 箱へ移動。途中でホームposeには戻りません。
  • ボールは箱の中に2×2の配置で再表示され、5〜7個目は2段目に積まれます。
  • 到達可能な作業領域を示す半透明の円筒が2つあり、2秒ごとに更新されます。

Inspecting it while it runs実行中の確認

ROS_DOMAIN_ID=42 ros2 topic echo /pick_place_status     # IDLE / GRASPING / PLACING / DONE
ROS_DOMAIN_ID=42 ros2 topic echo /validated_targets     # what the FSM is allowed to pick
ROS_DOMAIN_ID=42 ros2 topic hz /camera/image_raw        # should read ~10 Hz
ROS_DOMAIN_ID=42 ros2 node list

Known harmless shutdown noise終了時に出る既知の無害なメッセージ

MessageメッセージCause原因
rviz2 exit code -11A known MoveIt 2 Humble segfault in a destructor. Shutdown only.MoveIt 2 Humbleのデストラクタで起きる既知のsegfault。終了時のみ発生します。
move_group SIGKILL after 10 sA known Humble shutdown race. Shutdown only.Humbleの既知の終了時競合。終了時のみ発生します。
Arm never leaves IDLEアームがIDLEのまま動かないNothing is arriving on /validated_targets — check that the camera and detector started cleanly, and that the domain id matches./validated_targetsに何も届いていません。カメラと検出器が正常に起動したか、domain idが一致しているかを確認してください。
11

The short versionまとめ

The whole project compressed into one paragraph.

プロジェクト全体を1段落にまとめます。

A two-package ROS 2 Humble workspace in which a Franka Panda clears seven balls into a box using only what a camera saw. A Python node renders a synthetic 640×480 top-down frame; a second Python node finds the balls by HSV thresholding and recovers their table-plane positions by exact pinhole back-projection; a C++ node filters those positions against the arm's reachable cylinder; a Python state machine picks up only what survived, preferring straight-line Cartesian motion and falling back to OMPL, retiming the Cartesian trajectory by hand because Humble's service does not expose velocity scaling. Every tunable number lives in two YAML files. The one file that reimplements a standard library carries fifteen contract tests. The simulation boundary — drawn images, a plane assumption, mock hardware — is documented rather than implied.

2つのパッケージからなるROS 2 Humbleのワークスペースで、Franka Pandaがカメラで見たものだけを頼りに7個のボールを箱へ片付けます。 Pythonのnodeが真上から見た640×480の合成フレームを描画し、2つ目のPython nodeがHSVの閾値処理でボールを見つけ、厳密なピンホールback-projectionでテーブル平面上の位置を復元します。 C++のnodeがその位置をアームの到達可能な円筒と照合してフィルタし、Pythonのstate machineが残ったものだけを拾います。動作は直線的なCartesian動作を優先し、失敗時はOMPLにfallbackします。 HumbleのserviceがCartesian軌道の速度スケーリングを提供していないため、軌道のretimingは自前で行っています。調整可能な数値はすべて2つのYAMLファイルにあります。 標準ライブラリを再実装した唯一のファイルには、15個の契約テストがあります。シミュレーションの境界(描画画像、平面の仮定、mock hardware)は、暗黙の前提にせず明記しています。