API)を使って自動で書き起こしてみました。プレイヤーが声優事務所のマネージャーとなり、事務所に所属する16人の新人声優を育てていくというゲームです。美晴さんはほんわかおっとりしていながらも、周りの子たちをよく見ていて支えになるお姉さんです。CUE!にはシナリオパートがあり、キャラクター同士やキャラクターとプレイヤーの掛け合いを見ることができます。CUE!ではシナリオは上に挙げたような画像で、画像下部の台詞がテロップのように流れながらキャラクターがしゃべります。サービス開始日から遊んでいたアプリだったので、セリフを使って何か分析したりモデルを組んだりしたいと思ったのですが、そもそもセリフのテキストデータがないため自分でセリフを書き起こしてデータセットを作る必要がありました。セリフのスクリーンショットを用意しなくとも、スマートフォンやタブレットでストーリーを流しっぱなしにしたまま画面を録画した動画をインプットにできると楽です。そのためセリフ画像を切り出す所もコードで対応することにしました。CUE!はもうサービスが終了してしまったゲームであり、手元にはサービス提供中にストーリーを撮った動画が残っているという事情もあります。やったことCUE!のストーリーを録画した動画のmp4ファイルをインプットとし、各セリフの発話キャラクターとセリフ内容を列に、セリフの数だけ行を持つようなcsvファイルを出力しました。インプットに使用する動画はストーリーごとに1本ずつ分かれた動画であり、スマートフォンかタブレットの画面録画か、あるいはキャプチャボードを用いてスマートフォンやタブレットを接続したPCから録画するかのどちらかを想定しています。切り出した画像から、セリフが載っている画像だけを漏れなくダブりなく取り出すセリフはテロップのように流れるので、Step1で切り出した画像には、キャラクターが話し終わる途中でセリフが切れてしまっている画像が含まれていたり、セリフが全て写っている画像がダブっていたりします。セリフが含まれていない画像もあります。これらを取り除きます。APICUE!以外のスマホゲームでも同様のロジックで文字起こしができると思います。動画から静止画を切り出すFFmpegをコマンドラインから使えるようにダウンロードして設定した後、以下をコマンドプロンプトかbashで実行すると動画1秒につき15枚のjpg画像が切り出されます。1時間の動画であれば15×60×60=54000枚の画像が出力されます。動画の解像度や画質にもよりますが、画像1枚で数百KB程度になりますので、ローカルのストレージに十分な容量を確保する必要があります。画像を漏れなくダブりなく取り出す課題Step1で静止画を切り出すことができましたが、得られたキャプチャ画像には問題が3つあります。セリフが表示されていない幕間の場面をキャプチャしているセリフが全て表示し終わる前にキャプチャしているため、セリフが途中で切れているセリフが全て表示し終わってからキャプチャしているが、同じセリフが写っているキャプチャが何枚もある1と2の画像は全て削除して、3のキャプチャはセリフごとに1枚だけ残したいです。解決策まず問題1についてですが、画像を見て分かるように、セリフが映っている画像では、セリフの長方形の領域の背景はほぼ白です。一方で、セリフが表示されていない画像はそうではありません。このことを活かし、まずセリフの領域を切り出し、とりあえず雑にセリフの領域の左上の隅からx軸方向に2px、y軸方向に2pxの1ピクセルと、右下の隅からx軸方向に-2px、y軸方向に-2pxの1ピクセルの2点を取り出し、この2点がいずれも白色でなければその画像にはセリフのボックスが含まれていないとみなしてその画像を削除することにしました。セリフのボックスの位置は全ての画像で固定です。よって、GIMPなどのマウスカーソルを載せた場所の座標を取得できる画像ビューアを用いて事前にボックスの四隅の座標を調べておき、その座標を指定することで元の画像からセリフのボックスを切り出すことができます。255)と比較してCIEDE2000の色差が5以上であれば白色ではないとみなすことにしました。CIEDE2000での色差は、skimage.color.deltaE_ciede2000で求めることができます。次に問題2と3の解決方法についてです。今、セリフの領域を切り出して二値化した画像を用意し、この画像内で「最も右側の黒色の画素(=行列値が0)のx座標」を考えてみます。セリフは最大で2行あるため、セリフのボックスを上下2分割し、下段のセリフを上段のセリフの右端にくっつけた画像で考えます。大津の二値化を行ってから上下を横にくっつけたこちらの画像を用いて考えます。以下、画像iの「最も右側の黒色のピクセルのx座標」を$x_{i}$と表します。iは動画の初めから終わりまで順番に並んでいるものとします。$x_{i}$を求める関数は下のような感じで書けます。idxcalc_max_serifu_px(new_img_text)1573一つ前の画像を見ると、確かにx座標が1570px程度の所までセリフがあります。先程の2の状態では、セリフはテロップのように流れるため、$x_{i}$は広義の単調増加(単調非減少)です。3の状態に移ると、セリフは全て流れ切っているので$x_{i}$は横ばいです。次のセリフの2の状態に移ると、$x_{i}$はその前の$x_{i-1}$と比べ、大きく変動します。そこから再び$x_{i}$は広義の単調増加となります。これが繰り返されます。いま取り出したい「ユニークなセリフの画像」は、次の台詞の2の状態に移る直前の3の状態の画像ですから、すなわち以下の2つの条件を満たすiを見つければよいということになります。この数値はStep1で動画から静止画を取り出す際の、1秒あたり何枚の画像を取り出すかに影響を受けます。以上2つのロジックによって、セリフが映っていない画像は削除した上で、ユニークなセリフの画像のみを取り出すことができます。ストーリーを収録したインプットの動画を1時間とすると、Step1で得られた54000枚の画像から、Step2で500枚程度まで減らすことができました。画像を超解像で拡大するStep2で残った画像全てについて、以降のステップでOCRで画像を読み取りますが、その前に超解像でノイズ除去・拡大し、OpenCVでさらにノイズ除去などの前処理を行う必要があります。Step3は前者、Step4は後者です。waifu2x-caffeを用いました。上のリンクからダウンロード後GUI版を起動し、Step2までで取り出された画像を超解像にかけます。設定値は下記の通りです。8ノイズを除去したうえで縦横2倍に拡大しました。GPUを使って5万枚の画像を4時間程度で処理できました。cv2.imread("image_waifu2x.jpg")view(img)この画像から名前の部分とセリフ部分を切り出します。文字の縁のノイズを除去するためにバイラテラルフィルタをかけるとともに、文字が比較的太く、線と線がつながりやすく見えるため、収縮をかけて線同士を離します。preprocess_img_text(img_text)名前部分とセリフ部分の横幅を揃えて、一枚の画像に結合します。APIを用いてOCRにかけます。月間1000コールまで無料、それ以上は500万コールまでは1000コールごとに1.5ドルとリーズナブルです。なんじゃないかな?'“名前(改行記号)セリフ1行目(改行記号)セリフ2行目”の文字列で返ってきていることを利用し、名前とセリフに分けてpandas.DataFrameにします。なんじゃないかな?']割といい感じに取れていますね!実際はこの後、セリフ部分のテキストの正規化が必要になります。いうのも、以下のような誤認識が頻発するためです。句読点の後にスペースが入る句読点がカンマやピリオドとして認識される...(三点リーダ)が誤認識される・(中黒)として認識されるその他、中黒に似た記号として認識される三点リーダ1個が中黒か中黒に似た記号3個として認識されたりします。2個や4個として認識されたりもします。単語の一部文字だけ全く違う文字として認識される特に記号は難易度が高いですね...。上の画像においても、読点の後に半角スペースが入っていたり、1個の三点リーダが中黒や半角中黒(UnicodeでU+FF65)3個として認識されていたり、また三点リーダが1個減っていたりします。NFKC正規化などで一旦普通の中黒に置き換えてから三点リーダに置換する必要があります。おわりにOCR自体はAPIに投げるだけなので楽ですね。時間がかかったのはStep2の漏れなくダブりなく画像を取り出すロジックの考案とStep4の前処理のロジック、Step5のOCRの後のテキストの正規化でした。Anubis.suzuna。