mechArm 270 Pi 動的追従の限界を探る Log#10 | 視点を増やす日 設計判断とiPhone 2台体制の物理構築
このプロジェクトでは、iPhoneのカメラ映像をもとにロボットアームを動かして物を掴ませる、という仕組みを毎日少しずつ作っている。前回(Log#9)で気づいたのは、カメラを1台だけ使う構成には限界があるということだった。真上から撮るだけでは、アームの先端(グリッパー)が「正しい高さにあるか」「ちゃんと水平を向いているか」が見えない。映ってはいるが、奥行き方向の歪みで誤差が乗る。
そこで、カメラをもう1台──横向きから撮る2台目を追加することにした。ロボットを動かすコードや実機操作には触れず、設置位置の選定、運動学の机上設計、キャリブ(カメラの座標系を実空間に対応付ける作業)の計画、そしてiPhoneを2台同時にMacへ繋ぐ物理的な構築までを一日でまとめてやり切る予定だった。新たに使うのは手元に余っていたiPhone 11。プロジェクトのコアメッセージ「iPhoneとUnityだけで」と整合する選択だった。
途中でIriun(Wi-Fi経由でiPhoneをWebカメラ化するアプリ)の仕様にぶつかった。1台のMacで2台のiPhoneを別々のカメラデバイスとして同時に認識させることができない。macOS標準のContinuity Camera(iPhoneをMacのカメラとして使うApple純正機能)と併用することで切り抜けたが、当初想定していた役割分担は現場で逆転した。最終的に、上カメラ=iPhone 14 Pro Max(Continuity Camera)、横カメラ=iPhone 11(Iriun) の組み合わせで安定した。横カメラを設置した直後、机上で心配していた3つの懸念が一発で答え合わせされた。
💡 Key Takeaway
Log#9で「視差と平行スライド」の認識ズレが浮上した直後、最も合理的な次の一手は「視点を増やす」ことだった。横カメラを物理的に追加し、上カメラと役割分担させる。設計判断(平行スライド運動学・キャリブ手法)と物理設置(iPhone 2台をMacに並列接続)を一日でまとめてやり切った。
発生した問題 | 現場判断 | 結果 |
|---|---|---|
横カメラの設置位置を決める必要があった | マット手前・水平視線・レンズ高さ≈マット面のB案を採用。グリッパーが画面手前で常に最前列にきてオクルージョンしない | 物理設置完了。最初に頭に描いた構図が再現された |
Iriun単体では2台のiPhoneを独立カメラデバイスとして同時に出せない | macOS標準のContinuity Cameraと併用する。役割を入れ替え、上カメラ=iPhone 14 Pro Max(Continuity Camera)、横カメラ=iPhone 11(Iriun)に最適化 | OpenCVから2台が独立デバイスとして並列に読める状態を確立 |
| 関節角からpitchを事後計算する方針に切り替え。 | γ(lookup逆引き)で平行スライド運動学を解くロードマップが立った |
Log#9で何が見えていたか
Log#9(前セッション)で起きたのは、データ報告の途中で設計思想そのものに認識ズレがあると判明したことだった。これまで前提にしていた「APPROACH追従 → DESCEND遷移 → refine_modeで再追従」という段階分けの発想が、根本から間違っていた。
3つの要件が同時に浮上した。
第一に、上カメラはマットの真上ではなく少しズレた角度から撮影している。アームの高さが変わると画面上のTIP位置に視差が乗る。Log#8で「TIP_Xが61.4mmシフトした」と観測していた現象の正体はこれだった。
第二に、コマ撮り追従が正しい設計だった。「停止して観察→計算→動作→停止して観察」というステップ駆動を捨てて、「動いている最中も観察と動作を並行する」連続制御に切り替えるべきだった。これはLog#4で部分的に実装していたが、姿勢遷移の最中も追従し続けるところまで徹底できていなかった。
第三に、平行スライドの要件が具体化した。PICK姿勢で必要なのは「3関節同方向のリーチ伸縮」ではなく、「グリッパー水平・高さ一定でTIPだけスライドさせる動き」だった。j4も含めた姿勢拘束付きの運動学が必要だった。
Log#9でDESCEND姿勢領域のZ係数を実測した値(4.786 mm/deg)は、リーチ伸縮の感度であって、平行スライドの感度ではない。この値は使わない判断に至った。
ここから、次の一手は明確だった。視点を増やす。
上カメラだけで高さ・水平の検証はできない
Log#9の引き継ぎ書にこう書いた。横カメラを物理的に追加して、上カメラ=X方向、横カメラ=Y方向の役割分担にする。これがLog#10のスコープになった。
横カメラの設置位置 — 3案を1案に絞る
横カメラの主目的を3つに整理した。
グリッパーの 高さ Y が一定 であるか(平行スライド時の検証)
グリッパーの 水平姿勢(傾いていないか)
TIP の x/z 軌跡が直線か
これを見るには、カメラの視線がアームの動作平面と平行である必要がある。視線が斜めだと、結局上カメラと同じ「視差で測れない」問題に戻る。
物理マットの寸法とアームベースの位置関係を再確認した。
物理マット 620mm × 450mm
アームベース ── 画面上端から89mm、下端から361mm、左右中央
= アームはマットの「奥側中央」にいて、主に「手前方向(Z+)」に伸びるここから、横カメラの設置位置の選択肢は3つ。
案 | 設置位置 | 直接見える方向 | グリッパーの隠れにくさ | 上カメラへの映り込み |
|---|---|---|---|---|
A | マット左右脇 | X方向(左右)、Y | △(肩・肘で隠れる瞬間あり) | △(脇は俯瞰の視野内) |
B | マット手前側(アームの正面) | Z方向(手前奥)、Y | ◎(グリッパーが画面手前で常に最前列) | ◎(手前外側は俯瞰の死角) |
C | マット奥側(アームベース真後ろ) | Z方向、Y | ✕(アームベースが至近、グリッパーが奥に隠れる) | ◎ |
B案を採用した。3つの根拠がある。
第一に、オクルージョン耐性。アームは奥(ベース)から手前(ブロック)に伸びる構造。手前から見ると、グリッパーは画面で常に最前列にきて、肩や肘に隠れない。奥から見ると逆に隠れる。
第二に、上カメラとの干渉ゼロ。マット手前のさらに手前にiPhoneを置けば、上カメラ(俯瞰)の視野には絶対入らない。三脚の映り込みも避けられる。
第三に、作業姿勢と整合する。マットの手前側に立って実機操作するなら、iPhoneの設置・調整・電源管理も手元でできる。
仕様を確定した。
位置:マット手前端から外側に 30〜50cm
レンズ高さ:マット面 ±50mm(グリッパー先端〜アーム肩までを画面に収める)
視線:水平(マット面に対して平行)。少しでも下向き/上向きにすると視差が乗る
固定:iPhone対応の卓上三脚
横カメラ単体ではX方向(左右)の動きは測れない。カメラに対して奥行きになるからだ。ただし上カメラがX方向を担当しているので、上カメラ=X/Z平面、横カメラ=Z/Y平面 で役割分担になる。3軸全方向を1セットで測るには、もう1台(左右どちらかの脇)が必要だが、これは将来課題。
平行スライド運動学 — 制約条件と解法
横カメラの設置位置と並行して、平行スライド運動学の机上設計を進めた。
最初に確定すべきは「グリッパーの接近姿勢」だった。「平行スライド」と書いてきたものの、グリッパーの目標姿勢が曖昧だった。
掴み方には2通りある。
掴み方 | グリッパーの姿勢 |
|---|---|
上から掴む(垂直挟み) | フィンガー軸 = 重力方向、グリッパー先端は真下を向く(pitch ≈ 90°) |
横から掴む(水平挟み) | フィンガー軸 = 水平、グリッパー先端は前方を向く(pitch ≈ 0°) |
横から掴むを選んだ。これでLog#9引き継ぎ書のDESCEND姿勢(J1=87.74、J2=−10.0)の意図が確定する。「下降」というより「掴むためにブロックの中心高さまでグリッパーを下げて水平にする姿勢」と解釈できた。
赤ブロックを物差しで測ると 24mm。TIP の目標 Y は ブロック中心高さ = 12mm(マット面基準)になる。フィンガー先端の向きは アームのリーチ方向と一致(パターンX)にする。
これで運動学の制約条件が3つに整理できた。
TIPの y(高さ) = 12mm で一定
フィンガー軸 = 水平(グリッパーが傾かない)
TIPの x, z だけが目標位置に向かって変化する
これを満たす関節の動かし方は、6軸を3グループに分けて考えると綺麗に整理できる。
グループ | 役割 | 関節 |
|---|---|---|
① yaw合わせ | アームベース回転でブロック方向を向く | j0 |
② リーチ + 高さ保持 | 肩・肘・手首の協調でTIPの (距離, y) を制御 | j1, j2, j3 |
③ フィンガー姿勢 | 水平挟み or 垂直挟みの選択 | j4, j5 |
このうち②が平行スライド運動学の本体。j1+j2でリーチを伸縮させると手首が傾くので、j3で相殺する——という3関節協調が肝になる。
ここで設計上の懸念を一つ記録しておく。TIP目標Y = 12mm はマット面に異常に近い。グリッパー本体(フィンガーの根元部分)の下端がマット面と干渉する可能性がある。フィンガー先端は12mmにあっても、グリッパーアセンブリ全体の高さは数十mmあるはず。この検証こそ、横カメラを設置した直後の最初のジョブになる。横から見て「グリッパーがマット面に擦っていないか」を即座に判定できる——横カメラ導入の意義がここにも繋がる。
解法の選択 — γ(lookup逆引き)が最適だった
平行スライドを解く方法は3つある。
解法 | 中身 | 実装難度 | 既存資産との整合 |
|---|---|---|---|
α 解析的IK | DHパラメータから5×5の連立を解く | 高(mechArmのDH正確に必要) | FK scanは使わない |
β 数値解(ヤコビアン疑似逆) | 各時刻で速度ベクトルから関節速度を求める | 中 | FK scanは使わない |
γ lookup逆引き |
| 低 | 既存のlookup方式の自然な拡張 |
γ(lookup逆引き)を採用した。3つの根拠がある。
第一に、P008「測れる手段が推論より先」と完全に整合する。既存のFK scan(512,087点、実機キャリブ済み)を使う方が、机上のDHパラメータから組むより実機との誤差が小さい。
第二に、αはmechArmの正確なDHパラメータが必要で、公式値が信用できない場合がある。βは反復計算が重く実時間性に難がある。
第三に、Pixels to Motorsのコアメッセージとも整合する。「個人がiPhoneだけで」のスタンスで、複雑な数値解析を回避してテーブル参照で動くなら、メソドロジー的にも筋が通る。
ただし、γが成立するには条件が一つあった。
fk_scan_motion_based.csvの各点に、グリッパーの姿勢情報(pitch / yaw / roll)が記録されているか?
姿勢情報がないと、「フィンガー軸 = 水平」という制約で絞り込みができない。CSVを開いて列構成を確認した。
ee_x,ee_y,ee_z,j0,j1,j2,j3,j4,j5
0.231374,0.106889,0.227510,-37.000,95.000,-49.000,18.500,-70.000,-30.500
0.232657,0.093842,0.232371,-37.000,95.000,-49.000,18.500,-65.000,-30.500
0.232980,0.080483,0.236534,-37.000,95.000,-49.000,18.500,-60.000,-30.500姿勢列は入っていなかった。位置(ee_x, ee_y, ee_z)と関節角(j0..j5)だけ。512,087行。
ただし、これは致命的ではない。mechArm 270-Pi の機構は「ベース回転 → 肩ピッチ → 肘ピッチ → 手首ピッチ → 手首ヨー → 手先ロール」のシリアル6軸。フィンガー軸の世界座標系pitch(水平からの傾き)は、ピッチ系3関節の足し算で表現できる。
gripper_pitch_world ≈ j1 + j2 + j3 (符号は実機キャリブで確定)これが 0° 付近 = フィンガー軸が水平。Log#9のDESCEND姿勢(J1=87.74, J2=−10.0)の合計77.74° に対して、j3 = −77.74° 付近を選べば水平になる、という見通しが立った。
γ-lookup逆引きの実装方針はこうなる。
既存
fk_scan_motion_based.csvは変更禁止ルール遵守。読み込んで姿勢列を事後計算で追加し、新ファイルfk_scan_with_pose.csvとして保存する(一度きりの前処理)平行スライド逆引きの絞り込み手順:
第1フィルタ:
ee_y ≈ 12mm(許容誤差 ±2mm 程度)第2フィルタ:
|gripper_pitch_world| ≤ ε(例:±5°)第3フィルタ:目標
(ee_x, ee_z)に最近傍の点出力:その点の
j0..j5をsend_angles()に渡す
各フレームでこの絞り込みを実行 → コマ撮り連続追従が成立
第1フィルタ(ee_y ≈ 12mm)で 512,087点が一気に数千〜数万点に絞られるはずで、第2・第3フィルタは軽い。ループ内でも計算負荷は低い。
残る不確実性は2つ、いずれも実機検証フェーズの話だ。符号の確定(j1 + j2 + j3 でいいのか、それとも j1 - j2 + j3 などになるか)と、「水平」の実機キャリブ(横カメラで「フィンガー軸が水平」に見える瞬間の j1 + j2 + j3 を計測してオフセット補正)。
横カメラのキャリブ計画 — アーム自体をキャリブ装置として使う
キャリブを3層に分けて整理した。
層1:内部パラメータ(カメラ単体の歪み補正)
教科書的にはチェスボードキャリブで各カメラ個別に取る。が、やらない判断にした。上カメラがすでにhomography動的計算で十分な精度を出している。歪み補正を別途取らなくても、「マット平面に投影された結果」が直接使える設計になっている。横カメラも同じ思想で行ける。カメラの絶対精度より、運動の整合性が取れているかの方が優先——P008の延長線上にある考え方だ。intrinsicキャリブは「精度が足りないと判明したら取る」の後回しでいい。
層2:外部パラメータ(横カメラの位置・向き)
ここが本丸。「横カメラのpixel座標 ↔ マット座標 (z, y)」の対応をどう作るか。
通常のやり方は「チェスボードを置いて外部パラメータを推定」だが、Pixels to Motorsらしい解き方がある。
アームそのものをキャリブ装置として使う
具体的には次の手順。
アームを 複数の既知姿勢(j0..j5)に動かす(FK scanから選んだ点を使えばいい、新規スキャン不要)
各姿勢で、TIP の真の3D位置
(ee_x, ee_y, ee_z)は FK scan から既知横カメラ画像での TIP の pixel 位置を記録(青マーカー検出ロジック流用)
pixel_u, pixel_v↔(z, y)のサンプル点が数十〜数百得られる多項式回帰(もしくは homography)で変換式を作る
これがR2R2R設計思想と完全に整合する——既知のUnity真値を使ってリアルを校正する流れ。追加のキャリブ用具(チェスボード等)も不要。
層3:両カメラの統合
各カメラから取れる情報の役割分担。
軸 | 上カメラ | 横カメラ |
|---|---|---|
X(左右) | ◎ 主担当 | ✕ 奥行きで取れない |
Y(高さ) | △ 視差で誤差 | ◎ 主担当 |
Z(手前奥) | ○ 視差影響あり | ◎ 主担当 |
統合ロジックはこうなる。
x = x_top(上カメラ単独)y = y_side(横カメラ単独)z = (z_top + z_side) / 2(両者の平均、ただし大きくズレたらアラート)
最後の z の二重取り が地味に重要。z_top と z_side が大きく食い違ったらキャリブのどちらかがおかしい という整合性チェックが、実機での診断に効く。これは「測れる手段が増えると、誤りも測れるようになる」という形でP008の応用になる。
物理設置のトラブル — 仕様の落とし穴
設計判断が固まったところで、物理設置に進んだ。
横カメラとして使うのは、エイイチが手元に持っていた iPhone 11。コアメッセージ「iPhoneとUnityだけで」と整合する選択だった。
最初の想定は単純だった。
上カメラ(既存iPhone)= Iriun(現状維持)
横カメラ(iPhone 11)= Iriun(同じく追加)
ところが iPhone 11 を Iriun に追加で接続すると、Mac側のIriunアプリには2台のiPhoneがプルダウンで切り替え式として認識された。同時に2つのカメラデバイスとして出てこない。OpenCVから見ると、デバイスは1つしか見えなかった。
調査すると、Iriun単体では2台のiPhoneを独立カメラデバイスとして同時には出せない仕様だった。複数のレビューで「Great webcam, but single source only」と指摘されており、ユーザーから「support video from multiple devices」を要望として挙げている段階だった。
公式サイトの「multiple applications simultaneously」という記述は紛らわしい。これは1つのカメラ映像を複数のアプリで同時利用する話で、2台のiPhoneを別デバイスとして並存させる話ではなかった。仕様書の文言を額面通り受け取ると失敗する典型的なケースだった。
代替案は3つあった。
順位 | 代替案 | 内容 |
|---|---|---|
1 | Continuity Camera | macOS標準。iPhoneとMacが同じApple IDで自動連携。1台目をIriun、2台目をContinuity Cameraにすればデバイス分離できる |
2 | EpocCam | Iriunと別のWi-Fiカメラアプリ。Iriunと別デバイスとして見える |
3 | OBS Virtual Camera | 複数入力を統合する仮想カメラ。最後の手段 |
Continuity Camera を採用した。追加アプリ不要、コストゼロ、Apple純正で安定。
要件は macOS Ventura(13)以降、iOS 16以降、iPhone XR以降のモデル。MacBook Pro 14インチ 2021(M1 Pro、macOS Tahoe 26.4.1)と iPhone 11、要件は全てクリアだった。
役割の入れ替え — 当初想定からの転換
Continuity Cameraを有効にしてiPhone 11を Mac の近くに置くと、Mac側で「iPhone」というデバイスが認識された。OpenCVで見ると Camera 1〜2 にiPhoneが並ぶ。両方の映像を取って目視確認すると、当初想定で組んだ役割分担で問題が出た。
iPhone 11 が Continuity Camera だと、Iriunで上カメラを担当している既存iPhone との接続が不安定になることが何度かあった。試行錯誤の中で、エイイチが役割を入れ替えてみる判断をした。
iriunカメラをiPhone11の方にして、通常のカメラをiPhone14proに変更したよ。 こっちのほうがいいみたいだね。
最終構成はこうなった。
カメラ | 接続経路 | 機材 |
|---|---|---|
上カメラ | Continuity Camera | iPhone 14 Pro Max |
横カメラ | Iriun | iPhone 11 |
理由は明快だった。
iPhone 14 Pro Max + Continuity Camera ── Apple公式サポート機種で安定動作、新しい機種ほど相性良い
iPhone 11 + Iriun ── Iriunは機種を選ばないWi-Fi汎用ソフトで、古い機種でも安定
Log#9の引き継ぎ書では「既存iPhone上カメラ=Iriun、新規iPhone 11横カメラ=Continuity Camera」と書いていたが、実際は逆の組み合わせの方が安定するという発見だった。設計時の想定が現場で覆ることはよくある。
最終的なPython側のカメラスキャン結果はこうなった。
# Camera scan結果
=== Camera scan ===
Camera 0: opened but no frame
Camera 1: 1920x1080, brightness=115.8
Camera 2: 1920x1080, brightness=71.2Camera 0 はMacBook内蔵カメラ(接続不要)。Camera 1 と Camera 2 が iPhone 2台。
ただしここで一つ落とし穴があった。Camera 1 がどっちで Camera 2 がどっちか、起動順で入れ替わる。Iriun と Continuity Cameraの初期化タイミングで Mac側のカメラインデックスは平気で入れ替わる。
これはLog#5(LOG153)の「カメラインデックスが過去から1つズレていた」事故と同じパターンだった。当時のメモにこう書いてある。
detect_block_position.py時代に動いていたcv2.VideoCapture(1)が、今は別物を指していた。Mac環境(接続デバイス、Iriunのバージョン、macOSアップデート)が変わってインデックスが再割り当てされた結果。
実装時には固定値ではなく 起動時に判定するロジック で吸収する必要がある。具体的には find_iriun_camera() 関数を拡張して横カメラ用にも自動検出を入れる。これは次セッション(LOG193)の実装タスクとしてメモした。
切り分けのために、ライブビューで両方を並べて表示して、iPhone 11を手で動かしてどっちが反応するか即確認するスクリプトを書いた。
import cv2
caps = []
for i in [1, 2]:
cap = cv2.VideoCapture(i)
if cap.isOpened():
caps.append((i, cap))
print("ライブビュー起動。iPhone 11 を手で動かして、どっちのウィンドウが反応するか見る。'q' で終了")
while True:
for idx, cap in caps:
ret, frame = cap.read()
if ret:
small = cv2.resize(frame, (640, 360))
cv2.imshow(f'Camera {idx}', small)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
for _, cap in caps:
cap.release()
cv2.destroyAllWindows()このスクリプトでiPhone 11を手で動かすと、反応した方が iPhone 11 = 横カメラ、と確定できる。今回は Camera 1 が横カメラ(Iriun + iPhone 11)、Camera 2 が上カメラ(Continuity Camera + iPhone 14 Pro Max) だった。
横カメラの映像が答えてくれた
物理設置を完了した直後、Camera 1(横カメラ)の映像が3つの机上検討の答え合わせを一発でやってくれた。
第一に、グリッパー本体とマット面の干渉。机上で予測していた懸念が、横カメラの映像で一発で判定できた。グリッパー底部とマット面の間に明確な隙間がある。TIP=12mmで干渉しないことがほぼ確定した。
第二に、青マーカーが画面に入っている。これは大きい。上カメラで使っている「青シール幾何学法」のTIP検出ロジックが、横カメラにそのまま流用できる。アルゴリズム新規開発不要。同じdetect_tip_blue_geometry()関数で、入力フレームを切り替えるだけでいい。座標系の対応付けは別途必要だが、検出層は再発明しなくていい。
第三に、ブロックの位置関係。横カメラは「APPROACHからDESCEND遷移してまさにこれから掴む直前」のシーンを真横から正確に追える視点になっている。これがまさに上カメラの視差で見えなくなっていた領域だった。
これで本日のスコープは概ねカバーできた。確定事項を整理する。
番号 | 確定事項 |
|---|---|
1 | 横カメラ=iPhone 11(Iriun)、上カメラ=iPhone 14 Pro Max(Continuity Camera)、設置位置はマット手前側、水平視線 |
2 | TIP目標Y=12mm(ブロック高さ24mmの中心)、フィンガー軸=リーチ方向(パターンX) |
3 | 運動学はγ(lookup逆引き)、姿勢列は事後計算で追加( |
4 | 横カメラのextrinsicは「アーム自体をキャリブ装置として使う」、整合性チェックはz軸の二重取り |
5 | カメラインデックスは起動時に自動判定する設計(実装はLOG193で) |
次セッション以降の見通し
LOG193以降の実装フェーズ順序を整理した。
iPhone 11 の物理固定(卓上三脚で完全固定、レンズ高さ・水平視線を再現可能に)
カメラインデックスの自動検出ロジック実装(Iriun + Continuity Camera両対応)
横カメラ単体テスト:青マーカーTIP検出ロジック(上カメラの関数を流用)が動くか確認
キャリブスクリプト作成:FK scanから選んだ50〜100姿勢にアームを順次移動 → 両カメラで青マーカー pixel位置を記録 → CSV保存
データから横カメラの pixel ↔ (z, y) 変換式を作る(多項式回帰、
scipy.optimizeornumpy.polyfitベース)整合性検証:z_top と z_side が同じ姿勢でどれだけ一致するかを統計的に評価。ズレが大きい姿勢領域があれば原因特定
visual_servo_pick.pyを平行スライド対応 + 両カメラ統合にリファクタリング横カメラで「グリッパーが水平」「Y=12mmで安定」を視覚確認しつつ、PICK実機検証
Log#9で破棄した「段階分け追従」の代わりに、コマ撮り連続追従+平行スライド運動学+両カメラ統合という新しい構造が立ち上がる。この設計が成立すれば、PICK実機動作(爪が届く・掴める)への道筋が初めて通る。
実機を動かさず、ハードウェアを最小限の追加で済ませ、机上設計と物理設置の両方をカバーした一日だった。視点を増やすという判断の意味は、設置直後の映像で即座に答え合わせされた。
横カメラを置く前と後では、見える世界が違う。
