SwinIRを使って高解像度の動画を作成するswin_video

# はじめに
AIにより低解像度の静止画を綺麗に拡大することができるSwinIRを使って動画の解像度を2倍または4倍にするPython用のツールを作成しました。
古いSD動画などをドラッグアンドドロップすることで 2倍または4倍の動画を作成します。


https://github.com/JingyunLiang/SwinIR


# 処理結果
ピクサベイの動画を使っていくつか試してみました。
それぞれバイリニアとSwinIRで2倍にリサイズして比較します。

## 街の空撮
https://twitter.com/amarectv/status/1751459683033141365

- [olhao_-_14088(360p)_it_x2.mp4](http://www.amarectv.com/download/olhao_-_14088(360p)_it_x2.mp4)

https://pixabay.com/ja/videos/%E3%82%AA%E3%83%AA%E3%83%A3%E3%82%AA-%E3%83%9D%E3%83%AB%E3%83%88%E3%82%AC%E3%83%AB-14088/


## 海岸の桟橋
<img width="120" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/3390d830-575b-f3f9-acf0-962582827a12.png">

- [1198860324_it_x2.mp4](http://www.amarectv.com/download/1198860324_it_x2.mp4)

https://pixabay.com/ja/videos/%E6%A9%8B%E8%84%9A-%E6%B5%B7%E6%B4%8B-%E6%B3%A2-%E3%82%B9%E3%83%97%E3%83%A9%E3%83%83%E3%82%B7%E3%83%A5-20681/


## ドイツの森
<img width="240" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/3220cf73-0311-2318-9cf3-0cf251175681.png">

- [1114708233_sbs_x2.mp4](http://www.amarectv.com/download/1114708233_sbs_x2.mp4)

https://pixabay.com/ja/videos/%E6%A3%AE%E6%9E%97-%E8%88%AA%E7%A9%BA%E5%86%99%E7%9C%9F-%E8%87%AA%E7%84%B6-18534/




# 動作条件
- Windows10 / 11
- 4GB以上のnVIDIA製ビデオカード 
- Python 3.11.4

# ダウンロード
次のものをダウンロードしてください
- SwinIR一式
https://github.com/JingyunLiang/SwinIR
・'SwinIR-main'フォルダ
- SwinIR用学習データ 2つ
https://github.com/JingyunLiang/SwinIR/releases
・003_realSR_BSRGAN_DFO_s64w8_SwinIR-M_x2_GAN.pth
・003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN.pth
- ffmpeg
https://github.com/GyanD/codexffmpeg/releases/tag/2020-12-20-git-ab6a56773f
ffmpeg-2020-12-20-git-ab6a56773f-full_build.zip
・ffmpeg.exe
・ffprobe.exe
- ffstream
[ffstream1_1_0.zip](http://www.amarectv.com/cgi-bin/kz_dl_counter.cgi?http://www.amarectv.com/download/ffstream1_1_0.zip)
・'ffstream'フォルダ
- swin_video
[swin_video1_0_0.zip](http://www.amarectv.com/cgi-bin/kz_dl_counter.cgi?http://www.amarectv.com/download/swin_video1_0_0.zip)


swin_video.zipを解凍してできる'swin_video'フォルダへそれぞれコピーしてください。
全てコピーすると以下のようになります。
![files.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/29bc68c5-bba0-7f02-f7f6-b7b128291c38.png)




# Pythonのインストール
このツールはPython用のスクリプトになっていますので、Pythonを公式サイトからダウンロードしてインストールしてください。

https://www.python.org/

https://www.python.org/downloads/release/python-3114/
・python-3.11.4-amd64.exe



インストール画面ではカスタマイズを選択し Pythonへ パスを通すオプションをONにしてください。

<img width="120" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/e188ec98-2de1-0516-d855-b0cb400bd3d8.png"> <img width="120" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/8c9449d0-9a62-4178-5401-656c53ff4d5d.png">
インストールが終わったらPCを再起動します（再起動しないとパス設定が有効にならない）。


Windows PowerShellを起動してPythonが使えることを確認します。Pythonから抜けるには[Ctrl] + [Z]。
![power_python.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/2c6c185d-cadb-4305-9358-de9c94c67040.png)


AppStoreのPythonのページが表示されるようなら パスの設定ができていません。
インストーラーで修復か再インストールしてください。



# Pythonで必要なモジュール
```python:requirements.txt
ffmpeg-python==0.2.0
keyboard==0.13.5
numpy==1.24.4
opencv-contrib-python==4.8.0.76
requests==2.28.1
timm==0.9.12
tqdm==4.66.1
#torch==2.2.0.dev20230915+cu121
#torchvision==0.17.0.dev20230915+cu121
```
同梱のmodule_install.batで一括インストールできます。

また、PyTorchのCUDA 12版が必要です。CPU版や CUDA 11版では動かない場合があります。
動かない場合は 次の様に上書きインストールしてください。
```python
pip install --upgrade --force-reinstall torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
```
https://pytorch.org/


# 使い方
以下のバッチファイルへ動画をD&Dしてください。
カレントフォルダ（swin_videoのフォルダ）へ2倍または4倍に拡大された動画が作成されます。

- swin_video_x2.bat
2倍に拡大
- swin_video_x4.bat
4倍に拡大
- swin_video_sbs.bat
バイリニアで拡大した画像とSwinIRを左右に並べた動画を作成
- swin_video_it.bat
バイリニアとSwinIRを1秒毎に切り替える動画を作成

:::note info
注意
SwinIRは静止画用で 1枚の画像を処理するのに数秒かかります。
動画のように数万枚処理するにはとんでもなく時間がかかりますので途中でESCキーで終了してください。
RTX4090で640x480 30fps 1分の動画を処理（2倍に拡大）するのに 50分程度かかります。
:::



# サンプルプログラム
SwinIRを使って動画を2倍にするには次のようにします。

## 超解像モデル SwinIRの準備
コマンドライン引数を管理するargsへSwinIRに関する設定を追加してモデルを取得します。

```python
parser.add_argument('-s', '--scale', type=int, default=2, choices=[2, 4], help='scale factor: 2, 4')
args = parser.parse_args()
args.task = 'real_sr'
args.tile = None
if args.scale==2:
    # 2倍用のモデル
    args.model_path = '003_realSR_BSRGAN_DFO_s64w8_SwinIR-M_x2_GAN.pth'
    args.large_model = False
elif args.scale==4:
    # 4倍用のモデル
    args.model_path = '003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN.pth'
    args.large_model = True
    
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')   # 基本CUDA専用
model = main_test_swinir.define_model(args)
model.eval()
model = model.to(device)
```
倍率毎に使う学習データが異なります。

## 超解像処理 SwinIR
```python
def sr(model, args, frame_rgb:Tensor)->Tensor:
    with torch.no_grad():
        img_lq = frame_rgb.unsqueeze(0)  # CHW-RGB to NCHW-RGB
        window_size = 8
        # pad input image to be a multiple of window_size
        _, _, h_old, w_old = img_lq.size()
        h_pad = (h_old // window_size + 1) * window_size - h_old
        w_pad = (w_old // window_size + 1) * window_size - w_old
        img_lq = torch.cat([img_lq, torch.flip(img_lq, [2])], 2)[:, :, :h_old + h_pad, :]
        img_lq = torch.cat([img_lq, torch.flip(img_lq, [3])], 3)[:, :, :, :w_old + w_pad]
        output = main_test_swinir.test(img_lq, model, args, window_size)
        output = output[..., :h_old * args.scale, :w_old * args.scale]
        return output.squeeze()

# super resolution
output = sr(model, args, frame_rgb)

```
関数sr()へ モデルとargsとRGB画像(0.0～1.0の実数)を渡せば 2倍または4倍にされたRGB画像が作成されます。
作成されるRGB画像は0.0～1.0の実数ですが 範囲外になる場合がありますので、必要に応じて制限してください。
```python
output = output.clamp(0, 1)    # 0.0-1.0に制限する
```
FFStreamの場合は出力処理内で制限されるので不要です。



## 動画の入出力
ffmpeg-pythonを利用したFFStreamを使います。
最終的には別プロセスで実行しているffmpeg.exeで処理されます。

```python
# 入力パス 出力パス 画質20 ピクセルフォーマットRGB 音声がある場合はコピーする
# 出力画像サイズを2倍,4倍にする フレーム処理に使うデバイスを指定する
ff = FFStream(input_path, output_path, crf = 20, pix_fmt='rgb24',
            copy_audio_stream=True, image_scale=args.scale, device=device)
    
bar = tqdm(total=ff.frames, dynamic_ncols=True)
for i in range(ff.frames):
    if keyboard.is_pressed('escape'):
        break
        
    # FFmpegからRGBフレームを受け取る
    frame_rgb = ff.get_rgb()
    if (frame_rgb == None) :
        break                                       # 動画の最後に達した

    # super resolution
    output = sr(model, args, frame_rgb)

    # FFmpegの出力ストリームへRGBフレームを送る
    ff.put_rgb(output)

    bar.update(1)
bar.close()
ff.close()
```

FFStreamについて記事にしました。

https://qiita.com/amaman/items/463ddedf52393d3e0678

## 全体
```python:swin_video_s.py
version=(1,0,0)
# 2024.1.28	Ver1.0.0 初版
import os
import sys
import argparse
import torch
from torch import Tensor
import keyboard
from tqdm import tqdm

# SwinIRのフォルダを参照するようにする
sys.path.append(os.path.join(os.path.dirname(__file__), 'SwinIR-main'))
import main_test_swinir

from ffstream.ffstream import FFStream      #import ffmpeg-python

# 超解像処理
def sr(model, args, frame_rgb:Tensor)->Tensor:
    with torch.no_grad():
        img_lq = frame_rgb.unsqueeze(0)  # CHW-RGB to NCHW-RGB
        window_size = 8
        # pad input image to be a multiple of window_size
        _, _, h_old, w_old = img_lq.size()
        h_pad = (h_old // window_size + 1) * window_size - h_old
        w_pad = (w_old // window_size + 1) * window_size - w_old
        img_lq = torch.cat([img_lq, torch.flip(img_lq, [2])], 2)[:, :, :h_old + h_pad, :]
        img_lq = torch.cat([img_lq, torch.flip(img_lq, [3])], 3)[:, :, :, :w_old + w_pad]
        output = main_test_swinir.test(img_lq, model, args, window_size)
        output = output[..., :h_old * args.scale, :w_old * args.scale]
        return output.squeeze()

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    # 画像を2倍または4倍にします
    parser.add_argument('-s', '--scale', type=int, default=2, choices=[2, 4], help='scale factor: 2, 4')
    args = parser.parse_args()
    args.task  = 'real_sr'
    args.tile  = None

    if args.scale==2:
        # 2倍用のモデル
        args.model_path = '003_realSR_BSRGAN_DFO_s64w8_SwinIR-M_x2_GAN.pth'
        args.large_model = False
    elif args.scale==4:
        # 4倍用のモデル
        args.model_path = '003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN.pth'
        args.large_model = True

    # 超解像のモデル
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')   # 基本CUDA専用
    model = main_test_swinir.define_model(args)
    model.eval()
    model = model.to(device)

    # 動画の入出力
    input_path  = 'input.mp4'
    output_path = 'output.mp4'
    print(f'------------------------------------------------------')
    print(f'version :swin_video={version},  ffstream={FFStream.version},  ycbcr={FFStream.ycbcr_discription}{FFStream.ycbcr_version}')
    print(f'device  :{device}')
    print(f'input   :{input_path}')
    print(f'output  :{output_path}')
    print(f'press [escape] key to cancel')
    # 入力パス 出力パス 画質20 ピクセルフォーマットRGB 音声がある場合はコピーする 出力画像サイズを2倍,4倍にする フレーム処理に使うデバイスを指定する
    ff = FFStream(input_path, output_path, crf = 20, pix_fmt='rgb24', copy_audio_stream=True, image_scale=args.scale, device=device)
    
    bar = tqdm(total=ff.frames, dynamic_ncols=True)
    for i in range(ff.frames):
        if keyboard.is_pressed('escape'):
            break
        
        # FFmpegからRGBフレームを受け取る
        frame_rgb = ff.get_rgb()
        if (frame_rgb == None) :
            break                                       # 動画の最後に達した

        # super resolution
        output = sr(model, args, frame_rgb)

        # FFmpegの出力ストリームへRGBフレームを送る
        ff.put_rgb(output)

        bar.update(1)
    bar.close()
    ff.close()
```

# さいごに
処理時間がとんでもないことになっていますが、SwinIRで得られる画像はとても綺麗で「ここまで出来るんだ」と驚きました。

10年くらい前 高速道路の違反車を特定するのに 監視カメラの映像からナンバープレートを読み取る技術がテレビ番組で紹介されていました。一枚一枚の画像は解像度が低く文字の判別は出来ないけど、複数の画像から文字が視認できるまで情報を復元する技術に「これが自力でできたらいいのに」と思っていた夢の技術がもしかしたら・・・今手元にあるもので可能なのかもしれません。