Files
SignageSystem/docs/head_pose_smoothing.md

95 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 頭部角度(Yaw)の平滑化フィルタと静止時ゆらぎ
静止していてもYawが変化する問題に対する調査記録と、選択可能なフィルタの説明。
## 設定(`config/default.yaml` の `head_pose`)
| キー | 既定値 | 内容 |
|---|---|---|
| `smoothing_enabled` | true | false で平滑化なし(`none` と同じ) |
| `smoothing_method` | `ema` | `none` / `ema` / `median_ema` / `hampel_ema` / `one_euro` / `median_one_euro` |
| `ema_alpha` | 0.4 | EMA係数。`median_ema`・`hampel_ema` でも使用 |
| `smoothing_window` | 5 | 窓幅(サンプル数)。窓の時間幅は姿勢更新レートに依存する |
| `hampel_threshold` | 3.0 | Hampel判定のしきい値(頑健σの倍数) |
| `one_euro_min_cutoff` / `one_euro_beta` / `one_euro_d_cutoff` | 1.0 / 0.0 / 1.0 | One Euro Filter のパラメータ |
| `filter_reset_gap_sec` | 0.0 | 姿勢サンプルがこれより離れたらフィルタ状態をリセット(0で無効) |
| `smoothing_window_sec` | 0.0 | 0より大きいと、中央値系フィルタの窓を「直近N秒」で指定する(`smoothing_window` より優先)。更新レートが変わってもフィルタの強さが変わらない |
| `max_reprojection_error_ratio` | 0.0 | 再投影誤差が「目尻間距離×この値」を超えた姿勢を棄却(0で無効)。正常フレームの比率は約0.07、破綻フレームは0.4以上 |
既定値は従来動作(`ema`, α=0.4)と同じ。新キーはYAMLに書かなくてもよい(既定値が使われる)。
フィルタ本体は `src/yaw_filters.py`。
## 計測と比較の手順
1. 正面静止で30〜60秒ログを取る(`data/logs/face/*_face_frames.csv`。`raw_yaw` 列が生値)。
2. `python -m src.yaw_noise_report <csv> --track-id N --t-start 4 --t-end 44 --csv out.csv`
3. 各フィルタのSD・最大変動幅・フレーム間ジッタを比較する。
遅れは静止データからは測れないので、階段・ランプ入力のシミュレーションまたは実際の振り向き試行で確認する。
## 計測結果(2026-10-01, 研究室, 正面・3m・静止, 4〜44秒, n=714, 約17.8Hz, 1280x720)
| フィルタ | SD | 最大変動幅 | 階段90%遅れ | ランプ遅れ |
|---|---:|---:|---:|---:|
| 生Yaw | 6.27° | 49.6° | 0 | 0 |
| ema 0.4(従来) | 3.77° | 28.1° | 0.22秒 | 0.06秒 |
| median5 + ema 0.4 | 1.92° | 17.1° | 0.34秒 | 0.17秒 |
| median5 + one_euro(1.0, β=0.02) | 1.96° | 20.0° | 0.22秒 | 0.11秒 |
| hampel w7 k3 + ema 0.4 | 2.17° | 21.9° | 0.39秒 | - |
- 遅れは、サンプル間隔0.056秒で、0→30°のステップ入力が27°に達するまでの時間(ランプは60°/sで増加する入力に対する遅れ)。
- 1か所・1距離・1人の静止データによる比較。実際の振り向き中の挙動は未検証。
- 現行(ema 0.4)の出力は、静止していても3σ=11.3°、最大変動幅28°あり、`subtle`(10°)・`weak`(20°)のしきい値に達しうる。
## 追加計測(2026-10-02, 研究室, 正面・3m・静止+移動, median5+EMA 0.4設定時のログ, 約23.1Hz)
静止区間(フレーム250〜1390, 49.3秒):
| フィルタ | SD | 最大変動幅 | 平均から8°超の外れ | 階段90%遅れ |
|---|---:|---:|---:|---:|
| 生Yaw | 9.02° | 72.6° | - | 0 |
| ema 0.4(旧デフォルト) | 5.70° | 54.3° | 11.5% | 0.17秒 |
| median5 + ema 0.4 | 3.50° | 38.8° | 2.5% | 0.26秒 |
| median 0.45秒 + ema 0.5 | 2.27° | 31.0° | 0.5% | 0.35秒 |
| **median 0.55秒 + ema 0.5** | **1.85°** | **9.9°** | **0.0%** | 0.39秒 |
- 実機の `yaw` 列は、生Yawにオフラインで同じフィルタをかけた結果と一致した(最大差 9e-7)。
- 生Yawの+側スパイクは8.7%。連続長は1フレーム46回・2連続17回・3連続3回・4連続1回・6連続1回。
中央値は、窓n個で(n-1)/2連続までのスパイクを除去できる。median5(n=5)は2連続まで。
- 移動しながら向く区間(フレーム218〜240)で、生Yawは-68.7°→-23.1°。90%到達は
median5+EMA0.4が0.76秒、旧デフォルトが0.67秒、median 0.55秒+EMA0.5が0.80秒(50%到達は0.31/0.09/0.40秒)。
窓を広げても、実際の振り向きでの追加の遅れは小さい。
- 再投影誤差/目尻間距離の比は、正常フレームで p99 でも0.12〜0.19。0.4以上のフレームはすべて、
目尻間距離が20px以下の小さい顔(入退場の移動中)で、Yawも破綻していた(例: 比104.9でYaw -1.0°、比150でYaw -66.7°)。
比0.3で除外すると、静止区間のフレームは1つも除外されず、破綻フレームだけが落ちる。
静止時のスパイクとは相関しない(スパイク1.58px、その他1.57px)。
- 静止中の中央値からの逸脱が10°を超えた状態の連続時間は、旧デフォルトで最長0.27秒、median5で0.22秒、
median 0.55秒窓ではさらに短い。`min_turn_duration_sec`(0.3秒)との余裕は、窓を広げるほど大きくなる。
## 分かったこと
- 生Yawの分散の大部分は、フレームごとの独立なノイズと、単発の+側スパイク(約5%)による。
中央値系フィルタ(median / Hampel)が単発スパイクに有効。
- ROI・bboxのフレーム間ゆらぎは0.3〜0.5px程度で、原因ではない。
- 再投影誤差はスパイクと相関せず(外れ値フレーム1.59px、その他1.58px)、除外基準には使えない。
- 顔が小さい(3mで目尻間距離が約24px)ため、ランドマーク0.5px程度の誤差で、Yawの標準偏差は4〜5°程度になる
(シミュレーションで確認。実測の6.3°と同水準)。ROIを拡大しても元画素数は増えない。
→ 精度を上げるには、距離を縮める・解像度を上げる・望遠にするなど、顔を大きく映す対策が最も効く。
- `useExtrinsicGuess`(前フレームの解を初期値にする)はシミュレーション上で効果なし。実装していない。
- 焦点距離の仮定(現行はフレーム幅=1280px、実際は約800px相当と推定)は、Yawへの影響が1°未満。
## 注意点(要確認)
- **Yawの符号が反転している可能性が高い。** `src/head_pose.py` の `MODEL_POINTS` はYを下向きに直した一方で
zの符号は据え置かれ、目・口が鼻先より手前にある(奥行きが反転した)顔になっている。
シミュレーションでは、実際の頭部回転に対して推定Yawの符号が反転した(真値+15°→推定-15°、大きさは保たれる)。
`turn_detection.signage_yaw_direction` が実測に合わせて設定されていれば結果は一貫するが、
「正のYaw=物理的にどちら向きか」を前提にした解釈・報告では注意が必要。
モデルを直す場合は符号が変わるので、`signage_yaw_direction` と過去ログの扱いも同時に見直すこと。
- 正面を向いた静止時のYaw中央値が、2回の計測(2026-10-01, 10-02)とも約-20°だった(bbox中心はそれぞれ671px, 680px。
画像中心640pxから約30〜40px、約2°しか離れていない)。立ち位置では説明できない。
レンズとプレビュー画面は物理的に離れているため、プレビューを見ていた可能性は除外されている。
原因は未解明(3Dモデル点と実際の顔の不一致、頭部の実際の向きなど)。
距離を変えて(例: 1.5m)測り、オフセットが距離で変わるか(小さい顔による見かけの偏り)、
変わらないか(系統的なずれ)を確認する。振り向き検出は基準Yawとの差を使うので、固定のオフセットは影響しない。
- 一部の設定値(`median_one_euro` の β など)は、この静止データのノイズ規模に合わせたもの。距離が変わったら再評価する。