わくわく計算ライフ

ドムプラをキメつづけるブログになりつつある。

斬り麿 v0.4.0 とそのアルゴリズム

格ゲー動画を自動で試合ごとにカッティングするツール斬り麿のv0.4.0をリリースしました。
今回で機能的の基本的な部分は一旦完成になります。
アップデート内容とアルゴリズムについて簡単に解説したいと思います。

gitlab.com

1. v0.4.0 アップデート内容

今回は細かいTypo修正やリファクタリングを除くと

  • Advanced Variable Geo 2への暫定対応

が追加項目になります。
どのくらい暫定かというと、PS3アーカイブ版での出力のみ対応ということになります。
なんで、それだけなん?みたいな話はアルゴリズムのところも絡めて後程説明します。

2. アルゴリズム解説

斬り麿のアルゴリズムについて説明します。

2.1. そもそもの斬り麿の機能

斬り麿の本質的な機能は、ランクマに潜るときなどに録画ONにしっぱなしにした動画の中から試合の部分について、試合の開始時間及び終了時間を画像認識アルゴリズムを用いて抽出する機能です。
抽出した、試合の開始/終了時間の情報をもとに、動画編集ソフトウェアであるffmpegを実行する際のコマンド引数を自動生成し、生成したコマンドを用いてffmpegで動画を切り出しています。
なので、実際に動画を切り出している部分に関しては現バージョンでは完全にffmpeg任せです。

ffmpeg.org

2.2. 1試合の単位

1試合をどのようにとらえているかを、次図に示します。
基本的にはRound 1のRound開始表示Result, 勝利画面までを1試合として切っています。

f:id:savaki:20211230174206p:plain
試合の単位

現在の実装では、上図のROUND 1を検出した時間から固定数フレーム遡った位置を試合の開始時刻とし、勝利画面を検出した時間から固定フレーム遡った位置を試合終了位置としています。
なので、オンライン対戦のあるゲーム(現在の対応タイトルの中ではGGST)では、通信チェックとかの影響で終了位置は主に後ろにずれる可能性があります。

また、現在は乱入待ち中のCPU戦の試合であっても勝利画面が出てきた場合は切り出す作りになっています。
最近のゲームはオンラインの待ち受けは大体トレモですし、古いゲームは逆にそんなのなくてVSモードで連戦していると思うので実用上は問題ないと思います。

2.3. キー画面の検出アルゴリズム

画像切り出しのキーとなる画面をどのように検出しているかについて解説します。
現状のある語はめちゃくちゃシンプルで、固定位置だけを見て、画像を2値化したうえでパターンマッチングをしています。
ホントはカッコいいアルゴとかを使おうと思ったのですが、無くてもなんとかなってしまったので…。
オッカムの剃刀!(言いたかっただけ)

2.3.1 マッチング領域の指定

処理時間や対ノイズ性を考慮して、検出したい各パターンのマッチング領域を限定しています。
AVG2の例を挙げます。

f:id:savaki:20211230180928p:plain
AVG2 ROUND 1 領域指定例

ここでは、入力画像(録画時は1280x720)を960x540にリサイズして内部的に処理を行っています。
この画像上で、ROUND1を検出するのは図中の黄色の枠の領域のみの画像を見ています。
画像からわかるかと思いますが、PSアーカイブはもともと4:3の時代のゲームを16:9のHDMI出力の機種でエミュレーションして動いているため左右にはオリジナルに無い余白ができます。
余白の部分を無視できるように、中央のゲーム画像が表示される領域を切ってやればおそらくオリジナルとも共通で行けると思うのですが、今回は以下の理由により対応していません。

  1. PSアーカイブ中央部分とオリジナルとでアスペクト比が一致している自信が無い
    手持ちに実機か動画があれば解決する話ではあるのですが、現時点ではなかったので仕方なくPS3で実行して検証を行っています。
  2. 余白を切る/切らないは好みによる
    レトロゲーのプレイ配信をしているチャネル等をいくつか参考にしたのですが、余白の部分を切り捨てた上で再配置している人、そのまま使って余白になんか情報を入れる人などでパターンがいくつかあったので、なら無理に加工とかはしない方が良いよね、と思って余白無駄だけどHDMIキャプチャの出力まんまを基準としました。
  3. 範囲を指定して!となると利用者のハードルが上がる
    AviUtlとかで該当するフレームをキャプチャして、ImageJなどでpixel単位で範囲選択をして、パラメータを設定できる人というのはあんま居ないと思います。
    かといって、それやる用のGUIを新たに作るのも面倒だったので。(将来的には気分が乗ればやるかもしれませんが)

2.3.2 画像の2値化

画像の2値化はめちゃくちゃ単純で、斬り麿では色は24bitRGBで取り扱っており、各成分は0~255の値をとります。
例えば黒なら(R, G, B) =(0, 0, 0)、赤なら(255, 0, 0)と言った具合です。
先ほどの例でから持ってくると、ROUND 1の文字のうち1の部分の色を調べて代表色とします。
一見赤1色に見えますが、動画の圧縮アルゴリズムの関係やデザイナーがうっすらグラデーションをつけているケースもあるので、代表的なフレームで局所のヒストグラムをImageJで確認しながら決めています。
で、毎フレームテンプレートと同じ領域を代表値で2値化するのですが、代表色とのユークリッド距離が一定値より小さい物を前景(1)とし、それ以外を背景(0)としています。

f:id:savaki:20211230184423p:plain
2値化例

本来はテンプレートは手で数字の1の部分を綺麗に切り出してその中での色の平均とかを代表色にした方が良いのですが、面倒なのでこの程度の精度でやっています。
いや、実際数が増えると大変だし、色のばらつきを見るために何フレームかは観察してから決めているのでこれでも結構めんどい。
この図からみても、1色に見える1の部分でも色の揺らぎがあることが分かると思います。

2.3.3 パターンマッチングの評価

画素単位の正解・不正解は、テンプレートおよび当該フレームのマッチング対象領域を2値化したあと、どちらでも同じ値を取っていればその画素は正解、違う値を取っていれば不正解とします。 パターンマッチング全体の評価は面積の逆比で重みづけした過重正解率を使っています。
言っても話は簡単で、先ほどの2値化の例でいうと、1の数字の前景部分(1)と背景部分(0)でそれぞれ、正解率を出してその平均を取っています。

なぜかというと文字の様なものは、案外細いパターンが多く来る場合が多く、面積で言うと、対象の矩形の領域で文字部分が占める割合が25%ぐらいになることも良くあります。
そうすると、文字と関係ない色で埋め尽くされたフレームでも全体で正解率を出してしまうと正解率75%!結構似てるかも??みたいな判定が下されてしまいます。
これを、文字の部分、背景の部分でそれぞれ正解率を出して平均を取ることで、このようなケースでも正解率を50%に抑えることができます。

3. まとめ&今後の予定

という訳で、今回は斬り麿のアルゴリズムについて簡単に説明しました。
この程度の簡単なアルゴリズムでも結構いろいろできるもんです。

今後の予定としては、データが上手く集まればGGSTやAVG2の改修の準備をしつつ、現在Pythonで書いている画像認識部分をRustに移植する作業を進めようと思います。
使ってみてのバグ報告などがあった場合は、なるべく対応はしようと思っています。