FF14のプレイ動画からユーザ名を黒塗りするアプリを作った話

## 最終的にできたもの
こちらで紹介しております。

https://blog.calocenrieti.com/blog/wol_names_blacked_out/

処理前の動画、処理後の動画もyoutubeにアップしてリンクしてありますので、よければ見ていただけると嬉しいです。
処理前：https://youtu.be/oklXwWOAq1A
処理後：https://youtu.be/X-U9s3WTbNo

GUIはFlet、EXE化はpyinstallerで行いました。
2000円くらい払ってMicrosoftストアへの登録もしてみようと思ったのですが、WiX ToolsetとVS2022でのmsiパッケージ化が、フォルダ名やファイルサイズの影響ですぐには解決できそうになかったので、諦めてしまいました。
また元気があれば別のアプローチも含めてトライしてみたいと思っています。

## なぜ作ったのか
ゲームプレイをしていると、 **今の超ファインプレイじゃない？？** とか **え？こんなタイミングでこんなことある？？** みたいな、神プレイと言えるようなものに稀に遭遇します。
Geforce インスタントリプレイで常時録画しているので、さっきの！っていう時は保存をしているのですが、本当は **見て見て！今日はこんなことがあったんだよ！** とyoutubeにその動画をアップして、他の多くのプレイヤーと共有したいと思っていました。
FF14のサービスが始まった直後はあまり言われてはいなかった気がしますが、最近は他のユーザが動画に映っていると公開したらダメ（厳密にいうと、映っている人に了解を取っていないとダメ）という感じで、気軽に動画を公開できない環境になっています。
設定でイニシャル表示にもできるのですが、ゲーム内で声をかける時に、誰なのかわからないので私はあまり好きではありません。

**じゃあ、他のユーザ名を動画から消してしまえばいいじゃない？**
ということで、動画の中からユーザ名を消すプロジェクトが私の中でひっそりと立ち上がりました。

## ゲーム画面の中から消す対象となるユーザ名は
ユーザ名の特徴は次のような感じです。
* アルファベット＋記号（モンスターなどはカナ、かな、漢字、数字）
* 画面設定で、ユーザ名の前にジョブアイコンがつけられる
* キャラクターの上のユーザ名は、画面の手前でも奥でもサイズが変更されない

消す対象となりうるユーザ名は、いくつかあって

* キャラクターの上のユーザ名。画面上を動き回る
* パーティメンバーUIに表示されているユーザ名。画面上では固定
* チャット欄に表示されるユーザ名。チャット欄で発言などの度にスクロールしていく
* ターゲットしているモンスターが、ターゲットしているユーザ名。画面上では固定
* コンテンツ突入時に表示されるキャラクターポートレートのユーザ名。コンテンツ投入直後だけ表示される
* ユーザのペットの上に表示される持ち主のユーザ名。画面上を動き回る

上位のものほど、ユーザ名を消す優先度は高い、と考えていました。
ユーザ名と区別がつきにくく紛らわしいものとして、キャラクターユーザ名の前後につく称号がありました。

## "最終的にできたもの"までの紆余曲折
最終的にはタグ付けしたように、動画から物体検出によってユーザ名を黒塗りするようにしていますが、ここにたどり着くまでにいろいろ試行錯誤をしたので、供養としてここに経過を記しておきます。

### 1.ユーザ名を文字で認識して黒塗りできないか？
下記の記事を参考にして、画面をOCRし文字を認識することで、ユーザ名を黒塗りできないか試みました。

https://qiita.com/mingchun_zhao/items/3bbcafbd1026380b47eb

FF14の日本語環境では、ユーザ名は半角アルファベット+記号となっており、モンスターやチャットなどはカタカナやひらがな、漢字が中心だったため、アルファベットを対象として検出し、その場所を黒塗りすればうまくいくのでは？と思いトライしました。
結果は散々でした。
アルファベットとして認識できているところもありましたが、そもそも文字としても認識されていなかったり。
文書ではなくゲーム画面なので、今となっては当たり前かと思うのですが、一番コストをかけずに実装できそうだったので、とても残念でした。

### 2.アイコンをテンプレートマッチングし黒塗りできないか？
こちらの記事を参考に、OpenCV テンプレートマッチングでの検出を試みました。

https://qiita.com/Ikwus/items/0a978edcd24148a188cb

FF14では画面設定で、ユーザ名の前にジョブアイコンを表示できるようになっています。このアイコンをテンプレートマッチングで認識し、アイコンの後ろを消せばいいのではないか？と考えました。
動画を見ていると、キャラクターの頭上のユーザ名やジョブアイコンは、画面の手前でも奥でも、サイズが変わらないことにも気づきました。ゲーム画面は実写などに比べてはっきりと映っておりテンプレートマッチングと相性が良さそうな気がしていました。また、マッチングのスコアをある閾値以上を消す対象とできればいけるのでは！

結果的には、テンプレートマッチングによるユーザ名の黒塗りは60～80点くらいまで消すことができました。
いろいろ工夫をしたのですが、残りの20点がクリアできなかったので、最終的にはボツとなっています。
テンプレートマッチングによるユーザ名消しで工夫したことを書いてみます。
#### アイコンが隠れる問題
アイコンが全て見えているとそこそこのマッチングでしたが、モンスターの背後など、アイコンが少しでも隠れると当然ながら途端にスコアが大きくさがりました。
アイコンが全部隠れるパターンはテンプレートマッチングでは対応できないのですが、部分的に隠れているパターンには対応しようと、テンプレートをアイコン全部、アイコン左隅1/4、アイコン右隅1/4、の3つで実施しました。
これにより、部分隠れにはある程度のマッチングできるようになりました。但し3パターンでマッチングするため処理時間は3倍になりました。
#### マッチングしたりしなかったりで、チラチラする。
あるフレームではマッチングしていたが、次のフレームではマッチングしていなかったりで、キャラクター名が消えたり消えなかったりと、チラチラする様子がありました。
時間軸を考慮するように、1度検出した場所は、次の次のフレームまで削除対象とするように対応しました。
キャラクターが移動すると、少し引きずるような形にもなりますが、チラチラは減り、悪くない結果ではありました。
#### テンプレートマッチングでの対応が採用できなかったのは
いろいろ工夫をしたのですが、結果は80点で、100点満点ではなかった、というところです。チラチラ対応をしたものの、チラチラが全て無くなったわけではなく、やはりチラチラする様子が見られました。ゲーム画面でクッキリしているのになぜ？とも当初思いましたが、私のゲーム画面は魔法などのエフェクトがモリモリのものとなっており、そのエフェクトがアイコンに重なることで、マッチングの精度が落ちていると考えられました。
また、アイコンが隠れてしまうと当然マッチングせず、キャラクター名は消えません。これも採用できなかった大きな要因でした。

## では物体検出だ！
ゲームをしている人が、これはキャラクターの名前だと認識できるなら、学習させれば物体検出もできるのではないか。教師信号となるアノテーションの作業が大変そうだけど、やってみますか！
というわけで、取り組みました。
ただここまで既に無駄に文章が長くなってしまっているので、もしご興味があれば以下を参照ください。

https://blog.calocenrieti.com/blog/33-tsxhef/

https://blog.calocenrieti.com/blog/vrg1j_8nh/

https://blog.calocenrieti.com/blog/5ew2q1byz0/

個人的な発見というか知見は、
* アノテーションではCVATを利用させていただきました。ツールは最高！と感じましたがアノテーション作業は苦行でした。華やかなAIですが、何事にも地味な仕事の積み上げの上に成り立っていると感じました
* pytorch+cudaの環境で、MMDetectionを使わせていただきました。MMDetectionは様々なモデルを利用できるのが特徴ですが、YOLOが有名だったので、YOLOXがいいのだろうと思い込み利用してみましたが、思っているよりも検出できませんでした。物体検出モデルにもその生まれなどで得意、不得意があるというのが、当たり前ですが私には発見で驚きでした。最終的にはDAB-DETRというモデルを利用しています
* 推論に特化したONNXに期待して取り組んだのですが、入力の解像度が固定でいろいろな解像度となる一般ユーザを対象とすると不向きだったこと。しかも入力画像の解像度を1/4に落として処理するというのが、画面では小さい物体となるキャラクター名は潰れてしまい、検出には不適切だったのがとても残念でした
## FLET、pyinstallerでのEXE化
FLETでGUIを作成しました。最初は勝手がわからず苦労しましたが、公式のサンプルがとても充実しているので、助かりました。

https://flet.dev/docs/

FLET単体でもEXE化できそうだったのですが、いろいろと前提となる作業が多く大変だったのでpyinstallerでEXE化しました。

## pyinstaller＋MMDetection（MMCV）での問題
pyinstaller＋MMDetection（MMCV）では、hookを追加する必要がありました。

https://troubleprogram.blogspot.com/

### うちでは動くが、他のユーザでは動かない問題
EXE化して自分のPCで動作確認した上で、他のユーザに配布するとfbgemm.dllの参照モジュールが見つからずエラーになって起動できない！と言われてかなり焦りました。
DependenciesGui.exeで、 fbgemm.dllを確認し、検索などしてみたところ、原因は、pytorch 2.4 windows版で、libomp140.x86_64.dllがtorchの中にいないためのようです。pytorchでもissueとしてあがっていました。

https://github.com/pytorch/pytorch/issues/131662

### サブフォルダをHidden Importに全て登録する
上記の原因が確定できていない時、うちの環境では起動しているものの、pyinstallerでのModuleNotFoundErrorがあるため、エラーが発生しているのでは？と思い、コツコツとトライアンドエラーで登録していたのですが、あまりに終わらないので、サブフォルダを全てHidden Importに登録をしました。
コマンドプロンプト、LibraCalc、メモ帳で対応しています。
手順は、
1. コマンドプロンプトで、`dir /s /b > tree.csv`として、フォルダ、ファイルのパス一覧をcsvに出力
1. LibraCalcで固定長でインポートし、該当フォルダのレベルでセルを区切る
1. フィルタでファイルのパスに含まれる"."などを除く　（例：PyInstaller\bootloader\images）

1. フィルタで残ったセルを正規表現で置き換え。`=REGEX(B2,"\\","\.","g")`　（例：PyInstaller.bootloader.images）
1. 置き換えた結果をコピーして、まず値貼り付けし、その後、行列入れ替えで横向きに貼り付け
1. 横向きになった結果を、メモ帳に貼り付けて、タブを`','`に置き換え。最初と最後だけ手作業で追加

Excelでの行列入れ替え貼り付け→メモ帳でタブを置き換えは便利なので、皆さんも既にご承知かもしれませんが、ご参考までに記載させていただきました。

## できれば収益化したかった
windowsアプリは広告収入とすることもできず、結局断念しました。
また利用したライブラリが利用しているライブラリにGPLのものが2点ほどあり、それも心理上ではハードルでした。
皆さんは、GPL部分を自分で書き換えたり外したりするのでしょうか。それとも細心の注意をして純粋なMITやApacheのものだけで構成されたライブラリを使っているのでしょうか。
今後はこういう観点でも取り組んでいきたいですね。

最後まで読んでいただき、ありがとうございました。