Pixels to Motors DAY 196
mechArm 270 Pi Log #11 検出パイプライン分割

横カメラを統合する ── 検出パイプラインを上向きと前向きに分割した

FROM / Log#10
物理セットアップ完了
iPhone 11 三脚固定 Iriun 接続 ソフト未着手
→
TO / Log#11
2系統パイプライン確立
カメラ自動識別 TIP検出関数 新規実装 緑2点中点でY整合
💡 Key Takeaway ── 本日の達成
01 / カメラ識別
色パターンで3台から2台を自動判定
OpenCV index は非決定的。青≥2かつ緑=0→上カメラ、青≥2かつ緑≥2→横カメラで起動毎に確実判別。
02 / 関数分割
既存青3点幾何は横カメラに流用不可と確定
wrist 側青シールが爪本体に隠れて2点のみ。実行して (None,None,None) の確証を数字で残した。
03 / TIP定義
緑2点中点がY=959で掴み高さに整合
v1→v2→v3と定義を進化。緑シール位置(爪先端寄り)のみをTIPとすることで物理的な掴み高さと一致。
01

Z方向が収束しない根本原因 ── 上カメラ1台では分解能が足りない

幾何的制約

上カメラ homography の限界 ── 爪が浮いている分だけ視差が乗る

SIDE VIEW マット面 Z=0 BLK 爪 (TIP) 上cam 視差ズレ 見かけ位置 H (爪の高さ) 横cam → Y軸として直接読める 横カメラ追加後 上カメラ (homography制約あり) 横カメラ (Yを直接観測)
上カメラのみ Z不安定
  • 構造画像y軸方向 → Z+物理距離
  • 制約homography は Z=0 平面(マット面)を仮定
  • 問題爪が浮いていると見かけ位置が奥側にズレる
  • 結果ループが嘘の差分に向かって動き続ける
横カメラ追加 採用 ★
  • 構造横から見れば爪の高さYがy座標として直接読める
  • 制約homography の制約を受けない独立軸
  • 解決Z方向を独立観測できる欠落軸を補完
  • 前提Log#10 で iPhone 11 を三脚固定済み
"
上カメラを増やしてもZ方向の収束精度は上がらない。
必要なのは「Zを独立に観測できるカメラ」だ。
Log#11
設計判断
02

3台のカメラから「どれが何か」── OpenCV index は非決定的

環境構成

macOS + OpenCV における3カメラ混在問題

MacBook内蔵 FaceTime HD 爪を映していない iPhone 14 Pro Max Continuity Camera → 上カメラ iPhone 11 Iriun Webcam (WiFi) → 横カメラ cv2.VideoCapture(0〜2) 起動毎に index変動 AVFoundation順 ≠ cv2順 対応が公開されていない
試みと結果

ソフト側での識別 ── 全手法が失敗した理由

  1. 01
    AVFoundation デバイス名取得(ffmpeg) +
    NG AVFoundation順 ≠ cv2 index順 内部仕様・非公開 起動毎にずれた
  2. 02
    解像度判定(frame.shape) +
    NG 3台とも1920×1080になり得る MacBook内蔵もスケーリングされて返る 判定軸として使えない
  3. 03
    ブライトネス判定(過去実績あり) +
    NG 上カメラ・横カメラ共にiPhone → 明度差なし Log#7系の3台なら有効だったが今回は不可
  4. 04
    色パターン識別(物理マーカー追加)← 採用 +
    採用 ★ 爪上面=青シール / 爪前面=緑シール 映っているものが何かという最高信頼性の情報 ソフト依存ゼロ
識別ロジック

色パターンによるカメラ役割判定フロー

上カメラ視野(真上) ● 青×3 緑×0 横カメラ視野(前面) wrist隠れる 青×2 緑×2 MacBook内蔵 爪が映っていない 青×0 緑×0 → 上カメラ → 横カメラ → 除外
03

auto_detect_cameras.py ── 193行で3台から2台を判定する

確定仕様 auto_detect_cameras.py 設計パラメータ
⏱
Warmup
30 frames
自動露出安定待ち
◼
面積フィルタ
80〜8000 px²
9〜90px四方相当
⟳
モルフォロジー
open→close
ノイズ除去→穴埋め
≥
判定閾値
blue≥2
(3点目欠損許容)
HSV閾値設定

青・緑シール検出の HSV レンジ(OpenCV: H=0〜179)

Hue (0〜179) 0 50 100 150 179 GREEN 35〜85 BLUE 100〜130 Saturation / Value 下限 Blue S≥100 255 Blue V≥50 255 Green S≥80 255 Green V≥60 255
設計判断 01 / Warmup
30フレーム空読み
Iriun はWiFi経由でフレームを送る。開いた直後は黒フレームや低露出が来る。30フレームで概ね安定する。
設計判断 02 / Morphology
open→close の順
収縮→膨張でノイズ消去してから膨張→収縮で穴埋め。逆順にするとノイズが拡大されて誤検出が増える。
設計判断 03 / 判定条件
blue≥2(ジャストでない)
青3点目が手ブレで一瞬隠れても上カメラと識別できるよう、blue≥2という緩い条件にした。個数より「両方同時検出」が本質的弁別。
04

detect_tip_from_frame は横カメラに流用できない ── 数字で確証

物理制約

横カメラ視野での爪シール配置 ── wrist は爪本体に隠れる

上カメラ視野(真上) cA cB wrist 青3点 → 全て検出可能 ✓ 横カメラ視野(前面) cA cB wrist 隠れ 緑L 緑R 青2点のみ → detect_tip_from_frame → (None, None, None) ✗
試みた方法
流用
detect_tip_from_frame
期待青3点→TIP計算
実際青2点→(None,None,None)
原因wrist が物理的に隠れる
判定✗ 構造的に流用不可
→
採用した方法
新規
detect_tip_side()
入力緑2点(爪前面)
出力TIP = 緑2点の中点
チェック青2点を妥当性検証に使用
判定◎ 独立実装
"
流用できないと判定するために、修正や設定変更を
一度もしていない。関数を呼んで結果を見ただけ。
確証は数字で残す。
P008
測れる手段が
推論より先
05

TIP 定義の v1→v2→v3 ── 掴み高さに整合するまで

Y値の変化

TIP定義バージョン別 Y座標(画面下方向が大きい値)

820 px 900 px 930 px 959 px ← 緑Y平均(掴み高さ) Y≈900 v1 青2点中点 Y≈929 v2 左右爪中点 Y=959 ★ v3 緑2点中点
TIP定義の進化

v1→v2→v3 ── 掴み高さへの収束プロセス

v1
青2点中点
v2
左右爪中点
v3
緑2点中点
↑バージョンを選択
v1 / 青2点の中点 不採用
TIP Y値
≈ 900 px
爪上面の青シール位置。爪の最も上側。
問題
掴み高さに届かない
ブロックを掴む爪先端(緑シール位置)より約60px上。掴み動作で5〜8mm のオフセット。
爪の上面高さ ブロック接触なし
v2 / 左右爪それぞれの青+緑縦中点、のさらに中点 不採用
TIP Y値
≈ 929 px
青(≈900)と緑(≈959)の中間。爪の上下中央点。
問題
まだ青寄り
可視化PNGで赤円が爪の上下中央にプロット。掴み高さと約30px(5〜8mm)ズレている。ロジックは正しく動いているが定義が物理位置と合っていない。
ロジックは正しい 定義が物理とズレ
v3 / 緑2点の中点のみ 採用 ★
TIP Y値
= 959 px
緑2点のY平均と完全一致。爪先端寄りの物理的な掴み高さ。
根拠
緑シール = 爪下端寄り
緑シールは爪の前面・下端寄りに貼ってある。掴む高さがそのまま緑シール位置。青は妥当性チェックとして残す。
緑2点中点 = 掴み高さ 青2点は検証に残す tip_pixel.Y = 緑Y平均
v1 / 青2点中点
≈900
px (Y)
爪の上面
v2 / 左右爪中点
≈929
px (Y)
爪の上下中央
v3 / 緑2点中点 採用
959
px (Y)
掴み高さ ★
06

tip_pixel = (950, 959) ── 数値整合と可視化確認

実機出力

side_camera_utils.py 実行結果 ── 検出点の座標整合

1920×1080 フレーム(縮小表示) 爪(横から) ● 青 (999, 902) ○ 緑 (1000, 962) TIP = (950, 959) 計算整合 緑X: (1000+901)/2 = 950.5 → 950 ✓ 緑Y: (962+957)/2 = 959.5 → 959 ✓ tip_pixel = (950, 959) 緑Y平均と完全一致 ★ 整合確認
確定値 実機検証出力 ── 横カメラ index=1
◎
TIP pixel
(950, 959)
●
青シール2点
(999,902)
(902,899)
○
緑シール2点
(1000,962)
(901,957)
→
tip_yz_mm
None
(Log#12で変換)
07

連続実行で2/3が落ちる ── 露出問題と照明対策

露出の時系列

Iriun カメラ起動時の自動露出推移 ── warmup 30f の限界

0f 15f 30f 45f 60f 90f 120f 明るい 暗い 成功パターン(室内灯ON) 検出下限 (V≥50/60) warmup終了 30f 失敗パターン(照明不足) ← 暗いまま30f超過 RuntimeError発生 対策①: warmup→60f またはsleep 0.5〜1秒 物理: 室内灯ON
!
持ち越し課題
連続実行で約2/3の頻度で RuntimeError 落ち
仮説1
Iriun 露出リセット
仮説2
背景明度に引っ張られ
暫定対策
室内灯ON で安定
→ Log#12 で warmup_frames 増加、スリープ追加、照明プロトコル策定
08

誤検出4点目の観察と流用/分割の使い分け原則

!
上カメラ側の観察
青シールが4点検出されるケース(ノイズ1点混入)
想定検出
青3点
実観測
青4点(稀)
原因
キーボード上の青み反射
→ Log#12 で detect_tip_from_frame にマット範囲内フィルタを追加予定(今は観察のみ)
設計原則

流用 vs 分割 ── Log#11 で確定した構造判断

共有:環境依存パラメータ HSV閾値 / モルフォロジー / 面積フィルタ / bottom-2ピック import import 上カメラ検出 detect_tip_from_frame() 青3点幾何 → TIP+向き 戻り値: pixel+Unity+方向 横カメラ検出 detect_tip_side() ← 新規 緑2点中点 → TIP pixel 戻り値: pixel のみ 動的追従ループ(Log#12で統合) 分割 検出ロジック
分割するもの 独立実装
  • 対象検出ロジック全体
  • 理由1戻り値の形が異なる(pixel+Unity+向き vs pixel のみ)
  • 理由2既存ループへの副作用を避けるため
  • 方法side_camera_utils.py に detect_tip_side() を新規
共有するもの import 共有
  • 対象環境依存パラメータ
  • 内容HSV閾値・面積フィルタ・モルフォロジー
  • 理由片方だけ調整したとき整合性が壊れるのを防ぐ
  • 方法auto_detect_cameras から import で流用
09

Log#11 が確立した3つの成果と2つの設計原則

成果 01 / カメラ識別
auto_detect_cameras.py
macOS OpenCV index が非決定的な環境で、色パターンのみを根拠に3台から2台を正しく判定。1台を除外する。
成果 02 / 流用不可確定
detect_tip_from_frame の横カメラ流用不可
修正・設定変更ゼロで実行し (None,None,None) の返却を確認。推論でなく観察で確定。仕様確認スクリプトを残置。
成果 03 / TIP検出
detect_tip_side() / tip_pixel Y=959
緑2点中点がY=緑Y平均と完全一致。実機PNGで爪先端寄りへのプロットを確認。掴み高さに整合した。
設計原則

Log#11 で確立した2原則 ── Log#12以降の実装指針

原則 A 検出ロジックは分割する 環境パラメータは共有する HSV閾値・モルフォロジー・面積フィルタは 1ヶ所で定義してimport共有する 検出ロジックは視野方向ごとに独立 → 後の調整で整合性が壊れない 原則 B 流用判定は数字で残す 「見てダメ」より「実行して返値を記録」 関数を呼んで結果を見て 戻り値を文字で記録する 仕様確認スクリプトはコミットして残置 → 同じ確認を二度やらない
本日の結論 ── 横カメラのパイプラインが立った
BEFORE / Log#10まで
上カメラ1系統のみ
Z方向の独立観測✗
カメラ自動識別✗
横カメラ TIP 検出✗
掴み高さ整合✗
→
AFTER / Log#11
2系統パイプライン確立
色パターンでカメラ自動判定◎
detect_tip_side() 新規実装◎
TIP Y=959 が緑Y平均と完全一致◎
Log#12 でループ統合へ→