WiFi CSIでカメラレス姿勢推定がしたかった話

WiFi電波(CSI: Channel State Information)だけでカメラなしに人のポーズを推定できるらしい、という話が去年バズっていて。CMUの論文 DensePose from WiFi がきっかけで、「壁越しに人体が見える」みたいなニュースが色々出てた。

自分もインスタレーション向けにカメラレスでいけるなら最高だなと思い、ESP32-S3を4台買って実際にやってみた。結論から言うとWiFi CSIからの姿勢推定は現状かなり厳しくて、方向転換して別のことをやっている。その過程で元ネタのひとつがフェイクだったり、研究分野全体にデータリーク問題があったりと色々分かったので、メモとして残しておく。


ハードウェア構成

ESPr Developer S3 Type-C(スイッチサイエンス、¥3,740/台)を4台。ESP32-S3のCSI APIでWiFi電波の位相・振幅の生データが取れる。

2m四方の部屋の四隅に配置して、1台をTX(UDPブロードキャスト100Hz)、3台をRXにする構成。各RXが約40HzでCSIデータを取得する。ファームウェアは ESP-IDF v5.2で自前ビルド、Python側で Rerun を使ってリアルタイム可視化。

    左前(RX) ────── 右前(RX)
       │                │
       │    2m × 2m     │
       │                │
    左後(RX) ────── 右後(TX)

地味なハマりが多かった。プロジェクトパスに R&D ってアンパサンドが入っててCMakeが壊れるとか(ビルドは /tmp/ にコピーしてやった)。esp_wifi_80211_tx() の null-data フレームがESP32-S3のCSI抽出をほとんどトリガーしない(0.1Hzしか出ない)のでUDPブロードキャストに切り替えたとか。provision.pyがNVSパーティション全体を上書きするので --tx-mac だけ指定するとWiFi認証情報が消えるとか。この辺は公式ドキュメントに書いてない。


動き検出まではいけた

CSIの振幅データからMVS(Moving Variance Segmentation)で動き検出は実装できた。ESPectre のアルゴリズムを参考にしている。

  1. 起動時60パケットはAGC安定待ちで捨てる
  2. 次の200パケットでキャリブレーション — NBVI(Normalized Baseline Variability Index)で安定した12本のサブキャリアを選定
  3. 選定サブキャリアのstd(spatial turbulence)→ Hampelフィルタで外れ値除去 → 移動分散
  4. 閾値はノードごとの p95 × 3.0

起動15秒のキャリブレーションの後、静止時 motion ≈ 0.000 で安定。ここまでは順調だった。

因みにESP32の位相データはCFO(搬送波周波数オフセット)とSFO(サンプリング周波数オフセット)のノイズで毎フレームランダムにオフセットが乗るので使えない。最初は位相ベースの動き検出を試したんだけど、静止時でも値が1〜7くらい振れて全然ダメだった。振幅ベース一択。


元ネタがフェイクだった件

で、ここからが本題なのだけど。

元々参考にしてた RuView(ruvnet/wifi-densepose) がフェイクだった。GitHub trendingの上位に入ってスター数万。CMUの論文をオープンソース実装したと謳ってたやつ。

Claudeと一緒にコード読みながら実際に動かしてみて、結果がおかしくて。話しながら「これどうやってもならんやろ」ってなった笑

中身を見ていくと:

  • ESP32CSIParser.parse()np.random.rand() を返す(実データをパースしてない)
  • PyTorchモデルのアーキテクチャはあるけど、学習済み重みゼロ、学習スクリプトなし、データセットなし
  • テストが通るのはランダム数値生成器をテストしてるから(「100%テストカバレッジ」)
  • 「94.2%の姿勢検出精度」と主張しているが、学習してないので物理的に不可能
  • Dockerイメージ ruvnet/wifi-densepose:latest は監査当時Docker Hubに存在しなかった(その後追加された)

最初は「まだプレースホルダーなのかな」くらいに思ってたけど、他のパーサーも全部同じで、READMEの精度表記と辻褄が合わないってなってからは芋づる式に出てきた。

開発者本人は「キャリブレーションしてない人がfakeと騒いでるだけ」「impatience」と主張してて、SHA-256 Proof(witness bundle)も出してた。ただこれ「このバイナリが改竄されていない」証明であって「推論が正確である」証明ではない。repoのブランチ名やコミットログを見るとClaude AIで生成されたコードが多く、LLMは数学的に正しいFFTやフィルタのコードを生成できるけど学習済みモデルは生成できない。

その後、複数の独立した監査(Claude、GPT、Gemini)で完全にハリボテと確定。作者は批判issueを削除した後、”fix: Replace mock/placeholder code with real implementations” というコミットでランダムデータ生成を実コードに置き換えた(実質認めた形)。Hacker News でも「AI slop」(AI生成のハリボテ)という意見が大勢を占めている。

監査ドキュメントは fork に保存されていて、Cybernewsbyteiota でも取り上げられている。

ただ、RuViewの信号処理コード(Doppler FFT、PSD、Hampelフィルタ、CUSUM変化点検出)の数学的な部分は正しかったので、そこだけは自分のテストスケッチに移植して使っている。全部が嘘ではなかった。


WiFi CSI研究全体のデータリーク問題

RuView単体の問題じゃなかった。

アルバータ大の Andrew Walsh(MD, PhD)が Medium記事 で指摘していた件で、多くのWiFi CSI論文が時系列データをランダム分割していて、同一人物のフレームが train と test に混入している。GIGAZINE でも報じられてた。

2024-2025年にかけて、この問題を指摘する査読付き論文が4本出ている:

99%超の精度を主張してる論文がゴロゴロあるけど、被験者ベースのクロスバリデーションをやると大幅に下がるらしい。正式な論文撤回はまだない。

元になったCMUの “DensePose from WiFi” 自体は正当な研究っぽいけど、コードもモデルも未公開。X-Fi(ICLR 2025)は特に疑惑なく、チェックポイントも公開されている。ただしTP-Link/Atheros CSI Tool用のフォーマット(114サブキャリア)で、ESP32の56サブキャリアとは互換性がない。SenseFi は行動認識のみで姿勢推定ではない。データセットとしては MM-Fi(NeurIPS 2023、CSI + 17キーポイント3D)が一番まともだけど、これもTP-Link/Atheros。

結局、ESP32-S3でそのまま動く学習済みモデルは世の中に存在しない。やるなら、カメラ併用でground truthを収集して自前で学習するしかない。CSIチップ品質のランキングも ESP32-C5 > C6 > C3 ≈ S3 で、S3は下位。


で、方向転換した

せっかく機材買ったし、WiFi信号をセンサー代わりにするアイデア自体は、インスタレーションの体験者の匿名性を担保するのにとても魅力的なので。姿勢推定は諦めて、CSI単独で実際にできることを実装テストしている。全部カメラなし、MLモデルなし、純粋な信号処理。

3状態アクティビティ分類 — ABSENT(不在)/ STILL(静止)/ ACTIVE(活動中)をノードごとにリアルタイム判定。振幅分散 + モーション帯域(0.5-3.0Hz)のエネルギーでルールベース分類。

呼吸検出 — 人が静止しているとき、胸の微小な動き(0.1-0.5Hz)がWiFiマルチパスに乗る。バンドパスフィルタで呼吸帯域を分離してレート推定。期待値は12-20回/分 = 0.2-0.33Hz。

Dopplerスペクトラム + PSD — 振幅の時間差分のFFTでDoppler推定(位相ベースは前述の通り使えない)。帯域別パワー計算と、CUSUM変化点検出で状態遷移を捉える。

サブキャリア×時間ヒートマップ — 56サブキャリアの振幅をスペクトログラム的に可視化。動きがあるとどの帯域がどう変化するかが直感的に見える。

位置検知 — TX→各RXの3リンクそれぞれのCSI変化量の差から、部屋のどのエリアに人がいるかを推定。各リンクが異なるマルチパスを測定するので、ノード近くでの動きがそのノードのCSIに選択的に現れる。せめて室内位置くらいは取れるところまで持っていきたい。

CSIの生データを触ってると電波が生き物みたいに揺らいでるのは面白くて、呼吸で電波が変わるとか、コンセプトとしてはむしろ姿勢推定よりこっちの方がアート的に面白い気もする。自分が作ってるインスタレーション作品はどれも人の存在に反応するもので、姿勢の17キーポイントまでは正直いらなくて、Presence(いる/いない)とActivity(静止/動き)が取れれば成立する。呼吸検出ができたら建築が人と一緒に呼吸する表現もできる。カメラを使わないってのがコンセプト的にも技術的にも重要で、プライバシーフリーな空間センシング。


参照

論文・データセット:

RuView フェイク問題:

データリーク問題:

参考実装:

ghost-sense: