Files
SignageSystem/docs/head_pose_smoothing.md
T

64 lines
5.0 KiB
Markdown
Raw 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で無効) |
既定値は従来動作(`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°)のしきい値に達しうる。
## 分かったこと
- 生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` と過去ログの扱いも同時に見直すこと。
- 正面を向いた静止時の平均が約-19.3°だった。立ち位置は画像中心から約31px(約1.4°)しか離れておらず、
位置では説明できない。カメラのレンズでなくプレビュー画面を見ていた可能性などを、レンズに目印を付けて再測定して確認する。
- 一部の設定値(`median_one_euro` の β など)は、この静止データのノイズ規模に合わせたもの。距離が変わったら再評価する。