python + OpenCVで画像を加工⑥ 動画の顔を検出してモザイクをかける→動画を保存

[前回の記事](https://qiita.com/HIROKOBA/items/3b917b07497b319c8104)で動画の中から人の顔を検出してモザイクをかけて表示することをやりました。

その後、動画を保存しようとする際の設定が複雑であることが分かったので、自分用の備忘録もかねてまとめておこうと思います。

最終的なコードはこちらになります。
```python
import cv2

def mosaic(img, mosaic_rate):
    w = img.shape[1]
    h = img.shape[0]
    img = cv2.resize(img, (int(w * mosaic_rate), int(h * mosaic_rate)))
    img = cv2.resize(img, (w, h), interpolation = cv2.INTER_NEAREST)
    return img
    
cascade_file= cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
clas = cv2.CascadeClassifier(cascade_file)
cap = cv2.VideoCapture("movie.mp4")

#動画を保存
fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
fps = 20.0
size = (640, 360)
writer = cv2.VideoWriter('./test.mp4', fmt, fps, size)

while (cap.isOpened()):
    ret, frame = cap.read()
    if not ret:
        break

    #フレームのサイズを保存するサイズに合わせる
    frame=cv2.resize(frame, size)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    face_list = clas.detectMultiScale(gray, scaleFactor = 1.1, minNeighbors = 3, minSize=(30,30))

    for x, y, w, h in face_list:
       frame[y : y+h, x : x+w] = mosaic(frame[y : y + h, x : x + w], 0.05)

    #１フレームずつ画像を書き込む
    writer.write(frame)

    cv2.imshow("frame", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

writer.release()
cap.release()
cv2.destroyAllWindows()
```
##### 動画保存の形式を指定する
この４行が重要。でも分かりにくい。

```python
fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
fps = 20.0
size = (640, 360)
writer = cv2.VideoWriter('./test.mp4', fmt, fps, size)
```
簡単に言うと
```python
fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
fps = 20.0
size = (640, 360)
```
この３行で必要な引数を変数にそれぞれ代入し・・・
```python
cv2.VideoWriter('./test2.mp4', fmt, fps, size)
```
この部分で３つの変数を引数としてVideoWriterに渡しています（'./test.mp4'は動画を保存するディレクトリと保存する動画の名前）。そしてそれをwriter変数に代入、という流れです。

##### cv2.VideoWriter_fourcc
自分的に最初わからなかったのが
```python
cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
```
この部分です。公式リファレンスを探してもはっきり書いてある記事が見当たらない気がします。情報がないながらもヒントになったのが[OpenCV-Python チュートリアルのこの記事](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_gui/py_video_display/py_video_display.html)と、[OpenCVのドキュメントのこのページ](https://docs.opencv.org/3.4/dd/d9e/classcv_1_1VideoWriter.html#ad59c61d8881ba2b2da22cff5487465b5)でした。
> 動画ファイルとして保存するために VideoWriter 型のオブジェクトを生成します．第1引数は保存する動画名(例: output.avi)を指定します．第2~第4引数は FourCC コード(詳細は次の段落で説明)を指定し，動画の再生速度(fps)と解像度を設定します．最後の引数は isColor フラグを指定します．このフラグが True であればカラーの動画，そうでなければグレースケールの動画として保存されます．
> FourCC は動画のコーデックを指定するための4バイトのコードです．
> MJPG用のFourCC コードは cv2.VideoWriter_fourcc('M','J','P','G') もしくは cv2.VideoWriter_fourcc(*'MJPG) です．
>
> #Define the codec and create VideoWriter object
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi',fourcc, 20.0, (640,480))

> filename:
	Name of the output video file.
fourcc:
	4-character code of codec used to compress the frames. For example, VideoWriter::fourcc('P','I','M','1') is a MPEG-1 codec, VideoWriter::fourcc('M','J','P','G') is a motion-jpeg codec etc. List of codes can be obtained at Video Codecs by FOURCC page. FFMPEG backend with MP4 container natively uses other values as fourcc code: see ObjectType, so you may receive a warning message from OpenCV about fourcc code conversion.
fps:
	Framerate of the created video stream.
frameSize:
	Size of the video frames.
isColor:
	If it is not zero, the encoder will expect and encode color frames, otherwise it will work with grayscale frames (the flag is currently supported on Windows only).

cv2.VideoWriter_fourcc() のパラメータとしてFourCCというのを指定しなければならないらしい。じゃあFourCCって何よ？というので調べてみたら、[Microsoftのサイト](https://docs.microsoft.com/ja-jp/windows/win32/directshow/fourcc-codes)に解説がありました。
> FOURCC コードは、4 つの ASCII 文字を連結して作成される 32 ビット符号なし整数です。 たとえば、YUY2 ビデオの FOURCC コードは 'YUY2' です。

これらの記事を全部噛み砕いて理解すると、私が使おうとしているmp４ファイルの場合は「MP4V」になるらしい。そしてcv2.VideoWriter_fourcc()のパラメータとして使うには↓のどちらかになるらしい。
```python
cv2.VideoWriter_fourcc('M','P','4','V') 
cv2.VideoWriter_fourcc(*'MP4V)
```
・・・という経緯で現在のコード↓
```python
cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
```
に至ったという次第です。

##### fps

fpsは"frames per second"の略で、1秒間の動画が何枚の画像で構成されているかを示すの単位のこと。[このサイト](https://canon.jp/business/trend/what-is-framerate#:~:text=fps%EF%BC%89%E3%81%A8%E3%81%AF-,%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AC%E3%83%BC%E3%83%88%EF%BC%88fps%EF%BC%89%E3%81%A8%E3%81%AF%E3%80%811%E7%A7%92%E9%96%93%E3%81%AE%E5%8B%95%E7%94%BB,per%20second%E3%80%8D%E3%81%AE%E7%95%A5%E3%81%A7%E3%81%99%E3%80%82)に教えてもらいました。我々が一般的に使用する動画では24fpsでよいのではないかと思います。

##### size
sizeは解像度ということになると思うのですが、これも調べ始めると奥が深くてキリがないので、とりあえずPC画面上で扱いやすい(640, 360)にしておきます。

##### cv2.VideoWriter_fourcc()にパラメータを与える
それぞれの値を代入した変数をcv2.VideoWriter_fourcc()の変数として与えたのが
```python
writer = cv2.VideoWriter('./test2.mp4', fmt, fps, size)
```
・・・ということになります。

##### 実際に読み込んだ動画のフレームサイズと保存する動画のサイズを合わせる
```python
frame = cv2.resize(frame, size)
```
この１行で、実際に読み込んだ動画のフレームサイズと保存する動画のサイズを同じにしています。ここで指定しないと、mp4のファイルは作成されますが開くことができません。

##### １フレームずつ書き込む
```python
writer.write(frame)
```
whileループの中で１フレームずつ書き込んでいってます。これも１フレームずつ書き込まれていってるので、最終的にパラパラ漫画の要領で動画に見える。
##### リソースを解放
```python
writer.release()
```
この１行でリソースを解放します。

・・・という感じで、ちょっと理解するまでに時間がかかりましたが、加工した画像を出力して保存することができました。