为了你有更好的体验,本项目建议在搭配V100 32G显存的至尊版环境下运行。(16G高级版可能会出现dlib包安装错误。)

项目背景

本项目由PaddleGAN的Photo2cartoon项目改造升级,项目的灵感来源于当前越来越多的视频博主出于容貌不自信或者其他原因不方便露脸。传统的换脸方式有iPhone自带的应用(拟我表情)和视频剪辑等方式,但以上方式存在样式和设备局限或技术要求高等问题,但通过本项目可以实现一行代码把所有视频的大头人像转换成卡通人像,做到千(美)人(白)千(亮)面(肤),并且还自动提取和合并原视频的音频到新视频当中,无需后期再人工编辑对齐。

环境安装

安装PaddleGAN最新版本

!git clone https://gitee.com/paddlepaddle/PaddleGAN -b develop
!pip install -r PaddleGAN/requirements.txt
%cd PaddleGAN
!python setup.py install
%cd ../

通过源码编译安装dlib

此处避免直接使用pip安装dlib包,可能会导致内存溢出。

!git clone https://hub.fastgit.org/davisking/dlib.git
#需先安装和更新cmake和boost包
!pip install cmake
!pip install boost
%cd dlib/
!python setup.py install
%cd ../

重启内核

点击一次工具栏中的重启按钮,加载刚刚安装好的依赖环境。

改进思路

原项目(Photo2cartoon)参考了U-GAT-IT论文,利用AdaLIN函数(自适应正则化函数)和新的注意力模块实现图像风格转换,关注这个模块详细可查看PaddleGAN文档。这里主要剖析一下原项目封装好的Photo2CartoonPredictor这个类。PaddleGAN/ppgan/apps/photo2cartoon_predictor.py 源代码如下:

import os
import cv2
from PIL import Image
import numpy as npimport paddle
from paddle.utils.download import get_path_from_url
from ppgan.faceutils.dlibutils import align_crop
from ppgan.faceutils.face_segmentation import FaceSeg
from ppgan.models.generators import ResnetUGATITP2CGenerator
from .base_predictor import BasePredictorP2C_WEIGHT_URL = "https://paddlegan.bj.bcebos.com/models/photo2cartoon_genA2B_weight.pdparams"class Photo2CartoonPredictor(BasePredictor):def __init__(self, output_path='output', weight_path=None):self.output_path = output_pathif not os.path.exists(self.output_path):os.makedirs(self.output_path)if weight_path is None:cur_path = os.path.abspath(os.path.dirname(__file__))weight_path = get_path_from_url(P2C_WEIGHT_URL, cur_path)self.genA2B = ResnetUGATITP2CGenerator()params = paddle.load(weight_path)self.genA2B.set_state_dict(params)self.genA2B.eval()self.faceseg = FaceSeg()def run(self, image_path):image = Image.open(image_path)face_image = align_crop(image)face_mask = self.faceseg(face_image)face_image = cv2.resize(face_image, (256, 256), interpolation=cv2.INTER_AREA)face_mask = cv2.resize(face_mask, (256, 256))[:, :, np.newaxis] / 255.face = (face_image * face_mask + (1 - face_mask) * 255) / 127.5 - 1face = np.transpose(face[np.newaxis, :, :, :], (0, 3, 1, 2)).astype(np.float32)face = paddle.to_tensor(face)# inferencewith paddle.no_grad():cartoon = self.genA2B(face)[0][0]# post-processcartoon = np.transpose(cartoon.numpy(), (1, 2, 0))cartoon = (cartoon + 1) * 127.5cartoon = (cartoon * face_mask + (1 - face_mask) * 255).astype(np.uint8)pnoto_save_path = os.path.join(self.output_path, 'p2c_photo.png')cv2.imwrite(pnoto_save_path, cv2.cvtColor(face_image, cv2.COLOR_RGB2BGR))cartoon_save_path = os.path.join(self.output_path, 'p2c_cartoon.png')cv2.imwrite(cartoon_save_path, cv2.cvtColor(cartoon, cv2.COLOR_RGB2BGR))print("Cartoon image has been saved at '{}'.".format(cartoon_save_path))return cartoon

通过阅读源代码,可以清晰看到真人头像图片转卡通图片的流程。原作者先是写了一个align_crop的函数,根据人脸关键点信息做了人像位置纠偏,并通过脸部位置识别后往四周拓展坐标裁剪出人头的区域,然后用了PaddleGAN内置的人脸处理库中的FaceSeg语义分割函数,从上一步输入图像中分割出人像。

那既然已经摸清了源代码的关系,那么就可以根据源代码进行修改,使得满足我们视频化的需求。首先,我们需要把视频分成若干帧,每一帧就等于是一张图片。然后我们还是需要先识别脸部,并把脸部范围坐标拓宽到人头区域,但与源代码的直接裁剪出人头图像不同,我们需要保留头像外的图像信息,因此我们需要记录人像的坐标信息,提取人像进行卡通化处理之后,需要把处理后的卡通人像重新paste到原图中。最后把原视频的声音提取出来,合并到新的视频即可。

代码解析

import cv2
from PIL import Image
import numpy as np
import paddle
from ppgan.faceutils.face_segmentation import FaceSeg
from ppgan.models.generators import ResnetUGATITP2CGenerator
from face_align import align_crop
from ppgan.apps.base_predictor import BasePredictor
from paddle.utils.download import get_weights_path_from_urlparams = paddle.load(get_weights_path_from_url('https://paddlegan.bj.bcebos.com/models/photo2cartoon_genA2B_weight.pdparams'))
genA2B = ResnetUGATITP2CGenerator()
genA2B.set_state_dict(params)
genA2B.eval()class Predictor(BasePredictor):def __init__(self, output_path='output'):self.faceseg = FaceSeg()def run(self, image_array):image = Image.fromarray(cv2.cvtColor(image_array,cv2.COLOR_BGR2RGB))face_image,face_bbox = align_crop(image) #裁剪人头区域face_mask = self.faceseg(face_image) #人像分割face_image_pil = Image.fromarray(face_image)face_image_size = face_image_pil.sizeface_image = cv2.resize(face_image, (256, 256), interpolation=cv2.INTER_AREA)face_mask = cv2.resize(face_mask, (256, 256))[:, :, np.newaxis] / 255.face = (face_image * face_mask + (1 - face_mask) * 255) / 127.5 - 1face = np.transpose(face[np.newaxis, :, :, :], (0, 3, 1, 2)).astype(np.float32)face = paddle.to_tensor(face)# inferencewith paddle.no_grad():cartoon = genA2B(face)[0][0]# post-processcartoon = np.transpose(cartoon.numpy(), (1, 2, 0))cartoon = (cartoon + 1) * 127.5cartoon = (cartoon * face_mask + (1 - face_mask) * 255).astype(np.uint8)#还原原图大小cartoon = cv2.resize(cartoon,(face_image_size[0],face_image_size[1]))#去除白色背景cartoon = Image.fromarray(cartoon).convert('RGBA')for i in range(0,cartoon.size[0]):     # 遍历所有长度的点for j in range(0,cartoon.size[1]):       # 遍历所有宽度的点data = cartoon.getpixel((i,j))  # 获取一个像素if (data.count(255) == 4):  # RGBA都是255,改成透明色cartoon.putpixel((i,j),(255,255,255,0))image = image.convert('RGBA')r, g, b, a = cartoon.split()image.paste(cartoon,(face_bbox),mask=a) #替换原图return image
import cv2
import os
from PIL import Image
import numpy as np
from moviepy.editor import *
import warnings
import logging
from tempfile import NamedTemporaryFile,gettempdirwarnings.filterwarnings('ignore') #忽略warning,如需要修改和调试代码请注释掉video2Cartoon = Predictor()
logging.info('正在转换,如果视频文件时长过长,消耗时间可能长达数十分钟,请耐心等候……')
input_Video = 'video/driving_video.mp4' #输入视频路径
output_Video = 'yiyi.mp4' #视频输出路径
temp_Video = NamedTemporaryFile(suffix='.mp4',dir='/tmp',delete=False).name
vidcap = cv2.VideoCapture(input_Video)
audio_ori = VideoFileClip(input_Video).audio #提取音频
success, image = vidcap.read()frame = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT)) #帧数
fps = int(vidcap.get(5)) #帧率
frame_height = int(vidcap.get(cv2.CAP_PROP_FRAME_HEIGHT)) #帧高度
frame_width = int(vidcap.get(cv2.CAP_PROP_FRAME_WIDTH)) #帧宽度videoWriter = cv2.VideoWriter(temp_Video, cv2.VideoWriter_fourcc(*"mp4v"), fps,(frame_width,frame_height))while success:try:img2Cartoon = cv2.cvtColor(np.asarray(video2Cartoon.run(image)),cv2.COLOR_RGB2BGR)videoWriter.write(img2Cartoon)except:videoWriter.write(image)success, image = vidcap.read()
videoWriter.release()#插入音频
audio_Output = VideoFileClip(temp_Video).set_audio(audio_ori)
audio_Output.write_videofile(output_Video)
logging.info('转换完成,文件已保存到{}'.format(output_Video))

生成示例视频大概需要6分钟(V100 32G环境)

一键运行脚本

python v2c.py --input_video 原视频路径 --output 输出视频路径

!python v2c.py --input_video video/driving_video.mp4 --output ./yiyi.mp4

写在最后

目前本项目支持大部分的视频转换,但并不是完美的,首先由于目前缺乏头部分割模型,目前用的方法是识别脸部然后expand裁剪出头部,因此会出现裁剪到衣领位置或者头发不完整,甚至导致个别竖版视频出现推理视频和原视频头像偏移位置较大的情况,这个暂时可以根据实际视频需要通过修改face_align.py中的crop()函数expand部分代码解决。希望有兴趣的开发者fork并完善开源,大家相互交流学习。

参考项目

PaddleGAN
千星人像卡通化项目登陆PaddleGAN

PPDE福利环节

最后,有请一位知名PPDE为大家献上才艺展示!!!欢迎大家一键三连!!!

Video2Cartoon一行代码把视频头像转换成卡通头像相关推荐

  1. win下海康工业相机使用python读取视频并转换成cv格式

    硬件设备:海康威视工业相机CA013-A0UC USB3 环境:win10,python3.7,海康MVS 海康工业相机环境配置(MVS) 配置好环境后可以运行一下MVS和MVS\Developmen ...

  2. 使用ffmpeg进行视频文件转换成FLV整理

    本系列文章导航 Windows下FFmpeg快速入门 ffmpeg参数解释 mencoder和ffmpeg参数详解(Java处理视频) Java 生成视频缩略图(ffmpeg) 使用ffmpeg进行视 ...

  3. Python妙用:使用一行代码下载视频

    "如何用一行Python代码下载<后浪>等全网视频" 01 基于Python开发 是的,这款下载工具包是基于Python开发的,实际它不只支持视频下载,还支持图片.音乐 ...

  4. vep文件如何转换mp4_如何将DVD的vob视频格式转换成mp4格式

    首先简述一下,VOB是DVD Video OBject的缩写,vob文件用来保存所有MPEG-2格式的音频和视频数据,这些数据不仅包含影片本身,而且还有供菜单和按钮用的画面以及多种字幕的子画面流.如何 ...

  5. 如何把视频语音转换成文字呢?

    视频语音转换成文字操作实际上是将视频中的音频语言转换成文本形式.如果正常要实现这种操作,我们一般需要先了解视频在讲解什么,然后再手动整理内容.但其实这个操作是需要时间的.而现在我们只需要一些转换工具就 ...

  6. 怎么把视频语音转换成文字呢?

    相信有不少小伙伴们喜欢在网上找相关的课程视频,通过自学课程来提升自己,但是有时候会发现有些课程并不需要视频演示.由于这些网络课堂图片包含的知识重点较少,比较枯燥,容易分散注意力.这时有些朋友会想把视频 ...

  7. 视频语音转换成文字要怎么操作呢?

    近年来,随着人们对视频拍摄的需求越来越大,视频传输的速度越来越快,视频存储的空间越来越大,各种场景中积累了大量的视频数据,这就需要一种有效的视频管理.分析和处理工具.有时就需要把一些视频语音转换成文字 ...

  8. flv格式视频怎么转换成mp4

    在我们使用视频文件的时候,不免有遇到flv格式的时候,这是一种具有版权保护的视频格式,其特点是体积小,加载的速度快,很适用于在网络上传输分享.但是flv格式的兼容性,却给很多人带来了无法播放视频文件的 ...

  9. 视频如何转换成mp3

    很多人想要自学新媒体运营!因为自媒体短视频的风口依然还在,趁着现在热度不减,学会一些视频处理技巧是很实用的本领.今天就给大家分享一个视频剪辑干货内容"视频如何转换成mp3". 视频 ...

最新文章

  1. ks检验正态分布结果_数据分析基础(2)——正态分布检验
  2. 世界上最浪费时间的三件事
  3. Python爬虫遍历文档树
  4. openstack运维实战系列(十七)之glance与ceph结合
  5. uva 12105——Bigger is Better
  6. MySQL普通索引与唯一索引__mysql中唯一索引和普通索引的用途及区别
  7. rabbit和mysql事务_分布式事务原理及SpringBoot整合RabbitMQ实现可靠事件,TCC事务模型及接口幂等性...
  8. 分布式架构中数据一致性常见的几个问题
  9. P2P 漏洞曝光,数以百万计的物联网设备被入侵!
  10. ServletContextListener 启动SPRING加载数据到缓存的应用
  11. java 配置文件参数_从Java的配置文件中读取配置参数的最佳方法是什么?
  12. PHP入门-运算符与操作符
  13. R: ggplot2图片的布局排版
  14. Win10官网原版安装
  15. 体检预约系统项目总结
  16. 头文件和库函数的区别
  17. html怎么控制行的字数,怎样控制Word文档一页的行数和每行的字数?
  18. 扫雷游戏软件测试,软件测试扫雷游戏.doc
  19. wpf 非托管代码崩溃_崩溃! 地狱的网络托管公司
  20. STM32入门——基于RobotMaster——1.准备工作

热门文章

  1. SDN商业化之路开启,云杉网络坚持像VMware一样只卖软件
  2. SpringBoot使用SpringSecurity,使用oauth2登录,使用自定义/uaa/oauth/token报错解决
  3. 了解表格与表单的基本知识
  4. Ubuntu Grub u盘 LiveCD修复方法
  5. 陪你看这世间---写给自己成熟的句子
  6. 视频中的水印如何去除?教你几种简单去除视频水印方法
  7. 关于浙政钉水印的问题
  8. Windows 管理电脑登录的账户
  9. python统计文字个数_如何使用python语言中的count方法统计字符个数
  10. Java学习笔记:案例:对英语真题进行词频分析