「カメラで物を見つけてつかむ ― 色としきい値だけで作る物体検出ピッキング(Isaac Sim)
📷 はじめに:今回やること
前回は、キューブの位置を座標で教えていました。
「(0.72, 0, 0.03) にあるからつかんで」と。
今回はそれをやめます。カメラで赤いキューブを見つけて、その世界座標を自分で推定してからつかみに行く。これがフィジカルAIの入口、「見て、動く」の最初の一歩です。
ただし派手なことはしません。機械学習も OpenCV も使わない。
使うのは色(赤)のしきい値と深度、そして numpy だけ。
中身が全部見える状態で「カメラ→3D座標」を作ります。
💡 ロボットを動かす土台(逆運動学=IK、状態機械)は前回のコードをそのまま流用します。今回変わるのは「キューブの位置をどこから得るか」だけ。ここが差分だと分かると、話がすごくシンプルになります。
🔁 前回との差分は、たった1箇所
前回のコードには、キューブの真値を読むこんな関数がありました。
def _cube_pos(self):return self.cube.get_world_poses()[0]... # ← 真値(カンニング)
今回はここをカメラ検出の結果に差し替えるだけ。
def _cube_pos(self):return self.detected_cube_pos # ← カメラが見つけた位置
IK もフェーズも往復ループも、前回のまま。「どこを狙うか」の情報源を変えただけで、カメラピッキングになります。
🎯 カメラで座標を得る4ステップ
ワークスペースの真上に自前のカメラを1台置いて、見下ろします(前回キューブを自分で作ったのと同じ発想)。検出はこの4ステップ。
① 撮る … RGB画像と深度画像を取得
② 赤を抜く … 赤いピクセルをマスクして重心 (u, v) を求める
③ 深度を読む … 重心の深度 d を取る
④ 逆投影 … (u, v, d) を世界座標へ変換
言葉にすると簡単ですが、②と④に現実の落とし穴が待っていました。ここからが本題です。
🩸 ②の落とし穴:「赤」は絶対値で判定してはいけない
最初、こう書きました。
mask = (R > 120) & (G < 100) & (B < 100) # 素直な「赤」判定動かすと……赤画素ゼロ。デバッグログを仕込んで画像の一番赤いピクセルを見たら、こうでした。
最赤画素 RGB=(242, 241, 241) # ほぼ真っ白!照明とトーンマッピングで、赤いキューブが明るく=白っぽく描画され、G も B も高くなっていたんです。絶対値の G < 100 は照明にまるで勝てない。
そこで、「R が G・B よりどれだけ突出しているか」という相対判定に変えました。
redness = R - max(G, B) # 赤の“突出度”mask = (R > 90) & (redness > 55) # 明るさに左右されない
白い面は R≈G≈B なので redness≈0 で弾かれ、赤いキューブだけが残る。
色は絶対値でなく相対で見る ― これは実写でもシミュでも効く定石です。
🧮 ④の落とし穴と本命:逆投影は自分で書く
ピクセル (u, v) と深度 d から世界座標へ ― これを最初は Isaac の便利関数に任せたら、見当違いの場所を拾いました。座標規約が想定と違ったんです。
なので自分でピンホールモデルの逆算を書きました。中身はこれだけ。
x_c = (u - cx) / fx * d # 右方向(+X)y_c = -(v - cy) / fx * d # 上方向(+Y)。画像のvは下向きなので符号反転
z_c = -d # 前方は -Z(USDカメラ規約)
world = cam_pos + R @ [x_c, y_c, z_c] # カメラ姿勢で世界へ回す
fx(ピクセル単位の焦点距離)は 焦点距離 × 幅 / センサー幅 で出します。外部の魔法に頼らず、規約を自分で握る。こうするとズレたとき「どの軸がどうズレたか」を自分で追えます。実際これが次の落とし穴で効きました。
⚠️ 地味に重要:縦横のピクセルを正方にそろえる(縦アパーチャを解像度比で設定)。これを忘れると Y 方向だけ倍率がずれて、拾う位置が縦にずれます。
🕳️ ③の落とし穴:カメラが下を向いていなかった
「真上に置いて単位姿勢なら真下を向くはず」…… 甘かった。デバッグログにカメラの実姿勢を出したら:
カメラ姿勢: quat(wxyz)=[-0.5 -0.5 0.5 0.5]計算すると、視線が真下ではなく真横(+X方向)。Isaac のカメラは「単位姿勢=+Xを見る」規約だったんです。道理で赤が1画素も写らないわけです。
規約に振り回されないよう、カメラの姿勢を USD で直接「真下向き」に固定しました。逆投影側は実際の姿勢を読んで計算しているので、これで物理も計算もピタッと一致。ここで自前逆投影にしておいた恩恵が出ました。
修正後のログ:
検出: u=641 v=360 d=1.24 -> world=(0.72, 0.00, 0.06) # 実座標にピタリ🧹 仕上げ:離したあと、キューブを吹き飛ばさない
つかんで置けるようになった後、最後の問題。置いて離した直後、アームがキューブを弾く。原因は「離した瞬間にホーム姿勢へ一気にジャンプ」して、キューブの真横で急旋回していたからでした。
直し方は2つ。
RETREAT(真上へ退避)フェーズを追加 … 離したら、まず垂直に上へ逃げてキューブから離れる
ホーム復帰を補間でなめらかに … 一気に飛ばさず、約1秒かけて少しずつ戻す
# 現在姿勢からホームへ、フレームをかけて線形補間alpha = 経過フレーム / 全フレーム
blended = home_from + alpha * (home - home_from)
「その場で静かに離す → 真上へ退避 → ゆっくり定位置へ」。人の丁寧な手つきに近づきました。
▶️ 動かし方
# Windows:リポジトリのルートからC:\isaacsim\python.bat ".\02_camera_picking\camera_picking.py"
うまく検出できないときは debug: True のログを見て、
赤画素数 が0 → red_dominance / red_min を調整
world=(...) が実座標とズレる → カメラの向き・fx を確認
🚀 まとめと次回
「座標を教える」から「カメラで探す」へ。差分は情報源1箇所だけ
検出は 色の相対判定 + 自前ピンホール逆投影。numpy だけで中身が全部見える
ハマったのは、色(照明で白っぽい)・向き(真横を見ていた)・逆投影(規約違い)の3つ。デバッグログを出す→数値で切り分けるが結局いちばんの近道
カメラで“見て動く”の骨格ができました。次回は複数ワークの順次ピッキング、その先は検出を学習ベースに置き換えていきます。
コード全文はこちら 👉GitHubリンク:02_camera_picking/
次の記事
フィジカルAI・工場向けAI開発のご相談
私は製造業向けのAIシステム開発を行っています。
こんなお悩みはありませんか?
* 外観検査を自動化したい
* 作業監視をAI化したい
* PPE(保護具)着用チェックを導入したい
* ロボットアームへAIを組み込みたい
* ROS2・Isaac Simを活用したい
* フィジカルAIのPoCを進めたい
* 社内向けAIツールを開発したい
* Isaac simを使ってロボット操作したい
要件整理からPoC、実装まで対応可能です。
まずはお気軽にご相談ください。
お問い合わせ
https://forms.gle/qTxgVfBF622eiBWA6
技術ポートフォリオ
GitHub
Hugging Face
note
いいなと思ったら応援しよう!
無料で気軽に使えるアプリを個人で開発しています!
いただいたチップは、アプリの改善や開発環境の維持に大切に使わせていただきます。
応援していただけると、とても励みになります!