rembgを用いて白背景＋人物動画を黒背景動画にした話

# 概要
ffmpeg、rembg、opencvを使って、白背景＋人物の動画を黒背景＋人物動画にした。

# 背景
結婚式の余興で、香水のパロディー動画を作成することになった。
香水のMVのように黒背景で人が歌っている動画を作成したかったが、黒い布を買うといった手間をかけたくなかった。白背景ならどこでも撮影可能なので、白背景をなんとか黒背景にしたかった。

# どんな感じになったか

![raw.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542172/ec55d9a4-e2ca-68ad-cbe3-953070f68ba2.jpeg)
これが、


![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542172/dad9ae5d-d684-4c50-4f96-8f8ac7d1882f.jpeg)
こんな感じになった。素晴らしい精度です。rembgすごい。他の人物切り抜き方法も色々試しましたが、これが一番自然でした。


※モザイク化には、https://www.facepixelizer.com/jp/
を使わせていただきました。

# 前提
- pythonがインストールされている。
- rembgがインストールされている。（このインストールがやっかいです。別で解説してもいいかも）
- opencvがインストールされている。
- ffmpegがインストールされ、PATHが通っている。

# 手順
- 動画ファイル用意（MP4等、FPSは必要以上あげない（処理が重くなる））
- 動画ファイルを画像ファイルに分解。

　　コマンド例（rawフォルダに動画のコマ画像が出力される）：ffmpeg -i ~~.mp4 -vcodec png raw\image_%05d.png

- rembgを用いて画像ファイルの人物を切り抜く。動画の全画像を対象とするので、結構時間がかかる。

　　コマンド例：rembg -a -ae 15 -o output1\image_252.png raw\image_00001.png

　　https://github.com/danielgatis/rembg
　　を用いるが、導入がけっこう大変だった。pytorch、torchvisionのバージョン等が難しい。
　　pyenvやcondaの仮想環境で構築するのがいいと思う。

- 人物を切り抜いたあとの透明な部分を違う画像もしくは任意の色で塗りつぶす。
　　opencvでやった。

- 画像を動画化

　　コマンド例：ffmpeg -f image2 -r 30 -i image_%03d.png -r 30 -an -vcodec libx264 -pix_fmt yuv420p video.mp4

# 使用したスクリプト
## フォルダ構成
今回使用したスクリプトのフォルダ構成は以下の通り。

```
root/
　├ raw_video/ # 未加工動画を保存するフォルダ
　├ formatted_video/ #toMP4.pyでFPS、画質を調整した動画を保存するフォルダ
　├ output/ # 最終的に生成される動画を保存するフォルダ
　├ mask/ # 背景用黒画像を保存するフォルダ
   　└ black.png # 動画のサイズに合わせて、1920x1080の黒画像を用意。黒でなくても問題はない。
　├ tmp1/ # formatted_videoの動画を画像化したものを保存するフォルダ
　├ tmp2/ # tmp1の画像をrembgで白背景を透明にした画像を保存するフォルダ
　├ tmp3/ # tmp2の画像とをmask/black.pngの画像をopencvで合成し、黒背景にした画像を保存するフォルダ
　├ toMP4.py # raw_videoの動画のFPS、画質を調整して、formatted_videoフォルダに入れるスクリプト
　└ main.py # 一連の処理を行うスクリプト
```
　
## フルHD、30FPSのMP4ファイルにするコード（toMP4.py）
ffmpegのコマンドをpythonで生成し、実行するスクリプト。

```
import os

base_dir = "raw_video"
output_dir = "formatted_video"
fns = os.listdir(base_dir)

print(len(fns))
for f in fns:
    print(f)
    cmd = "ffmpeg.exe -i {} -s hd1080 -c:v libx264 -c:a copy -r 30 {} -y".format(
        base_dir + "\\" + f, output_dir + "\\" + f + "__.mp4")
    os.system(cmd)
```

## 一連の処理を行うコード（main.py）
- 動画ファイルを画像ファイルに分解。
- rembgを用いて画像ファイルの人物を切り抜く。動画の全画像を対象とするので、結構時間がかかる。
- 人物を切り抜いたあとの透明な部分を違う画像もしくは任意の色で塗りつぶす。
- 画像を動画化

```
import os
import shutil
import cv2
import matplotlib.pylab as plt

base_dir = "formatted_video"
tmp1_dir = "tmp1"
tmp2_dir = "tmp2"
tmp3_dir = "tmp3"
output_dir = "output"
fns = os.listdir(base_dir)

print(len(fns))
for f in fns:
    bn = os.path.basename(f)
    print(f)
    # フォルダ作成
    shutil.rmtree(tmp1_dir)
    os.makedirs(tmp1_dir)
    shutil.rmtree(tmp2_dir)
    os.makedirs(tmp2_dir)
    shutil.rmtree(tmp3_dir)
    os.makedirs(tmp3_dir)
    # os.makedirs(output_dir)

    # # 動画ファイルを画像ファイルに分解
    p1 = base_dir + "\\" + f
    p2 = tmp1_dir + "\\" + bn + "_%05d.png"
    cmd1 = "ffmpeg -i {} -vcodec png {}".format(p1, p2)
    print(cmd1)
    os.system(cmd1)
    # rembgを用いて画像ファイルの人物を切り抜く
    fn2s = os.listdir(tmp1_dir)
    for f2 in fn2s:
        if(f2[-3:] != "png"):
            continue
        p3 = tmp1_dir + "\\" + f2
        p4 = tmp2_dir + "\\" + f2
        cmd2 = "rembg -a -ae 15 -o {} {}".format(p4, p3)
        print(cmd2)
        os.system(cmd2)
    # 人物を切り抜いたあとの透明な部分を違う画像もしくは任意の色で塗りつぶす
    fn2s = os.listdir(tmp2_dir)
    for f2 in fn2s:
        if(f2[-3:] != "png"):
            continue
        print('{}/{}'.format(tmp2_dir, f2))
        frame = cv2.imread('mask/black1.png')
        png_image = cv2.imread('{}/{}'.format(tmp2_dir, f2),
                               cv2.IMREAD_UNCHANGED)  # アル ファチャンネル込みで読み込む
        # png_image[:, :, 3:] = np.where(png_image[:, :, 3:] > 200, 255, 0)
        x1, y1, x2, y2 = 0, 0, png_image.shape[1], png_image.shape[0]
        frame[y1:y2, x1:x2] = frame[y1:y2, x1:x2] * (1 - png_image[:, :, 3:] / 255) + \
            png_image[:, :, :3] * (png_image[:, :, 3:] / 255)
        # plt.imshow(frame)
        # plt.show()
        cv2.imwrite('{}/{}'.format(tmp3_dir, f2), frame)
    # 画像を動画化
    cmd3 = "ffmpeg -f image2 -r 30 -i {} -r 30 -an -vcodec libx264 -pix_fmt yuv420p {}.mp4 -y".format(
        tmp3_dir + "\\" + bn + "_%05d.png", output_dir + "\\" + bn + "_bg_blk")
    os.system(cmd3)
    # break

```

デバッグ用に、余計なコードも入ってます。

# rembgのインストール
- Anacondaで仮想環境を作る。（python=3.8）
- conda install pytorch===1.7.0 torchvision===0.8.1 torchaudio cpuonly -c pytorch
- pip install rembg==1.0.18
- pip install numpy==1.19.3 → rembgインストール時、numpy 1.19.4がインストールされるが、うまく動かなかったため、1.19.3にした。
- ついでにopencvもインストールしとく。pip install opencv-python
- 仮想環境上で、rembg -o 〇〇.png ✕✕.pngを実行し、✕✕.jpg画像の人物以外が透過された〇〇.pngファイルが出力されていればOK。

# 最後に
あくまで自分のメモ程度の内容なので、わからにくい部分もあるかと思いますが、ご了承ください。