激安HDMIキャプチャーボードを買ってから、ときどきゲームプレイの録画・配信をしている。OBS Studioというソフトウェアがデファクトらしく、自分もこれを使っている。
便利なことにOBSにはWebSocketで操作できるインタフェースがあり、JavaScriptやPythonからかなり自由に操作することができる。となればソフトウェアエンジニアとしてはプレイログを構造化して残したいわけ。
WebSocket経由でスクリーンショットも随時取得できるので、画像を分析することでたとえばシーン判定はできるが、さらに詳細な情報を取ろうとするとテキスト情報もほしい。クラウドサービスなどに金をかけずに手軽にやるならTessaract一択となるが、素晴らしいソフトウェアではあるものの期待する精度を出すには工夫がいりそう。具体的には、ポケモンの名前は日本語だけでなく中国語の場合もある(左下の「古劍豹」)。
そんな試行錯誤をしているうちにmacOSのプレビュー.appで画像の文字列選択ができることに気付く。精度もかなり高い。これでいいやん。
オンデバイスAPI - 機械学習 - Apple Developer
macos-obs-websocket-ocr
ということで掲題のとおり、OBS WebSocketから得たスクリーンショットから、Visionフレームワークを使ってテキスト抽出をおこなう。インタフェースはいろいろと考えられるが、簡単に使いはじめられることを意識してWebSocketのプロキシとした。これなら既存のクライアントを大きく変えずに使いはじめることができる。まあOBS関係なく使いたいなら、そういうのを書いている人がいるか、いなければすぐ書けるし……。
Releaseからダウンロード、または自分でコンパイルしたら以下のように起動する:
$ obs-websocket-ocr --upstream-url ws://localhost:4455 --hostname local --prot 4456
オプションを指定しなければ上記の通りのデフォルトで起動する。クライアント側は接続先をポート4456にすれば、あとは同様に動くはず。
obs-websocket-ocrは特別なリクエストタイプ __GetTextFromLastScreenshot
だけはOBS側に伝えず、自分で処理する。これは最新の GetSourceScreenshot
の結果からテキストを抽出して返してくれる特別なリクエスト。結果の形式に関してはREADMEを参照してください。
実装の詳細
SwiftのウェブフレームワークであるVaporがWebSocketに対応していたのでこれをそのまま使った。ほどほどに楽に使えてよかったが、バイナリの起動時にサブコマンドを要求するのがデフォルトになっている(app serve
とか app routes
とか)のを迂回するのにちょっとコードが必要だった。フルスタックなものを使うと細かいところで手作りが必要になるのは仕方がない。想定された用途から少しズレているのもあるし。
プロキシとして作用させることで、OBSとの通信セッションを時前で確立する必要がない(パスワードなどを自分が知らなくてもよい)のはよかった。やりとりされるJSONを一部インターセプトすることになるが、ペイロードを細かく分析したいのは一部のフィールドが特定の値だったときのみなのでjson.RawMessage的なものがあるとよかったのだけど、Swift/Codableでは難しそうだった。現状では下層で使われてるJSONSerializationを使うのがよさそう。
クライアントは obs-websocket-py、obsws-python、goobs、obs-websocket-js を試してみたが、requestIdはstringだって書かれてるのに数値で送ってきたり、クライアント側で既定されていないリクエストタイプを発行できないAPIだったりとそれぞれちょっとずつやりづらかった……。けどクライアントが多数あるのはいいことですね。
デモ
というか例が ./example/pokemon-sv-battle-messages にあります。ポケモンSVの対戦中のメッセージを読み取る例。以下のようにかなりいい精度でテキストを検出できています。
----- 古劍豹の つららおとし! ----- ----- 相手のアプリボンの とびつく! ----- 効果はバツグンだ! ----- 古劍豹の 素早さが下がった! ----- 相手を見る様子を見る ----- ----- 相手のアプリボンの ねばねばネット! ----- ----- ねばねばネットが 広かった! ----- 古劍豹の つららおとし! ----- ----- 相手のアプリボンはたおれた! -----
せっかく動画づいてるのでライブコーディングしてみたけど声がボソボソで聞き取りづらかったっす;;
9:00あたりから実際に動いている様子が見えます。
おまけ
作ってるときに書いてたRawなログを以下に残しておきますよ。