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 介绍

官网:https://www.gradio.app/

简单来说就是快速生成 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

效果:

Gradio 效果

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 略有改动),然后拼到一起。


更新:文档中代码可能不是最新版本的,最新请参考: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 关于示例缓存

用法以及注意事项:

示例的输出缓存会在一开始就被生成,并被放在项目目录下的 gradio_cached_examples 文件夹下。

之后点到下面的样例就可以直接得到输出(注意不需要点 submit),点了 submit 就会重新生成。

在示例文件或者是模型更改后,要把 gradio_cached_examples 文件夹整个删除,不然 Gradio 会认为缓存可以继续使用。反过来说,如果确定缓存不会变化,那么就可以把这个文件夹直接放到云盘中项目相应的位置


下面是一点碎碎念:

输入进来的视频文件放在 /tmp 中,不能自己指定位置;

示例缓存也不能自己指定文件,非要他启动之后自己跑一遍得到,真是有够麻烦的。

3.2 框架太轻量

由于 Gradio 本身就是很轻量的,所以对于并发和大文件的效果都不是很好。


官方建议用 queue 控制并发,但是实际效果不佳,不能很好的处理。


大文件问题更是该仓库 issue 中被问到很多次的问题(看开发者回复似乎 4.0 版本会改进)。事实上经常有前端已经发出 error,但是后端还在处理的情况。

目前只能使用尽量小的视频文件来测试。建议大小 1MB 左右。

3.3 无法生成 H264 编码的 mp4 视频

注意到 output 的 video 可以下载,但是无法在浏览器上播放。初步判断为视频编码格式的问题。

具体详细见:

视频编码的问题比较复杂。仅从结论来说,是 OpenCV 调用的 FFMPEG 没有生成 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“.没安装 Ninjasudo apt-get update -y<br/>sudo apt-get install -y ninja-buildhttps://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'

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/ 转换了一下编码,最后勉强能使用。

最后修改:2023 年 11 月 27 日
如果觉得我的文章对你有用,请随意赞赏