6 台の防犯カメラに野鳥観察を教える
自前の BirdNET-Go に 6 台の UniFi カメラのマイクを繋ぐと、夜はハリネズミやキツネまで検出できた。ただしカメラの音声は 16 kHz までしかなく、高く鳴くミソサザイはそもそも録れていない。
日本語
コピー
家の周りにはマイク付きの UniFi カメラが6台あるが、一度も使ったことがない。一方で庭には名前も知らない鳥ばかりで、踏み潰された芝生を見る限り、夜にはハリネズミとキツネが列をなして通っているらしい。だから BirdNET-Go——BirdNET モデルをリアルタイム音声に対して動かせるセルフホスト型の解析ツール——をたまたま見つけたとき、計画は自然に固まった。カメラのマイクに繋いで、鳥を見る仕事を機械に任せるのだ。
コミュニティには既成の Proxmox スクリプトがあり、インストールが一番退屈な工程だろうと思っていた。そうではなかった。
朝食前に落ちた2つのコンテナ
最初のデプロイは、対話式のストレージ選択プロンプトで静かに死んだ。新しいコミュニティスクリプトは、ストレージプールを単一アプリの変数ファイルから読むようになっていた。30以上のコンテナがずっと頼ってきたグローバルなデフォルト値ではなく。2回目のデプロイは起動したがデフォルトゲートウェイがなかった。appdefaults モードではグローバルのゲートウェイ設定も同じように無視されるからだ。次にインストーラが CPU 80% で空回りし、解決できない Debian ミラーを探し続けた。3回目、6つのキーをアプリファイルに書いたら3分で入った。
音声の取り込みは意外なほどすんなりいった。UniFi Protect の統合 API は既存のコントローラキーをそのまま受け入れ、カメラごとに1回呼ぶだけで RTSPS アドレスが返ってきた:
GET /proxy/protect/integration/v1/cameras//rtsps-stream
6本のストリーム、6つの ffmpeg プロセスが映像を剥がし、1分も経たないうちにダッシュボードにミソサザイが現れた。
ありえない鳥が現れた朝
最初の夜明けのコーラスは1000件を超える識別結果を生み、種のリストは一目でおかしいと分かった。カワガラス——川辺に暮らす鳥だ。9月初旬に61羽のコマツグミ、どのコマツグミの到着よりも6週間早い。カワセミ、ヨシガモ、ヒゲガラ。これは路面騒音だった。アスファルトを碾くタイヤのシューッという音は広帯域で、川辺のデータで訓練されたモデルにはカワガラスに聞こえる。
本当の増幅器はもっと分かりにくかった。平均信頼度は0.5、しきい値は0.7なので、理屈の上では結果は出ないはずだ——動的しきい値を知るまでは。一度の高信頼度の誤検出がその種のしきい値を0.2まで下げ、丸一日維持する。すると似た音がすべて滑り込んでくる。26種のしきい値が騒音によって引き下げられていた。
修正にはプロジェクト付属のチューニングツール群を使った。ただし今回は正しく。深層検出は同一種が時間窓内で複数回ヒットし、互いに裏付け合うことを要求する。より厳しい音域フィルタは、これだけで候補リストを6,522種から139種に絞り、カワガラスは弾き出された。音域内に残った3つの騒音の重灾区には、種ごとに0.9のしきい値を設定。さらに250 Hzのハイパスでエンジンの低周波の轟きを除去した。データベースも空にしたので、これ以降はすべてクリーンな基準だ。
そして調べすぎた。誤検出フィルタのレベル3は5回の重複ヒットを要求し、29の候補が次々と捨てられていくのを眺めることになった。あるヨーロッパカヤクグリはすでに4回ヒットしていて、あと1回で捨てられるところだった。プライバシーフィルタは人声の可能性があるものをすべてミュートする設定にしたが、0.06の信頼度で車の交通音にも反応した。レベルを2に戻し、プライバシーしきい値を0.3にしたら、鳥が戻ってきた。
クラッシュし、その後何事もなかった GPU
このマシンには Intel の内蔵 GPU があり、BirdNET-Go は OpenVINO 経由でその上で推論を動かせる。有効にするとサービスはクラッシュループに陥った:free(): invalid next size、ドライバのデバイス列挙中に glibc のヒープ破損で abort。CPU に固定して、寝た。
翌朝、本気で解決しようとした。同じ列挙を行う最小の C プログラムはきれいに動いた。valgrind でチェック:エラーゼロ。そして本物のバイナリももうクラッシュしなかった。この2日の間にコンテナのコア数とスレッド設定を変え、ドライバのシェーダーキャッシュを温めたが、どれが効いたのか今でも分からない。今は両方のモデルが内蔵 GPU で動いており、CPU 使用率は30%から10%未満に下がった。
直せるのは故障モードだ。検出中のドライバの abort がアナライザ全体を巻き込むべきではない。そこでパッチを書いた:デバイス列挙を短命の子プロセスで実行し、子プロセスが死んだら ONNX Runtime にフォールバックする。列挙の互換レイヤに abort() を注入して故障テストを行い、子プロセスが元と全く同じ特徴でクラッシュするのを見届けたが、サービスは意に介さず動き続けた。これは今や上流のissueとpull requestになっている。2つのレビューボットが最初のバージョンから3つの本物の問題を見つけ出し、赤面ものだが実際に役に立った。
ハリネズミ、そしてあの落とし穴
夜勤の時間帯には、Google の Perch v2 を2つ目のモデルとして追加した。そのラベルセットには Red Fox、European Hedgehog、Badger がある。夜行性の分類群は昼間のフィルタのリストに加え、日中に識別された「fox」は騒音として捨てられるようにした。
そしてあの落とし穴にぶつかった——きっかけは、玄関に立っていれば聞こえる鳥のスコアがわずか0.55しかないのはなぜかと調べたことだった。ffprobe でカメラのストリームを見た。すべての画質段階で音声は同じ:AAC、モノラル、16 kHz。つまり信号の上限が8 kHzに張り付き、鳴禽の相当な部分のエネルギーはその周波数より上にある。モデルが聞く庭は、高域がこもって削られたバージョンであり、どんなにパラメータを調整しても、そもそも採取されなかった周波数は取り戻せない。
だからカメラはカラス科、ムクドリ、キツネに任せておこう。どれも8 kHz以下にしっかり収まっている。ミソサザイとキクイタダキについては、次の一手は庭に小さなボードを置き、まともな48 kHzマイクを繋いで7本目のストリームとして送り返すことだ。ハリネズミはというと、今夜来るらしい。