さあ帰ろう、ペダルをこいで(ディープ・ラーニングで)

2008年のブルガリア映画「さあ帰ろう、ペダルをこいで」は、交通事故で記憶喪失になった青年が祖父とともにタンデム(二人乗り)自転車で故郷へ向かう物語。ほのぼのとしたタイトルやスチル写真、そして「世界各国の人々の心に大きな感動を残した、心あたたまるロードムービー」という謳い文句を見れば、甘酸っぱい予感しかしない。だが、本当にそうだろうか?

そこで、先月取った杵柄、ディープ・ラーニングによる物体検出YOLO3を用いて、この映画での自転車の出現具合を調べてみた。以前と記事と同じく、フレーム(映像の一コマ)ごとに検出された自転車が画面に占める割合に信頼度を掛けた数値をグラフ化。これはかなり空虚な印象になった。自転車が登場しているのは間違いなさそうだが、ロードムービーとは呼べない疑惑が持ち上がる。

また、総フレーム198,931に対して、自転車が検出され、その信頼度が0.8以上であるフレーム数は3,831であった。つまり、小さく描かれている場合も含めて自転車が登場するシーンは全体の2%しかないわけだ。これでさらに自転車映画ではない疑惑が強まる。なお、信頼度を0.8としたのは経験則だが、数値が低い場合は誤認識かもしれないので、処理する対象から除外している。

自転車と誤認識された例

次に自転車が映っているシーンだけを集めてみる。これも信頼度が0.8以上とすれば全体の2%となり、1時間50分少々の映画が2分16秒ほどになった。この短編ムービーはなかなか爽快で、自転車で旅する気分が満喫できる。これならロードムービーと言えそう。だが、これは2%でしかない。一体どのようなストーリーなのか皆目見当がつかない。物語は残る98%でも進行しているのだから無理もない。


パスワードはcritical

目立たない自転車が認識された例

それでは最後に映画全編を普通に鑑賞しよう。旧共産社会での弾圧、過酷な難民生活、そして交通事故で両親を失い、青年は記憶喪失になる。甘酸っぱい雰囲気はどこにもない。だが、祖父がタンデム自転車で青年を連れ出す。都市を抜け、草原を駆け、山岳に挑み、祖国を目指す。救済と再生、恋愛と家族愛。2%とは言え、自転車の旅こそが物語を紡ぐ縦糸であったことが分かる。以下は予告編だ。

さて、特定のシーンを抜粋したビデオを作成するには、まずKeras-Yolo3によって映像を分析する。これは前回と同じ処理だ。分析結果のビデオを出力する必要はないが、分析結果のテキストはファイルに保存する必要がある。mediaフォルダにあるtest.movというビデオ・ファイルを処理するのであれば、ターミナルで以下のコマンドを打つことになる。

python yolo_video.py --input media/test.mov | tee media/test-result.txt

分析結果が得られれば、以下のPythonプログラムで抜粋ビデオを生成する。これも前回同様に、分析結果のテキストからフレームごとに検出した物体を処理する。それが対象物であり、信頼度が一定値以上であれば、そのフレームを出力すれば良い。実際には連続するフレームから成るクリップを作ってリストに登録しておき、最後にビデオ・ファイルへ出力している。ビデオの入出力はmoviepyを利用した。

from moviepy.editor import *

input_path = 'media/test.mov'           # 処理するビデオ・ファイルのパス
data_path = 'media/test-result.txt'     # 分析したデータ・ファイルのパス(Keras-Yolo3の出力)
output_path = 'media/test-bicycle.mp4'  # 抜粋して出力するビデオ・ファイルのパス
keyword = 'bicycle'                     # 抜粋する物体のキーワード
accuracy_threshold = 0.8                # 物体認識の信頼度

input = VideoFileClip(input_path, audio=True)   # ビデオ・ファイルを開く
frame_duration = 1.0 / input.fps                # 1フレームの時間長を求める

with open(data_path) as data:   # データ・ファイルを開く

    lines = data.readlines()    # データを読み込む
    
    # 変数を初期化
    max_accuracy = 0.0
    frame_count = 0
    start_frame = -1
    clip_list = []
    
    for i in range(len(lines)): # データを1行ずつ処理

        items = lines[i].split()    # 1行を要素に分解
        
        if  items[0] == keyword:    # 検出した物体が指定キーワードなら
            accuracy = float(items[1])  # 信頼度を求めて
            if accuracy > max_accuracy: # 1フレーム内の最大信頼度より大きければ
                max_accuracy = accuracy # 最大信頼度を更新
                         
        if items[0].replace('.','').isdigit():  # 1フレームの終わりなら
            if max_accuracy >= accuracy_threshold:  # 最大信頼度が閾値より大きければ
                if start_frame == -1:               # 開始フレームが設定されていなければ
                    start_frame = frame_count       # 開始フレームを更新
            else:
                if start_frame != -1:    # 開始フレームがあれば
                    print(start_frame, frame_count)
                    start_time = start_frame * frame_duration  # 抜き出す開始時間を求める
                    end_time = frame_count * frame_duration    # 抜き出す終了時間を求める
                    clip = input.subclip(start_time, end_time) # クリップを抜き出す
                    clip_list.append(clip)                     # クリップをリストに追加
                    start_frame = -1    # 開始フレームを初期化

            max_accuracy = 0.0              # 最大信頼度を初期化
            frame_count = frame_count + 1   # フレーム数を更新

output = concatenate_videoclips(clip_list) # クリップのリストを連結
# ビデオ・ファイルに出力
output.write_videofile(output_path, temp_audiofile="temp-audio.m4a", remove_temp=True, codec="libx264", audio_codec="aac")

なお、最初のグラフはgnuplotを用いている。この映画の総フレーム数は200,000に近いので、スプレッドシートでは取り扱えないか、取り扱えたとしても面倒だからだ。このgnuplotはmacOSならMacPortsを使って、以下のコマンドでインストールできる。Homebrewでのインストールは、最近になってaquatermなどのオプション指定ができなくなったので注意しよう。

sudo port install gnuplot +aquaterm

Leave a Reply

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA