PaddleGAN と OpenVINO を使用して写真をアニメに変換

この Jupyter ノートブックはオンラインで起動でき、ブラウザーのウィンドウで対話型環境を開きます。ローカルにインストールすることもできます。次のオプションのいずれかを選択します。

Google Colab GitHub

このチュートリアルでは、PaddlePaddle/PaddleGAN AnimeGAN モデルを OpenVINO IR 形式に変換する方法を示し、PaddleGAN および OpenVINO IR モデルの推論結果を示します。

モデルの詳細については、PaddleGAN の AnimeGAN ドキュメントを参照してください。

anime

アニメ

目次

準備

要件をインストール

%pip install -q "openvino>=2023.1.0"

%pip install -q "paddlepaddle>=2.5.1" "paddle2onnx>=0.6"
%pip install -q "git+https://github.com/PaddlePaddle/PaddleGAN.git" --no-deps

%pip install -q opencv-python matplotlib scikit-learn scikit-image
%pip install -q "imageio==2.9.0" "imageio-ffmpeg" "numba>=0.53.1" easydict munch natsort
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
paddleclas 2.5.1 requires faiss-cpu==1.7.1.post2, but you have faiss-cpu 1.7.4 which is incompatible.
paddleclas 2.5.1 requires gast==0.3.3, but you have gast 0.4.0 which is incompatible.
ppgan 2.1.0 requires librosa==0.8.1, but you have librosa 0.10.1 which is incompatible.
ppgan 2.1.0 requires opencv-python<=4.6.0.66, but you have opencv-python 4.9.0.80 which is incompatible.
scikit-image 0.21.0 requires imageio>=2.27, but you have imageio 2.9.0 which is incompatible.
Note: you may need to restart the kernel to use updated packages.

インポート

import sys
import time
import os
from pathlib import Path
import urllib.request

import cv2
import matplotlib.pyplot as plt
import numpy as np
import openvino as ov
from IPython.display import HTML, display

# PaddlePaddle requires a C++ compiler. If importing the paddle packages fails,
# install C++.
try:
    import paddle
    from paddle.static import InputSpec
    from ppgan.apps import AnimeGANPredictor
except NameError:
    if sys.platform == "win32":
        install_message = (
            "To use this notebook, please install the free Microsoft "
            "Visual C++ redistributable from <a href='https://aka.ms/vs/16/release/vc_redist.x64.exe'>"
            "https://aka.ms/vs/16/release/vc_redist.x64.exe</a>"
        )
    else:
        install_message = (
            "To use this notebook, please install a C++ compiler. On macOS, "
            "`xcode-select --install` installs many developer tools, including C++. On Linux, "
            "install gcc with your distribution's package manager."
        )
    display(
        HTML(
            f"""<div class="alert alert-danger" ><i>
    <b>Error: </b>PaddlePaddle requires installation of C++. {install_message}"""
        )
    )
    raise

設定

MODEL_DIR = "model"
MODEL_NAME = "paddlegan_anime"

os.makedirs(MODEL_DIR, exist_ok=True)

# Create filenames of the models that will be converted in this notebook.
model_path = Path(f"{MODEL_DIR}/{MODEL_NAME}")
ir_path = model_path.with_suffix(".xml")
onnx_path = model_path.with_suffix(".onnx")

関数

def resize_to_max_width(image, max_width):
    """
    Resize `image` to `max_width`, preserving the aspect ratio of the image.
    """
    if image.shape[1] > max_width:
        hw_ratio = image.shape[0] / image.shape[1]
        new_height = int(max_width * hw_ratio)
        image = cv2.resize(image, (max_width, new_height))
    return image

PaddleGAN モデルの推論

PaddleGAN のドキュメントでは、.run() メソッドを使用してモデルを実行する方法が説明されています。Jupyter の ?? ショートカットを使用して関数のドキュメント文字列とソースを表示し、その関数が何を行うかを確認します。

# This cell will initialize the AnimeGANPredictor() and download the weights from PaddlePaddle.
# This may take a while. The weights are stored in a cache and are downloaded once.
predictor = AnimeGANPredictor()
[02/09 23:41:27] ppgan INFO: Found /opt/home/k8sworker/.cache/ppgan/animeganv2_hayao.pdparams
# In a Jupyter Notebook, ?? shows the source and docstring
predictor.run??

AnimeGANPredictor.run() メソッドは次のように動作します。

  1. OpenCV で画像を読み込み RGB に変換します。

  2. 画像を変換します。

  3. 変換された画像をジェネレーター・モデルに伝播し、結果を後処理して [0,255] の範囲の配列を返します。

  4. 結果を (C,H,W) から (H,W,C) の形状に転置します。

  5. 結果の画像を元の画像サイズに変更します。

  6. (オプション) 結果画像の明るさを調整します。

  7. 画像を保存します。

これらの手順を手動で実行し、結果が正しいことを確認できます。推論時間を短縮するには、ネットワークを介して伝播する前に大きな画像のサイズを変更します。次のセルの推論ステップの実行にはまだ時間がかかります。この手順をスキップするには、次のセルの最初の行で PADDLEGAN_INFERENCE = False を設定します。

PADDLEGAN_INFERENCE = True
OUTPUT_DIR = "output"

os.makedirs(OUTPUT_DIR, exist_ok=True)
# Step 1. Load the image and convert to RGB.
image_path = Path("./data/coco_bricks.png")
# fetch the image from the web
image_path.parent.mkdir(parents=True, exist_ok=True)
urllib.request.urlretrieve(
    "https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco_bricks.png",
    image_path
)

image = cv2.cvtColor(cv2.imread(str(image_path), flags=cv2.IMREAD_COLOR), cv2.COLOR_BGR2RGB)

## Inference takes a long time on large images. Resize to a max width of 600.
image = resize_to_max_width(image, 600)

# Step 2. Transform the image.
transformed_image = predictor.transform(image)
input_tensor = paddle.to_tensor(transformed_image[None, ::])

if PADDLEGAN_INFERENCE:
    # Step 3. Do inference.
    predictor.generator.eval()
    with paddle.no_grad():
        result = predictor.generator(input_tensor)

    # Step 4. Convert the inference result to an image, following the same steps as
    # PaddleGAN's predictor.run() function.
    result_image_pg = (result * 0.5 + 0.5)[0].numpy() * 255
    result_image_pg = result_image_pg.transpose((1, 2, 0))

    # Step 5. Resize the result image.
    result_image_pg = cv2.resize(result_image_pg, image.shape[:2][::-1])

    # Step 6. Adjust the brightness.
    result_image_pg = predictor.adjust_brightness(result_image_pg, image)

    # Step 7. Save the result image.
    anime_image_path_pg = Path(f"{OUTPUT_DIR}/{image_path.stem}_anime_pg").with_suffix(".jpg")
    if cv2.imwrite(str(anime_image_path_pg), result_image_pg[:, :, (2, 1, 0)]):
        print(f"The anime image was saved to {anime_image_path_pg}")
The anime image was saved to output/coco_bricks_anime_pg.jpg

PaddleGAN モデルの推論結果を表示

if PADDLEGAN_INFERENCE:
    fig, ax = plt.subplots(1, 2, figsize=(25, 15))
    ax[0].imshow(image)
    ax[1].imshow(result_image_pg)
else:
    print("PADDLEGAN_INFERENCE is not enabled. Set PADDLEGAN_INFERENCE = True in the previous cell and run that cell to show inference results.")
../_images/206-vision-paddlegan-anime-with-output_15_0.png

ONNX および OpenVINO IR へのモデル変換

まず paddle2onnx を使用して PaddleGAN を ONNX に変換し、次にモデル変換 API を使用して ONNX モデルを OpenVINO IR に変換して、PaddleGAN モデルを OpenVINO IR に変換します。

ONNX への変換

ONNX にエクスポートするには、PaddlePaddle InputSpec で入力シェイプを指定し、paddle.onnx.export を呼び出す必要があります。次に、変換された画像の入力形状を確認し、ONNX モデルの入力形状として使用します。ONNX へのエクスポートにはそれほど時間はかかりません。エクスポートが成功すると、次のセルの出力には paddlegan_anime.onnx に保存された ONNX モデルが含まれます。

target_height, target_width = transformed_image.shape[1:]
target_height, target_width
(448, 576)
predictor.generator.eval()
x_spec = InputSpec([None, 3, target_height, target_width], "float32", "x")
paddle.onnx.export(predictor.generator, str(model_path), input_spec=[x_spec], opset_version=11)
2024-02-09 23:41:36 [INFO]  Static PaddlePaddle model saved in model/paddle_model_static_onnx_temp_dir.
[Paddle2ONNX] Start to parse PaddlePaddle model...
[Paddle2ONNX] Model file path: model/paddle_model_static_onnx_temp_dir/model.pdmodel
[Paddle2ONNX] Paramters file path: model/paddle_model_static_onnx_temp_dir/model.pdiparams
[Paddle2ONNX] Start to parsing Paddle model...
[Paddle2ONNX] Use opset_version = 11 for ONNX export.
[Paddle2ONNX] PaddlePaddle model is exported as ONNX format now.
2024-02-09 23:41:36 [INFO]  ONNX model saved in model/paddlegan_anime.onnx.
I0209 23:41:36.202327 2843665 program_interpreter.cc:212] New Executor is Running.

OpenVINO IRへの変換

OpenVINO IR 形式を使用すると、前処理の正規化をモデルファイルに保存できます。これにより、入力画像を手動で正規化する必要がなくなります。.run() メソッドが使用した変換を確認します。

predictor.__init__??
t = predictor.transform.transforms[0]
t.params
{'taget_size': (448, 576)}
## Uncomment the line below to see the documentation and code of the ResizeToScale transformation
# t??

変換には、サイズ変更、転置、正規化の 3 つがあり、正規化では平均とスケールとして [127.5, 127.5, 127.5] が使用されます。

ResizeToScale クラスは、サイズの引数として (256,256) を指定して呼び出されます。さらに分析すると、これがサイズ変更の最小サイズであることが分かります。ResizeToScale クラスの変換は、幅と高さが 32 の倍数になるように、ResizeToScale パラメーターで指定されたサイズに画像を変更します。変換されたモデルに画像を入力する前に、同じ方法で前処理を行います。

ここで、モデル変換 API を使用して、モデルを OpenVINO IR に変換します。

モデル変換 Python API を使用して ONNX モデルを OpenVINO IR に変換

print("Exporting ONNX model to OpenVINO IR... This may take a few minutes.")

model = ov.convert_model(
    onnx_path,
    input=[1, 3, target_height, target_width],
)

# Serialize model in IR format
ov.save_model(model, str(ir_path))
Exporting ONNX model to OpenVINO IR... This may take a few minutes.

OpenVINO IR および PaddleGAN モデルでの推論結果の表示

変換が成功すると、上のセルのモデル変換 API の出力に SUCCESS と表示され、OpenVINO IR モデルが生成されます。

ここで、PaddleGAN モデルの adjust_brightness() メソッドを適用して、モデルを推論に使用します。ただし、PaddleGAN をインストールせずに OpenVINO IR モデルを使用するには、これらの関数が何を行うかを確認し、抽出することが役立ちます。

後処理関数を作成

predictor.adjust_brightness??
predictor.calc_avg_brightness??

平均輝度は標準式によって計算されます。明るさを調整するには、ソース画像とデスティネーション (アニメ) 画像の明るさの差を計算し、それに基づいてデスティネーション画像の明るさを調整します。画像は 8 ビット画像に変換されます。

これらの関数を次のセルにコピーし、OpenVINO IRモデルの推論に使用します。

# Copyright (c) 2020 PaddlePaddle Authors. Licensed under the Apache License, Version 2.0


def calc_avg_brightness(img):
    R = img[..., 0].mean()
    G = img[..., 1].mean()
    B = img[..., 2].mean()

    brightness = 0.299 * R + 0.587 * G + 0.114 * B
    return brightness, B, G, R


def adjust_brightness(dst, src):
    brightness1, B1, G1, R1 = AnimeGANPredictor.calc_avg_brightness(src)
    brightness2, B2, G2, R2 = AnimeGANPredictor.calc_avg_brightness(dst)
    brightness_difference = brightness1 / brightness2
    dstf = dst * brightness_difference
    dstf = np.clip(dstf, 0, 255)
    dstf = np.uint8(dstf)
    return dstf

OpenVINO IR モデルで推論を行う

OpenVINO IR モデルをロードし、PaddleGAN モデルと同じ手順に従って推論を実行します。OpenVINO IR モデルの推論の詳細については、OpenVINO ランタイム API ノートブックを参照してください。

OpenVINO IR モデルは、入力画像に基づいて計算された入力形状を使用して生成されます。入力形状が異なる画像に対して推論を実行すると、結果が PaddleGAN の結果と異なる場合があります。

推論デバイスの選択

OpenVINO を使用して推論を実行するためにドロップダウン・リストからデバイスを選択します。

import ipywidgets as widgets

core = ov.Core()
device = widgets.Dropdown(
    options=core.available_devices + ["AUTO"],
    value='AUTO',
    description='Device:',
    disabled=False,
)
device
Dropdown(description='Device:', index=1, options=('CPU', 'AUTO'), value='AUTO')
# Load and prepare the IR model.
core = ov.Core()

model = core.read_model(model=ir_path)
compiled_model = core.compile_model(model=model, device_name=device.value)
input_key = compiled_model.input(0)
output_key = compiled_model.output(0)
# Step 1. Load an image and convert it to RGB.
image_path = Path("./data/coco_bricks.png")
image = cv2.cvtColor(cv2.imread(str(image_path), flags=cv2.IMREAD_COLOR), cv2.COLOR_BGR2RGB)

# Step 2. Do preprocess transformations.
# Resize the image
resized_image = cv2.resize(image, (target_width, target_height))
input_image = resized_image.transpose(2, 0, 1)[None, :, :, :]
# Normalize the image
input_mean = np.array([127.5,127.5,127.5]).reshape(1, 3, 1, 1)
input_scale = np.array([127.5,127.5,127.5]).reshape(1, 3, 1, 1)
input_image = (input_image - input_mean) / input_scale

# Step 3. Do inference.
result_ir = compiled_model([input_image])[output_key]

# Step 4. Convert the inference result to an image, following the same steps as
# PaddleGAN's predictor.run() function.
result_image_ir = (result_ir * 0.5 + 0.5)[0] * 255
result_image_ir = result_image_ir.transpose((1, 2, 0))

# Step 5. Resize the result image.
result_image_ir = cv2.resize(result_image_ir, image.shape[:2][::-1])

# Step 6. Adjust the brightness.
result_image_ir = adjust_brightness(result_image_ir, image)

# Step 7. Save the result image.
anime_fn_ir = Path(f"{OUTPUT_DIR}/{image_path.stem}_anime_ir").with_suffix(".jpg")
if cv2.imwrite(str(anime_fn_ir), result_image_ir[:, :, (2, 1, 0)]):
                                    print(f"The anime image was saved to {anime_fn_ir}")
The anime image was saved to output/coco_bricks_anime_ir.jpg

推論結果を表示

fig, ax = plt.subplots(1, 2, figsize=(25, 15))
ax[0].imshow(image)
ax[1].imshow(result_image_ir)
ax[0].set_title("Image")
ax[1].set_title("OpenVINO IR result");
../_images/206-vision-paddlegan-anime-with-output_37_0.png

パフォーマンスの比較

画像の推論にかかる時間を測定します。これはパフォーマンスの指標となります。これは完璧な測定ではありません。PaddleGAN モデルは推論にかなりのメモリーを必要とするため、1 つの画像でのみ推論を測定します。より正確なベンチマークを行うには、ベンチマーク・ツールを使用します。

NUM_IMAGES = 1
start = time.perf_counter()
for _ in range(NUM_IMAGES):
    compiled_model([input_image])
end = time.perf_counter()
time_ir = end - start
print(
    f"OpenVINO IR model in OpenVINO Runtime/CPU: {time_ir/NUM_IMAGES:.3f} "
    f"seconds per image, FPS: {NUM_IMAGES/time_ir:.2f}"
)

## `PADDLEGAN_INFERENCE` is defined in the "Inference on PaddleGAN model" section above.
## Uncomment the next line to enable a performance comparison with the PaddleGAN model
## if you disabled it earlier.

# PADDLEGAN_INFERENCE = True

if PADDLEGAN_INFERENCE:
    with paddle.no_grad():
        start = time.perf_counter()
        for _ in range(NUM_IMAGES):
            predictor.generator(input_tensor)
        end = time.perf_counter()
        time_paddle = end - start
    print(
        f"PaddleGAN model on CPU: {time_paddle/NUM_IMAGES:.3f} seconds per image, "
        f"FPS: {NUM_IMAGES/time_paddle:.2f}"
    )
OpenVINO IR model in OpenVINO Runtime/CPU: 0.427 seconds per image, FPS: 2.34
PaddleGAN model on CPU: 6.182 seconds per image, FPS: 0.16

関連情報

このノートブックに示されている PaddleGAN コードは、PaddlePaddle の作者によって作成され、Apache 2.0 に基づいてライセンスされています。このコードのライセンスは以下に示されています。

#  Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.