【OpenVINO+paddle】一切皆可二次元-CPU部署飞桨GAN实现所有照片二次元化
在这篇文章中我将小喵咪、小姐姐、钢铁侠图片二次元化,事实上你可以尝试任何一张你感兴趣的图像。在这里你将会使用到PP-AnimeGAN模型,将这个模型导出为onnx模型,然后转换为OpenVINO的IR格式,并在CPU上实现任意的图片的二次元化。
免责声明:上面所有图片都是采用百度公开的图片,如果有侵权图片请联系我删除。
想要了解更过飞桨的这个算法,可以点击这个链接 PaddleGAN’s AnimeGAN
我已经在aistudio挂载了我所有的源代码和代码结果,大家可以直接点开下面的链接进行尝试。
https://aistudio.baidu.com/aistudio/projectdetail/3591452?contributionType=1
引用会使用到的库
这里我们的环境是使用jupyter notebook,如果小伙伴还没有安装到openvino的库可以看下我这篇文稿,直接一键配置环境。
https://aistudio.baidu.com/aistudio/projectdetail/3563713?contributionType=1
import sys
import time
import os
from pathlib import Path
import cv2
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import HTML, display
from openvino.inference_engine import IECore
# PaddlePaddle需要c++编译器。下面的代码主要针对window,因为linux大多数都有C++
#安装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)
# 创建好转换的模型的文件名。
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):
"""
调整' image '为' max_width ',保持图像的长宽比。
"""
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 notebook 上代码后面打’ ??'快捷方式显示文档字符串和函数的源代码。
下面我们将定义好AnimeGANPredictor()并从PaddlePaddle下载权重。下载可能需要一段时间,下载一次就行反复下载也没啥。
predictor = AnimeGANPredictor()
predictor.run??
这里介绍一下AnimeGANPredictor.run()的使用方法:
- 使用OpenCV加载图像并将其转换为RGB
- 变换图像
3.通过生成器模型传播转换后的图像,并对结果进行后处理,会返回一个范围为[0,255]的数组 - 将(C,H,W)的结果转置为(H,W,C)形状
- 将结果图像的大小调整为原始图像大小
- 可选地调整结果图像的亮度
- 保存图像
我们可以手动执行这些步骤并确认结果。如果要加快推理时间,需要在通过网络传播大型图像之前调整它们的大小。下一个单元格中的推理步骤仍然需要一些时间来执行。
PADDLEGAN_INFERENCE = True
OUTPUT_DIR = "output"
os.makedirs(OUTPUT_DIR, exist_ok=True)
#第1步。加载图像并转换为RGB
image_path = Path("data/gangtiexia.jpeg")
image = cv2.cvtColor(cv2.imread(str(image_path), flags=cv2.IMREAD_COLOR), cv2.COLOR_BGR2RGB)
##对大的图像进行推理需要很长时间。将大小调整为最大宽度600
image = resize_to_max_width(image, 600)
#第2步。变换图像
transformed_image = predictor.transform(image)
input_tensor = paddle.to_tensor(transformed_image[None, ::])
if PADDLEGAN_INFERENCE:
#第三步。做推理。
predictor.generator.eval()
with paddle.no_grad():
result = predictor.generator(input_tensor)
#第四步。按照与下面相同的步骤将推理结果转换为图像
# padlegan的predictor.run()函数
result_image_pg = (result * 0.5 + 0.5)[0].numpy() * 255
result_image_pg = result_image_pg.transpose((1, 2, 0))
#第五步。调整结果图像的大小
result_image_pg = cv2.resize(result_image_pg, image.shape[:2][::-1])
#步骤6。调整brightness
result_image_pg = predictor.adjust_brightness(result_image_pg, image)
#步骤7。保存结果图像
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}")
显示GAN模型的推理结果
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.")
你可以看下结果
将模型转换到ONNX和IR
我们将PaddleGAN模型转换为OpenVINO IR,首先使用“paddle2onnx”将PaddleGAN转换为ONNX,然后使用OpenVINO的模型优化器将ONNX模型转换为IR。
转换为ONNX
导出到ONNX需要指定输入形状与PaddlePaddle的’ InputSpec ‘和调用’ PaddlePaddle . ONNX .export ‘。我们检查转换后的图像的输入形状,并使用它作为ONNX模型的输入形状。输出到ONNX应该不会花很长时间。如果导出成功,结果输出会显示’ ONNX模型保存在paddlegan_animation . ONNX '。
target_height, target_width = transformed_image.shape[1:]
target_height, target_width
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)
转换为IR模型
OpenVINO IR格式允许在模型文件中存储预处理规范化。这样就不再需要手动对输入图像进行归一化。我们我们可以??来检查一下’ .run() '方法使用的转换:
predictor.__init__??
t = predictor.transform.transforms[0]
t.params
这里一共有三种转换:resize、transpose和normalize,其中normalize使用平均值和刻度[127.5,127.5,127.5]。
使用(256,256)作为大小参数调用ResizeToScale类。进一步的分析表明这是事实
要调整到的最小大小。对象中指定的图像大小
ResizeToScale参数,宽度和高度为32倍。
现在我们知道了均值和标准差,以及模型输入的形状,我们可以调用model Optimizer并使用这些值将模型转换为IR。我们使用FP16精度,并将日志级别设置为CRITICAL,以忽略与本演示无关的警告。有关模型优化器参数的信息,请参阅模型优化器文档。
转换模型到IR与Model Optimizer
onnx_path = model_path.with_suffix(".onnx")
print("Exporting ONNX model to IR... This may take a few minutes.")
!mo --input_model $onnx_path --output_dir $MODEL_DIR --input_shape [1,3,$target_height,$target_width] --model_name $MODEL_NAME --data_type "FP16" --mean_values="[127.5,127.5,127.5]" --scale_values="[127.5,127.5,127.5]" --log_level "CRITICAL"
显示IR和PaddleGAN模型的推理结果
创建Postprocessing函数
predictor.adjust_brightness??
predictor.calc_avg_brightness??
平均亮度是通过标准公式计算出来的,请参阅https://www.w3.org/TR/AERT/#color-contrast 通过计算源图像与目标图像的亮度差来调整目标图像的亮度。然后将图像转换为8位图像。
我们将这些函数复制到下一个单元格,并使用它们对IR模型进行推理
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
对IR模型进行推理
加载IR模型并进行推理,执行与PaddleGAN模型相同的步骤。请参阅OpenVINO推理引擎API了解更多关于IR模型推理的信息。
IR模型是用基于输入图像计算的输入形状生成的。如果对具有不同输入形状的图像进行推理,结果可能与PaddleGAN结果不同。
# 加载和准备IR模型
ie = IECore()
net = ie.read_network(ir_path)
exec_net = ie.load_network(net, "CPU")
input_key = next(iter(net.input_info.keys()))
output_key = next(iter(net.outputs.keys()))
# Step 1.加载图片如何转化成RGB
image_path = Path("data/cat.jpeg")
image = cv2.cvtColor(cv2.imread(str(image_path), flags=cv2.IMREAD_COLOR), cv2.COLOR_BGR2RGB)
# Step 2. 重新定义图片尺寸
resized_image = cv2.resize(image, (target_width, target_height))
input_image = resized_image.transpose(2, 0, 1)[None, :, :, :]
# Step 3.进行推理
result_ir = exec_net.infer({input_key: input_image})
# Step 4. 按照与下面相同的步骤将推理结果转换为图像
# PaddleGAN's predictor.run() function
result_image_ir = (result_ir[output_key] * 0.5 + 0.5)[0] * 255
result_image_ir = result_image_ir.transpose((1, 2, 0))
# Step 5. 重新定义图像
result_image_ir = cv2.resize(result_image_ir, image.shape[:2][::-1])
# Step 6. 调整brightness
result_image_ir = adjust_brightness(result_image_ir, image)
# Step 7. 保存图片
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}")
显示推理结果
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");
性能比较
测量对图像进行推理所需的时间。这给出了性能指标。这不是一个完美的衡量标准。由于PaddleGAN模型需要相当多的内存来进行推理,所以我们只测量一个图像上的推理。要进行更精确的基准测试
NUM_IMAGES = 1
start = time.perf_counter()
for _ in range(NUM_IMAGES):
exec_net.infer(inputs={input_key: input_image})
end = time.perf_counter()
time_ir = end - start
print(
f"IR model in Inference Engine/CPU: {time_ir/NUM_IMAGES:.3f} "
f"seconds per image, FPS: {NUM_IMAGES/time_ir:.2f}"
)
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}"
)