Python + OpenCV で雑コラ動画を作成する③ 雑コラ動画作成

##0. はじめに
前回([その② 静止画コラ作成](http://qiita.com/k_sui_14/items/bb9dc8395da85400e518))からの続きです。
前回行った静止画の顔へのオーバレイを動画に適用します。
以下のサイトを参考にしました。
[【シリーズ】「pythonとOpenCVを用いたCVプログラミング 」第9回： OpenCV-python② 動画の入出力とhighGUIでのマウス、キーボードコールバック](http://derivecv.tumblr.com/post/73561473978)


##1. 動画の入出力
まず始めに動画の入出力をしてみます。
単純に動画を読み込んでそのまま出力するだけです。
以下がコードです

```py3:export_movie.py
#coding=utf-8

import cv2

def export_movie():


    # 入力する動画と出力パスを指定。
    target = "target/test_input.mp4"
    result = "result/test_output.m4v" 

    # 動画の読み込みと動画情報の取得
    movie = cv2.VideoCapture(target) 
    fps    = movie.get(cv2.CAP_PROP_FPS)
    height = movie.get(cv2.CAP_PROP_FRAME_HEIGHT)
    width  = movie.get(cv2.CAP_PROP_FRAME_WIDTH)

    # 形式はMP4Vを指定
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    
    # 出力先のファイルを開く
    out = cv2.VideoWriter(result, int(fourcc), fps, (int(width), int(height)))
   
    # 最初の1フレームを読み込む
    if movie.isOpened() == True:
        ret,frame = movie.read()
    else:
        ret = False

    # フレームの読み込みに成功している間フレームを書き出し続ける
    while ret:
        
        # 読み込んだフレームを書き込み
        out.write(frame)

        # 次のフレームを読み込み
        ret,frame = movie.read()


if __name__ == '__main__':
    export_movie()
```
超簡単

##2. 結果
容量が3倍近くなりましたが出力された動画を再生できました。
この辺はきちんとコーデックを導入しなければならないようです。
あと音は入っていません。

##3. 雑コラ動画作成
動画の入出力には成功したのでいよいよ雑コラ動画を作成します。
と言っても動画の出力前に顔認識、オーバレイをするだけです。
動画ファイルから読み込んだフレームは画像ファイルと同じように扱えます。
あと、overlayOnPart関数の最後が戻り値の画像にアルファチャンネル(透過)を含まないように変わっています。
以下、コードです。

```py3:overlay_movie.py
#coding=utf-8

import cv2
import datetime
import numpy as np
from PIL import Image

def overlay_movie():

    # 入力する動画と出力パスを指定。
    target = "target/test_input.mp4"
    result = "result/test_output.m4v"  #.m4vにしないとエラーが出る

    # 動画の読み込みと動画情報の取得
    movie = cv2.VideoCapture(target) 
    fps    = movie.get(cv2.CAP_PROP_FPS)
    height = movie.get(cv2.CAP_PROP_FRAME_HEIGHT)
    width  = movie.get(cv2.CAP_PROP_FRAME_WIDTH)

    # 形式はMP4Vを指定
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    
    # 出力先のファイルを開く
    out = cv2.VideoWriter(result, int(fourcc), fps, (int(width), int(height)))

    # カスケード分類器の特徴量を取得する
    cascade_path = "haarcascades/haarcascade_frontalface_alt.xml"
    cascade = cv2.CascadeClassifier(cascade_path)

    # オーバーレイ画像の読み込み
    ol_imgae_path = "target/warai_otoko.png"    
    ol_image = cv2.imread(ol_imgae_path,cv2.IMREAD_UNCHANGED)
        
    # 最初の1フレームを読み込む
    if movie.isOpened() == True:
        ret,frame = movie.read()
    else:
        ret = False

    # フレームの読み込みに成功している間フレームを書き出し続ける
    while ret:
        
        # グレースケールに変換
        frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 顔認識の実行
        facerecog = cascade.detectMultiScale(frame_gray, scaleFactor=1.1, minNeighbors=1, minSize=(1, 1))

        if len(facerecog) > 0:
            # 認識した顔に画像を上乗せする
            for rect in facerecog:

                # 認識範囲にあわせて画像をリサイズ
                resized_ol_image = resize_image(ol_image, rect[2], rect[3])
                    
                # オーバレイ画像の作成
                frame = overlayOnPart(frame, resized_ol_image, rect[0],rect[1])

        # 読み込んだフレームを書き込み
        out.write(frame)

        # 次のフレームを読み込み
        ret,frame = movie.read()

        # 経過を確認するために100フレームごとに経過を出力
        if movie.get(cv2.CAP_PROP_POS_FRAMES)%100 == 0:
            date = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
            print(date + '  現在フレーム数：'+str(int(movie.get(cv2.CAP_PROP_POS_FRAMES))))

        # 長いので途中のフレームまでで終了する
#        if movie.get(cv2.CAP_PROP_POS_FRAMES) > 1000:
#            break

    print("完了")
    

def resize_image(image, height, width):
    
    # 元々のサイズを取得
    org_height, org_width = image.shape[:2]
    
    # 大きい方のサイズに合わせて縮小
    if float(height)/org_height > float(width)/org_width:
        ratio = float(height)/org_height
    else:
        ratio = float(width)/org_width
    
    resized = cv2.resize(image,(int(org_height*ratio),int(org_width*ratio)))
    
    return resized    

# PILを使って画像を合成
def overlayOnPart(src_image, overlay_image, posX, posY):

    # オーバレイ画像のサイズを取得
    ol_height, ol_width = overlay_image.shape[:2]

    # OpenCVの画像データをPILに変換
    #　BGRAからRGBAへ変換
    src_image_RGBA = cv2.cvtColor(src_image, cv2.COLOR_BGR2RGB)
    overlay_image_RGBA = cv2.cvtColor(overlay_image, cv2.COLOR_BGRA2RGBA)
    
    #　PILに変換
    src_image_PIL=Image.fromarray(src_image_RGBA)
    overlay_image_PIL=Image.fromarray(overlay_image_RGBA)

    # 合成のため、RGBAモードに変更
    src_image_PIL = src_image_PIL.convert('RGBA')
    overlay_image_PIL = overlay_image_PIL.convert('RGBA')

    # 同じ大きさの透過キャンパスを用意
    tmp = Image.new('RGBA', src_image_PIL.size, (255, 255,255, 0))
    # 用意したキャンパスに上書き
    tmp.paste(overlay_image_PIL, (posX, posY), overlay_image_PIL)
    # オリジナルとキャンパスを合成して保存
    result = Image.alpha_composite(src_image_PIL, tmp)
    
    # COLOR_RGBA2BGRA から COLOR_RGBA2BGRに変更。アルファチャンネルを含んでいるとうまく動画に出力されない。
    return  cv2.cvtColor(np.asarray(result), cv2.COLOR_RGBA2BGR)
               
if __name__ == '__main__':
    overlay_movie()
```

だんだん長くなってきました。

##4. 結果
***
2016/03/11 21:57:04現在フレーム数：100.0
2016/03/11 21:57:16現在フレーム数：200.0
2016/03/11 21:57:28現在フレーム数：300.0
・
・
・
2016/03/11 22:08:59現在フレーム数：7100.0
完了
***

フレームサイズ718×480、24fps、5:00の動画に対してCore i5-4670 3.40GHz  メモリ16GBの環境で実行しました。
結構時間がかかりましたが、雑コラ動画を出力できました。
![動画認識結果.JPG](https://qiita-image-store.s3.amazonaws.com/0/3099/c8928640-c2aa-a646-41a6-2db8797cb223.jpeg)


##5. 最後に
が、これでは不完全です。
複数の顔があると全てに上書きしたり、
![複数人.JPG](https://qiita-image-store.s3.amazonaws.com/0/3099/f14c0822-6c7b-88f8-efca-d7b7b25b29f6.jpeg)


誤って認識した顔にも手当たり次第上書きしてしまいます。
![誤認識.JPG](https://qiita-image-store.s3.amazonaws.com/0/3099/03ec5c50-0296-1941-5ea2-e816eb861248.jpeg)


次回はこの問題の解決を目指します。
[Python + OpenCV で雑コラ動画を作成する④ 課題への対処](http://qiita.com/k_sui_14/private/6a404ea0d6e442dd99e0)