AnimeGANv2 + Gradio 轻量展示
系列仓库地址:https://github.com/xuanhao44/AnimeGANv2
更新:文中 3.3 问题已被解决:见 https://www.sheniao.top/tech/197.html。
0 服务器
带显卡的服务器:RTX A4000。
另外,在 RTX 4090 上也测试过,shell 提示的信息略有区别,但是不影响最后的结果。
不知为何,在 RTX 2080 Ti 上出现了错误,故本次不使用。
1 Gradio 介绍
简单来说就是快速生成 AI 模型的前端展示页面。
重要要求:至少 Python 3.8 以上。
样例代码:
import gradio as gr
def video_identity(video):
return video
demo = gr.Interface(
video_identity,
inputs=gr.Video(),
outputs="playable_video"
)
if __name__ == "__main__":
demo.launch(share=True)
服务器启动:(xxxxxxxxxxxxx 是屏蔽)
(/cloud/newanime) ➜ ~ python app.py
Running on local URL: http://127.0.0.1:7860
Running on public URL: https://xxxxxxxxxxxxx.gradio.live
This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run `gradio deploy` from Terminal to deploy to Spaces (https://huggingface.co/spaces)
^CKeyboard interruption in main thread... closing server.
Killing tunnel 127.0.0.1:7860 <> https://xxxxxxxxxxxxx.gradio.live
效果:
2 部署的改变
AnimeGANv2 的环境是 Python 3.6,经测试 Python 3.7 也可用,但在 Python 3.8 下,Tensorflow 1.15 不再被支持,因此无法使用。
而 Gradio 确实是需要 Python 3.8 及以上,经测试,在 Python 3.6 下无法运行,在 Python 3.7 下无法正常输出。
那么该怎么办呢?在网上找到了 Python 3.8 以上版本安装 Tensorflow 1.15 的方法。
参考:https://blog.csdn.net/hadoopdevelop/article/details/128531447
于是改变创建虚拟环境的命令:
conda create --prefix /cloud/newanime python=3.8 -y
conda activate /cloud/newanime
pip install --user opencv-python==4.2.0.32
pip install --user tqdm
pip install --user numpy
pip install --user glob2
pip install --user argparse
pip install --user onnxruntime
pip install --user gradio
pip install --user socksio
conda install --prefix /cloud/newanime cudatoolkit==10.0.130 -y
conda install --prefix /cloud/newanime cudnn=7.6.0=cuda10.0_0 -y
pip install --user nvidia-pyindex
pip install --user nvidia-tensorboard==1.15
pip install --user nvidia-tensorflow
经初步测试,AnimeGANv2 和 Gradio 两者都可正常运行。
每次启动之后指令:
conda activate /cloud/newanime
cd AnimeGANv2
python app.py
直接开始运行。
3 拼接
从源代码中抄了一些函数(函数内容省略,cvt2anime_video
略有改动),然后拼到一起。
cvt2anime_video
函数的output_format
改为小写的mp4v
,这样就不会在加载视频的时候报错。函数添加第一句:
tf.reset_default_graph()
,这样就不会有 OpenCV VideoWriter 报错(虽然不影响)。
更新:文档中代码可能不是最新版本的,最新请参考:https://github.com/xuanhao44/AnimeGANv2/blob/main/app.py
import gradio as gr
import os
import cv2
from tqdm import tqdm
import numpy as np
import tensorflow as tf
from net import generator
def check_folder(path):
if not os.path.exists(path):
os.makedirs(path)
return path
def process_image(img, x32=True):
h, w = img.shape[:2]
if x32: # resize image to multiple of 32s
def to_32s(x):
return 256 if x < 256 else x - x%32
img = cv2.resize(img, (to_32s(w), to_32s(h)))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32)/ 127.5 - 1.0
return img
def post_precess(img, wh):
img = (img.squeeze()+1.) / 2 * 255
img = img.astype(np.uint8)
img = cv2.resize(img, (wh[0], wh[1]))
return img
def cvt2anime_video(video, output, checkpoint_dir, output_format='mp4v'): # 小写就不报错了,只是仍然无法在浏览器上播放
'''
output_format: 4-letter code that specify codec to use for specific video type. e.g. for mp4 support use "H264", "MP4V", or "X264"
'''
tf.reset_default_graph() # Python 的控制台会保存上次运行结束的变量
gpu_stat = bool(len(tf.config.experimental.list_physical_devices('GPU')))
if gpu_stat:
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
gpu_options = tf.GPUOptions(allow_growth=gpu_stat)
test_real = tf.placeholder(tf.float32, [1, None, None, 3], name='test')
with tf.variable_scope("generator", reuse=False):
test_generated = generator.G_net(test_real).fake
saver = tf.train.Saver()
# load video
vid = cv2.VideoCapture(video)
vid_name = os.path.basename(video)
total = int(vid.get(cv2.CAP_PROP_FRAME_COUNT))
fps = vid.get(cv2.CAP_PROP_FPS)
width = int(vid.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT))
codec = cv2.VideoWriter_fourcc(*output_format)
tfconfig = tf.ConfigProto(allow_soft_placement=True, gpu_options=gpu_options)
with tf.Session(config=tfconfig) as sess:
# tf.global_variables_initializer().run()
# load model
ckpt = tf.train.get_checkpoint_state(checkpoint_dir) # checkpoint file information
if ckpt and ckpt.model_checkpoint_path:
ckpt_name = os.path.basename(ckpt.model_checkpoint_path) # first line
saver.restore(sess, os.path.join(checkpoint_dir, ckpt_name))
print(" [*] Success to read {}".format(os.path.join(checkpoint_dir, ckpt_name)))
else:
print(" [*] Failed to find a checkpoint")
return
video_out = cv2.VideoWriter(os.path.join(output, vid_name.rsplit('.', 1)[0] + "_AnimeGANv2.mp4"), codec, fps, (width, height))
pbar = tqdm(total=total, ncols=80)
pbar.set_description(f"Making: {os.path.basename(video).rsplit('.', 1)[0] + '_AnimeGANv2.mp4'}")
while True:
ret, frame = vid.read()
if not ret:
break
frame = np.asarray(np.expand_dims(process_image(frame),0))
fake_img = sess.run(test_generated, feed_dict={test_real: frame})
fake_img = post_precess(fake_img, (width, height))
video_out.write(cv2.cvtColor(fake_img, cv2.COLOR_BGR2RGB))
pbar.update(1)
pbar.close()
vid.release()
video_out.release()
return os.path.join(output, vid_name.rsplit('.', 1)[0] + "_AnimeGANv2.mp4")
def anime(video_filepath, style):
try:
output = "output"
check_folder(output) # 空文件夹真烦人
checkpoint_dir = "checkpoint/generator_Hayao_weight"
if style == "《起风了》(宫崎骏)":
checkpoint_dir = "checkpoint/generator_Hayao_weight"
elif style == "《红辣椒》(今敏)":
checkpoint_dir = "checkpoint/generator_Paprika_weight"
elif style == "《你的名字》(新海诚)":
checkpoint_dir = "checkpoint/generator_Shinkai_weight"
try:
output_filepath = cvt2anime_video(video_filepath, output, checkpoint_dir)
return output_filepath
except RuntimeError as error:
print('Error', error)
except Exception as error:
print('global exception', error)
return None, None
demo = gr.Interface(
fn=anime,
inputs=[
gr.Video(source="upload"),
gr.Dropdown([
'《起风了》(宫崎骏)', # Hayao
'《红辣椒》(今敏)', # Paprika
'《你的名字》(新海诚)', # Shinkai
],
type="value", # 默认
value='《起风了》(宫崎骏)',
label='style'),
],
outputs=[
gr.PlayableVideo(),
],
allow_flagging='never',
examples=[
["sample/1.mp4", "《你的名字》(新海诚)"],
["sample/2.mp4", "《红辣椒》(今敏)"],
["sample/3.mp4", "《你的名字》(新海诚)"],
["sample/4.mp4", "《你的名字》(新海诚)"],
["sample/5.mp4", "《你的名字》(新海诚)"],
],
cache_examples=True, # 缓存示例以实现快速运行,如修改需要手动删除
)
if __name__ == "__main__":
# https://www.gradio.app/guides/setting-up-a-demo-for-maximum-performance
# queue 方法允许用户通过创建一个队列来控制请求的处理速率,从而实现更好的控制。用户可以设置一次处理的请求数量,并向用户显示他们在队列中的位置。
demo.launch(share=True, show_error=True)
3.1 关于示例缓存
用法以及注意事项:
- https://www.gradio.app/guides/key-features#styling
- https://www.gradio.app/guides/more-on-examples
- https://www.gradio.app/docs/examples
示例的输出缓存会在一开始就被生成,并被放在项目目录下的 gradio_cached_examples
文件夹下。
之后点到下面的样例就可以直接得到输出(注意不需要点 submit),点了 submit 就会重新生成。
在示例文件或者是模型更改后,要把 gradio_cached_examples
文件夹整个删除,不然 Gradio 会认为缓存可以继续使用。反过来说,如果确定缓存不会变化,那么就可以把这个文件夹直接放到云盘中项目相应的位置。
下面是一点碎碎念:
输入进来的视频文件放在 /tmp
中,不能自己指定位置;
示例缓存也不能自己指定文件,非要他启动之后自己跑一遍得到,真是有够麻烦的。
3.2 框架太轻量
由于 Gradio 本身就是很轻量的,所以对于并发和大文件的效果都不是很好。
官方建议用 queue 控制并发,但是实际效果不佳,不能很好的处理。
大文件问题更是该仓库 issue 中被问到很多次的问题(看开发者回复似乎 4.0 版本会改进)。事实上经常有前端已经发出 error,但是后端还在处理的情况。
- https://github.com/gradio-app/gradio/issues/2322
- https://github.com/gradio-app/gradio/issues/2700
- https://github.com/gradio-app/gradio/issues/3845
- https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI/issues/160
- https://stackoverflow.com/questions/77181434/using-gradio-with-http2-due-to-google-cloud-run-request-size-limit
目前只能使用尽量小的视频文件来测试。建议大小 1MB 左右。
3.3 无法生成 H264 编码的 mp4 视频
注意到 output 的 video 可以下载,但是无法在浏览器上播放。初步判断为视频编码格式的问题。
具体详细见:
视频编码的问题比较复杂。仅从结论来说,是 OpenCV 调用的 FFMPEG 没有生成 H264 编码的 mp4 视频的功能。
解决的办法有两种:
第一种是自己编译 OpenCV。
第二种是绕个弯子,先生成 avi,然后转成 H264 编码的 mp4。
个人认为从性能上考虑应该使用第一种。
4 尝试解决 3.3 —— 自己编译 OpenCV(已失败)
在前面的环境基础上继续操作:
先卸载掉原有的 OpenCV。
pip uninstall opencv-python==4.2.0.32
4.1 按照原样测试——包版本冲突
git clone --recursive https://github.com/skvark/opencv-python.git
cd opencv-python
export CMAKE_ARGS="-DWITH_FREETYPE=ON"
export ENABLE_CONTRIB=1
export ENABLE_HEADLESS=0
export MAKEFLAGS="-j $(($(nproc)-1))"
pip wheel . --verbose
编译过程中的几个小问题:
输出 | 问题 | 解决 | 参考 |
---|---|---|---|
CMake Error: CMake was unable to find a build program corresponding to“Ninja“. | 没安装 Ninja | sudo apt-get update -y<br/>sudo apt-get install -y ninja-build | https://blog.csdn.net/qq_24345071/article/details/116331648 |
“Cmake error :generator: Ninja“ | 没删除 cache | 见参考页面 | https://rtoax.blog.csdn.net/article/details/108143830 |
关键问题:
在运行 app.py
时出错,输出:
global exception cudaGetDevice() failed. Status: CUDA driver version is insufficient for CUDA runtime version
尝试解决:强行安装编译好的 OpenCV:(之前把安装文件转移到云盘中了)
pip install --force-reinstall --user ~/work/opencv_contrib_python-4.6.0.66-cp38-cp38-linux_x86_64.whl
报错,输出:
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.
nvidia-tensorflow 1.15.5+nv23.3 requires numpy<1.24,>=1.22.0; python_version >= "3.7", but you have numpy 1.24.4 which is incompatible.
Successfully installed numpy-1.24.4 opencv-contrib-python-4.6.0.66
发现所需要的 numpy
版本冲突。NVIDIA 包需要 1.24 以下的,而 OpenCV 顺带安装了 1.24.4。
4.6.0 版本(b45a6a9f43d29acdea4b4a4e88078fb1923f417b (tag: 66) Merge pull request #668 from asenyaev/asen/check_latest_commits_4.x)
(注:数字为 commit id,可用 git reset --hard xxxxxxx
切换版本;查看 commit id 方法是 git log --pretty=oneline
)
有同样的问题,不再提出。
观察到如果后安装 OpenCV,最后的 numpy
就是 1.24.4 版本,故猜想先安装 OpenCV,然后安装 NVIDIA 包,可能就可以解决问题。
在 4.6.0 版本下,尝试先安装 OpenCV,后安装 NVIDIA 包——无效,还是 cudaGetDevice() failed
。
4.2 使用低版本——编译过程出错
4.5.1 版本(fd4e6040af94d9db3da25874132ca092b4c9e3a1 (tag: 48) disable Qt on macOS for now due to multiple issues)
关键问题:
- 编译过程中报错:
File "<string>", line 451, in _classify_installed_files_override TypeError: _classify_installed_files() got an unexpected keyword argument 'cmake_install_dir'
- 问题:
opencv-python
版本低,应升级版本 4.6 及以上 - 解决:在使用低于 4.6 版本的情况下无法解决。
- 参考:https://blog.csdn.net/weixin_44359953/article/details/127257534
4.5.3 版本(86c3d2a285c16f95a66c07275187da3f95be0af5 (tag: 56) Merge pull request #500 from williamjacksn/upload-sdist-to-pypi)
编译过程中报错:
OpenCV:cv2.cpp:23:33: fatal error: numpy/ndarrayobject.h
解决:
sudo apt-get install python-numpy
参考:https://blog.51cto.com/u_13161667/3295685
但是,编译过程中同样出现 TypeError: _classify_installed_files() got an unexpected keyword argument 'cmake_install_dir'
,也即版本过低问题。
4.3 尝试直接使用 pip 安装
直接重新建一套环境:
conda create --prefix /cloud/animeneo python=3.8 -y
conda activate /cloud/animeneo
pip install --user opencv-contrib-python==4.5.1.48
pip install --user tqdm
pip install --user numpy
pip install --user glob2
pip install --user argparse
pip install --user onnxruntime
pip install --user gradio
pip install --user socksio
conda install --prefix /cloud/animeneo cudatoolkit==10.0.130 -y
conda install --prefix /cloud/animeneo cudnn=7.6.0=cuda10.0_0 -y
pip install --user nvidia-pyindex
pip install --user nvidia-tensorboard==1.15
pip install --user nvidia-tensorflow
conda install --prefix /cloud/animeneo -c conda-forge openh264 -y
虽然可以跑,但是不能解决问题。到此我已无法解决,停手。
用 https://convertio.co/ 转换了一下编码,最后勉强能使用。