動画の共通部分から背景画像を抽出する(ImageMagick)

# 対象読者

[日向坂46 「ひなリハ」～青春の馬～（4K）
](https://www.youtube.com/watch?v=mTVIzJm-9oU) のような背景が固定の動画から、以下のような背景画像を抽出したい人向けです。
![hogehoge.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693216/50abcba7-e39c-ceff-3ca5-acc690aa5c7c.png)

# 1. 背景の抽出
いくつか調べましたが、[ImageMagick](https://imagemagick.org/) での抽出を行っています。

# 1.1. ImageMagick を使う方法

「[Creating image masks from video file](https://www.arj.no/2020/02/21/image-masks/)」を参考にしました。

```shell
ffmpeg -i input.mp4 -r 1 -f image2 org-%05d.png
```

まず動画から連番画像を抽出します。画像が足りない際は `-r 1` を増やして適宜準備します。その後、

```shell
for file_name in `ls -1 org-*.png | shuf`
do
    if [ "${before_file_name}" != "" ]; then
        convert \
            \( \
                ${before_file_name} \
                \( \
                    -clone 0 \
                    ${file_name} \
                    -compose difference \
                    -composite \
                    -threshold 1% \
                    -negate \
                \) \
                -compose multiply \
                -flatten \
            \) \
            background.png \
            -background black \
            -compose src-over \
            -flatten \
            -transparent black \
            background.png

        count=`expr ${count:-0} + 1`
        cp background.png `printf background-%05d.png ${count}`
        echo `date +%Y-%m-%d\ %H:%M:%S` ${count} ${before_file_name} done
    fi
    before_file_name=${file_name}
done
```

`-compose difference` で差分がなかったところ = 背景として、重ね合わせて背景画像を作っていきます。
共通の程度は動画の内容によって変わってくるので `-threshold` で調整した方が良い感じでした。
また、動画から抽出した画像を連番で比較した場合、共通部分が多くなり誤って背景判定されることが多くなるため、全体を `shuf` してから前後の比較をするようにしています。

最終的に、[compose の src-over](https://imagemagick.org/script/compose.php) で、抽出した背景を重ね合わせ `background.png` にします。元記事では一括でやっていますが、ファイル数が多くなった際にメモリが足りなくなったため、逐次結合しています。
![output-palette.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693216/ccf72677-cbd7-1d91-95f0-1fbd5fb5ade8.gif)
背景が写っている頻度が少ない箇所はゴミが残ってしまうので、`-compose src-over` の代わりに `-compose lighten` (明るいもので上書かれるので背景が明るい際に有用) や `-compose darken` (背景が暗い際に有用) を使うとゴミが少なくできそうです。
# 1.2. OpenCV のサンプルを使う方法(のメモ)

```shell
docker run -v /c/Users/${USER}/Downloads:/Downloads -it --rm -e DISPLAY=host.docker.internal:0.0 opencvcourses/opencv-docker:4.4.0
```

上記で [opencv/opencv](https://github.com/opencv/opencv) の Docker を起動します。

```shell
git clone https://github.com/opencv/opencv
python opencv/samples/python/tutorial_code/video/background_subtraction/bg_sub.py --input /Downloads/input.mp4
```

コンテナ内で上記を実行することで、リポジトリにある [bg_sub.py](https://github.com/opencv/opencv/blob/master/samples/python/tutorial_code/video/background_subtraction/bg_sub.py) が実行できますが、出力の保存等は試していません。

# 1.3. BGSLibrary を使う方法(のメモ)

```shell
docker run -v /c/Users/${USER}/Downloads:/Downloads -it --rm -e DISPLAY=host.docker.internal:0.0 andrewssobral/bgslibrary:opencv_3.4.1py36 bash
```

上記で [andrewssobral/bgslibrary](andrewssobral/bgslibrary) の Docker を起動します。

```shell
cd /root/bgslibrary/
./build/bgslibrary -uf -fn=/Downloads/input.mp4
```

コンテナ内で上記を実行することで、コマンドが実行できますが、出力の保存等は試していません。

# 2. おまけ: 動画の背景を分離

上記の画像を使って [PeterL1n/BackgroundMattingV2](https://github.com/PeterL1n/BackgroundMattingV2) の [Video matting (Colab)](https://colab.research.google.com/drive/1Y9zWfULc8-DDTSsCH-pX6Utw8skiJG5s?usp=sharing) などを使うと、背景をそこそこ分離できました。