opencvで動画からアニメキャラクターの画像を集めてみよう！

## はじめに
前から、好きなアニメキャラの画像+機械学習を使って何かできることはないかなと考えていたのですが、去年は画像が全然集まらず断念しました💔
今年こそは！と思い、まずはキャラクターの画像集めから始めようと思います。

今回は、動画から画像を保存して、画像からキャラクターの顔を検出したので、記事にしてみました。

(ちゃんと画像付きでもう一度チャレンジ!)

## opencvとは
[opencv(Computer Vision Library)](https://opencv.org/)は、オープンソースのコンピュータビジョン向けの、画像処理・画像解析および機械学習等の機能を持つC/C++、Java、Python、MATLAB用ライブラリ。

今回はpythonでopencvを用いて画像処理を行いました。

## インストール
mainモジュールを使用するので、下のコマンドでpipインストールしました。

```
$ pip install opencv-python 
```


## 動画から画像を保存

早速opencvを利用して、動画から任意の範囲のフレーム画像を保存しました。


```python:動画を読み込んで、画像として保存する
import cv2
import os

def movie_to_image(video_path, output_dir_path, num_cut=1, start_frame=0):

    # 動画ファイルから画像データを取得
    capture = cv2.VideoCapture(video_path)
    
    # 静止画像を保存するフォルダを作成
    os.makedirs(output_dir_path, exist_ok=True)

    # 動画の総フレーム数
    digit = capture.get(cv2.CAP_PROP_FRAME_COUNT)
    image_cnt = 0

    for n in range(start_frame, int(digit), num_cut):
        # 動画の現在位置(フレーム)をnに指定
        capture.set(cv2.CAP_PROP_POS_FRAMES, n)
        ret, frame = capture.read()
        if ret:
            # 静止画像を指定したjpg画像として保存
            cv2.imwrite('{}/image_{}.jpg'.format(output_dir_path, image_cnt), frame)
            image_cnt += 1
        else:
            return

    # 動画を閉じる
    capture.release()
```


```python:movie_to_image実行
MOVIE_PATH = 'free_1.mov'
OUTPUT_FOLDER = 'data/free_image'
movie_to_image(MOVIE_PATH, OUTPUT_FOLDER, 10, 0)
```

video_path：動画の保存先のpath(ファイル名を含む)
output_dir_path：静止画像の保存先(フォルダ名)
num_cut：任意のフレーム数ごとに画像を保存
start_frame：画像保存を始める最初のフレーム数を指定する

os.makedirs()は、output_dir_pathの中間のディレクトリ(今回の`data/`)が存在しなかった場合でも、エラーなくディレクトリを作成してくれます。また、`exist_ok=True`とすることで、すでにディレクトリが存在していた場合でもエラーが出ないようになります。

フレームレートとは1秒間に何コマあるかという数字で、1秒間に24コマだと24fps、30コマあれば30fpsとなります。一般的にアニメや映画は24fps、テレビは30fpsで作成されているようなので、24fpsだといろんな表情の画像が保存できるはずです！

今回は、秒数指定ではなく、何フレーム飛ばすかというパラメータを用いたため、10フレームごとに画像を保存しました。

動画ファイル自体は総フレーム数54,270だったので、5,427枚の静止画像が保存できました。
MacBook ProのlocalのJupiter notebook上で実行し、約45分で完了しました。


## 画像から顔の検出

opencvの[「カスケード分類器」](http://opencv.jp/opencv-2.2/c/objdetect_cascade_classification.html)という仕組みがあったので、これを用いてキャラクターの顔を検出し、枠で囲いました。

アニメ画像の顔検出をするための特徴量ファイルは[こちら](https://github.com/nagadomi/lbpcascade_animeface)の`lbpcascade_animeface.xml`を利用しています。

他にもたくさん特徴量ファイルの種類があるようなので、気になる方は[こちら](https://weblabo.oscasierra.net/python/opencv-object-detection-tutorial-1.html)を参考にしてください。

```python:画像から顔を検出
import cv2
import os

def face_cascade(img_path, save_cascade_image_dir, name):
    # 画像を読み込む
    img = cv2.imread(img_path)
    
    # カスケード分類器を生成 -> 今回はアニメ画像の正面の顔を検出
    cascade = cv2.CascadeClassifier('lbpcascade_animeface.xml')

    # 保存先ディレクトリ作成
    os.makedirs(save_cascade_image_dir, exist_ok=True)
    # 顔を検出する
    facerect = cascade.detectMultiScale(img)

    # 検出した顔を四角い枠線で囲む(検出した顔の数だけ繰り返す)
    for rect in facerect:
        cv2.rectangle(img, tuple(rect[0:2]),tuple(rect[0:2] + rect[2:4]), (255, 255, 255), thickness=2)

    # 画像を保存する
    cv2.imwrite('{}/cascade_image_{}.jpg'.format(save_cascade_image_dir, name), img)
```
```python:face_cascade実行
SAVE_CASCADE_FOLDER = 'free_2/facecascade'

for i in range(5427):
    face_cascade('free_2/image_{}.jpg'.format(i), SAVE_CASCADE_FOLDER, str(i))
```
img_path：読み込む画像のpath(ファイル名を含む)
save_cascade_image_dir：顔を枠で囲った画像の保存先ディレクトリ
name：画像の保存ファイル名

facerectは検出された顔の位置座標と大きさを表しています。
[x座標, y座標, width, hight]

実行結果

```python
print(facerect)

>> [[ 502  243  519  519]
    [1522  377  519  519]]
```

![cascade_image_2000.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613110/0d9cf772-73e3-cfe1-ea92-176b07bb88cc.jpeg)


```python
print(facerect)

>> [[ 926  237  277  277]
    [1250  244  442  442]
    [ 486  306  215  215]
    [1815  499  314  314]]
```

![cascade_image_4550.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613110/45b29668-287b-656d-af30-389d953c671e.jpeg)


正面を向いているはっきりした画像だと、大人数でも顔が検出できました🎉

## 画像から顔の切り抜き
最後に、検出された顔を切り抜いて画像として保存します。

```python:顔の切り抜き
def face_cut(img_path, save_cut_image_dir, name):
    # 画像を読み込む
    img = cv2.imread(img_path)
    
    # カスケード分類器を生成 -> 今回はアニメ画像の正面の顔を検出
    cascade = cv2.CascadeClassifier('lbpcascade_animeface.xml')

    # 保存先ディレクトリ作成
    os.makedirs(save_cut_image_dir, exist_ok=True)
    # 顔を検出する
    facerect = cascade.detectMultiScale(img)

    for i, (x,y,w,h) in enumerate(facerect):
        # 一人ずつ顔を切り抜く
        face_image = img[y:y+h, x:x+w]
        # サイズを(128×128)に統一 -> 機械学習で使いやすいように！
        face_image = cv2.resize(face_image, (128, 128))
        cv2.imwrite('{}/cut_image_{}.jpg'.format(save_cut_image_dir, name + '_' + str(i)),face_image)
```

```python:face_cut実行
SAVE_CASCADE_CUT_FOLDER = 'free_2/face_cut_image'
for i in range(5427):
    face_cut('free_2/image_{}.jpg'.format(i), SAVE_CASCADE_FOLDER,SAVE_CASCADE_CUT_FOLDER, str(i))
```

img_path：読み込む画像のpath(ファイル名を含む)
save_cascade_image_dir：カットされた画像の保存先ディレクトリ
name：保存する画像名

![cut_image2000_0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613110/2bb888f6-5375-243d-6077-17485dbca417.jpeg)![cut_image2000_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613110/1b9b375c-3aa7-7568-8241-0faeaef77a43.jpeg)

![cut_image_4550_0_n.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613110/15fde6cf-203f-895d-a6d2-0952e147b985.jpeg)![cut_image_4550_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613110/3adbbd7f-6d57-9add-ff04-04b3a7ee43fd.jpeg)![cut_image_4550_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613110/b90041cc-1ed2-aec6-c5e9-787d634dfb8e.jpeg)![cut_image_4550_3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613110/94231704-4e2e-2be9-a0e4-2a2571a9fec2.jpeg)




良い感じに切り取れました♡


## おわりに
今回は動画から画像を保存し、画像からキャラの顔を検出して画像として保存しました。
15分の動画から2,853枚の画像が取得できました✨(約1%は誤認識)
動画から切り抜くことで、キャラクターのいろいろな表情を集めることができて、とても楽しかったです😎
次は集めた画像でganや機械学習を使って、遊んでみようと思います。




## 参考文献
https://note.nkmk.me/python-opencv-video-to-still-image/
https://oku.edu.mie-u.ac.jp/~okumura/python/opencv.html
https://opencv.org/
https://weblabo.oscasierra.net/python/opencv-object-detection-tutorial-1.html
https://qiita.com/mczkzk/items/fda37ac4f9ddab2d7f45