真没想到,Python还能实现5毛特效
来源 | ZackSock(ID:ZackSock)
图源 | 视觉中国
Python牛已经不是一天两天的事了,但是我开始也没想到,Python能这么牛。前段时间接触了一个批量抠图的模型库,而后在一些视频中找到灵感,觉得应该可以通过抠图的方式,给视频换一个不同的场景,于是就有了今天的文章。
我们先看看能实现什么效果,先来个正常版的,先看看原场景:
下面是我们切换场景后的样子:
看起来效果还是不错的,有了这个我们就可以随意切换场景,坟头蹦迪不是梦。另外,我们再来看看另外一种效果,相比之下要狂放许多:
实现步骤
我们都知道,视频是由一帧一帧的画面组成的,每一帧都是一张图片,我们要实现对视频的修改就需要对视频中每一帧画面进行修改。所以在最开始,我们需要获取视频每一帧画面。
在我们获取帧之后,需要抠取画面中的人物。
抠取人物之后,就需要读取我们的场景图片了,在上面的例子中背景都是静态的,所以我们只需要读取一次场景。在读取场景之后我们切换每一帧画面的场景,并写入新的视频。
这时候我们只是生成了一个视频,我们还需要添加音频。而音频就是我们的原视频中的音频,我们读取音频,并给新视频设置音频就好了。
具体步骤如下:
读取视频,获取每一帧画面
批量抠图
读取场景图片
对每一帧画面进行场景切换
写入视频
读取原视频的音频
给新视频设置音频
因为上面的步骤还是比较耗时的,所以在视频完成后通过邮箱发送通知,告诉我视频制作完成。
模块安装
我们需要使用到的模块主要有如下几个:
pillow
opencv
moviepy
paddlehub
我们都可以直接用pip安装:
pip install pillow
pip install opencv-python
pip install moviepy
其中OpenCV有一些适配问题,建议选取3.0以上版本。
在我们使用paddlehub之前,我们需要安装paddlepaddle:具体安装步骤可以参见官网。用paddlehub抠图参考:别再自己抠图了,Python用5行代码实现批量抠图。我们这里直接用pip安装cpu版本的:
# 安装paddlepaddle
python -m pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple
# 安装paddlehub
pip install -i https://mirror.baidu.com/pypi/simple paddlehub
有了这些准备工作就可以开始我们功能的实现了。
具体实现
我们导入如下包:
import cv2 # opencv
import mail # 自定义包,用于发邮件
import math
import numpy as np
from PIL import Image # pillow
import paddlehub as hub
from moviepy.editor import *
其中Pillow和opencv导入的名称不太一样,还有就是我自定义的mail模块。另外我们还要先准备一些路径:
# 当前项目根目录,系统自动获取当前目录
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "."))
# 每一帧画面保存的地址
frame_path = BASE_DIR + '\\frames\\'
# 抠好的图片位置
humanseg_path = BASE_DIR + '\\humanseg_output\\'
# 最终视频的保存路径
output_video = BASE_DIR + '\\result.mp4'
接下来我们按照上面说的步骤一个一个实现。
(1)读取视频,获取每一帧画面
在OpenCV中提供了读取帧的函数,我们只需要使用VideoCapture类读取视频,然后调用read函数读取帧,read方法返回两个参数,ret为是否有下一帧,frame为当前帧的ndarray对象。完整代码如下:
def getFrame(video_name, save_path):"""读取视频将视频逐帧保存为图片,并返回视频的分辨率size和帧率fps:param video_name: 视频的名称:param save_path: 保存的路径:return: fps帧率,size分辨率"""# 读取视频video = cv2.VideoCapture(video_name)# 获取视频帧率fps = video.get(cv2.CAP_PROP_FPS)# 获取画面大小width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))size = (width, height)# ????获取帧数,用于给图片命名frame_num = str(video.get(7))name = int(math.pow(10, len(frame_num)))# 读取帧,ret为是否还有下一帧,frame为当前帧的ndarray对象ret, frame = video.read()while ret:cv2.imwrite(save_path + str(name) + '.jpg', frame)ret, frame = video.read()name += 1video.release()return fps, size
在标????处,我获取了帧的总数,然后通过如下公式获取比帧数大的整十整百的数:
frame_name = math.pow(10, len(frame_num))
这样做是为了让画面逐帧排序,这样读取的时候就不会乱。另外我们获取了视频的帧率和分辨率,这两个参数在我们创建视频时需要用到。这里需要注意的是opencv3.0以下版本获取帧率和画面大小的写法有些许差别。
(2)批量抠图
批量抠图需要用到paddlehub中的模型库,代码很简单,这里就不多说了:
def getHumanseg(frames):"""对帧图片进行批量抠图:param frames: 帧的路径:return:"""# 加载模型库humanseg = hub.Module(name='deeplabv3p_xception65_humanseg')# 准备文件列表files = [frames + i for i in os.listdir(frames)]# 抠图humanseg.segmentation(data={'image': files})
我们执行上面函数后会在项目下生成一个humanseg_output目录,抠好的图片就在里面。
(3)读取场景图片
这也是简单的图片读取,我们使用pillow中的Image对象:
def readBg(bgname, size):"""读取背景图片,并修改尺寸:param bgname: 背景图片名称:param size: 视频分辨率:return: Image对象"""im = Image.open(bgname)return im.resize(size)
这里的返回的对象并非ndarray对象,而是Pillow中定义的类对象。
(4)对每一帧画面进行场景切换
简单来说就是将抠好的图片和背景图片合并,我们知道抠好的图片都在humanseg_output目录,这也就是为什么最开始要准备相应的变量存储该目录的原因:
def setImageBg(humanseg, bg_im):"""将抠好的图和背景图片合并:param humanseg: 抠好的图:param bg_im: 背景图片,这里和readBg()函数返回的类型一样:return: 合成图的ndarray对象"""# 读取透明图片im = Image.open(humanseg)# 分离色道r, g, b, a = im.split()# ????复制背景,以免源背景被修改bg_im = bg_im.copy()# 合并图片bg_im.paste(im, (0, 0), mask=a)return np.array(bg_im.convert('RGB'))[:, :, ::-1]
在标????处,我们复制了背景,如果少了这一步的话,生成的就是我们上面的“千手观音效果”了。
其它步骤都很好理解,只有返回值比较长,我们来详细看一下:
# 将合成图转换成RGB,这样A通道就没了
bg_im = bg_im.convert('RGB')
# 将Image对象转换成ndarray对象,方便opencv读取
im_array = np.array(bg_im)
# 此时im_array为rgb模式,而OpenCV为bgr模式,我们通过下面语句将rgb转换成bgr
bgr_im_array = im_array[:, :, ::-1]
最后bgr_im_array就是我们最终的返回结果。
(5)写入视频
为了节约空间,我并非等将写入图片放在合并场景后面,而是边合并场景边写入视频:
def writeVideo(humanseg, bg_im, fps, size):""":param humanseg: png图片的路径:param bgname: 背景图片:param fps: 帧率:param size: 分辨率:return:"""# 写入视频fourcc = cv2.VideoWriter_fourcc(*'mp4v')out = cv2.VideoWriter('green.mp4', fourcc, fps, size)# 将每一帧设置背景files = [humanseg + i for i in os.listdir(humanseg)]for file in files:# 循环合并图片im_array = setImageBg(file, bg_im)# 逐帧写入视频out.write(im_array)out.release()
上面的代码也非常简单,执行完成后项目下会生成一个green.mp4,这是一个没有音频的视频,后面就需要我们获取音频然后混流了。
(6)读取原视频的音频
因为在opencv中没找到音频相关的处理,所以选用moviepy,使用起来也非常方便:
def getMusic(video_name):"""获取指定视频的音频:param video_name: 视频名称:return: 音频对象"""# 读取视频文件video = VideoFileClip(video_name)# 返回音频return video.audio
然后就是混流了。
(7)给新视频设置音频
这里同样使用moviepy,传入视频名称和音频对象进行混流:
def addMusic(video_name, audio):"""实现混流,给video_name添加音频"""# 读取视频video = VideoFileClip(video_name)# 设置视频的音频video = video.set_audio(audio)# 保存新的视频文件video.write_videofile(output_video)
其中output_video是我们在最开始定义的变量。
(8)删除过渡文件
在我们生产视频时,会产生许多过渡文件,在视频合成后我们将它们删除:
def deleteTransitionalFiles():"""删除过渡文件"""frames = [frame_path + i for i in os.listdir(frame_path)]humansegs = [humanseg_path + i for i in os.listdir(humanseg_path)]for frame in frames:os.remove(frame)for humanseg in humansegs:os.remove(humanseg)
最后就是将整个流程整合一下。
(8)整合
我们将上面完整的流程合并成一个函数:
def changeVideoScene(video_name, bgname):""":param video_name: 视频的文件:param bgname: 背景图片:return:"""# 读取视频中每一帧画面fps, size = getFrame(video_name, frame_path)# 批量抠图getHumanseg(frame_path)# 读取背景图片bg_im = readBg(bgname, size)# 将画面一帧帧写入视频writeVideo(humanseg_path, bg_im, fps, size)# 混流addMusic('green.mp4', getMusic(video_name))# 删除过渡文件deleteTransitionalFiles()
(9)在main中调用
我们可以把前面定义的路径也放进了:
if __name__ == '__main__':# 当前项目根目录BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "."))# 每一帧画面保存的地址frame_path = BASE_DIR + '\\frames\\'# 抠好的图片位置humanseg_path = BASE_DIR + '\\humanseg_output\\'# 最终视频的保存路径output_video = BASE_DIR + '\\result.mp4'if not os.path.exists(frame_path):os.makedirs(frame_path)try:# 调用函数制作视频changeVideoScene('jljt.mp4', 'bg.jpg')# 当制作完成发送邮箱mail.sendMail('你的视频已经制作完成')except Exception as e:# 当发生错误,发送错误信息mail.sendMail('在制作过程中遇到了问题' + e.__str__())
这样我们就完成了完整的流程。
发送邮件
邮件的发送又是属于另外的内容了,我定义了一个mail.py文件,具体代码如下:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart # 一封邮件def sendMail(msg): # sender = '发件人'to_list = ['收件人']subject = '视频制作情况'# 创建邮箱em = MIMEMultipart()em['subject'] = subjectem['From'] = senderem['To'] = ",".join(to_list)# 邮件的内容content = MIMEText(msg)em.attach(content)# 发送邮件# 1、连接服务器smtp = smtplib.SMTP()smtp.connect('smtp.163.com')# 2、登录smtp.login(sender, '你的密码或者授权码')# 3、发邮件smtp.send_message(em)# 4、关闭连接smtp.close()
里面的邮箱我是直接写死了,大家可以自由发挥。为了方便,推荐发件人使用163邮箱,收件人使用QQ邮箱。另外在登录的时候直接使用密码比较方便,但是有安全隐患。
总结
老实说上述程序的效率非常低,不仅占空间,而且耗时也比较长。在最开始我切换场景选择的是遍历图片每一个像素,而后找到了更加高效的方式取代了。但是帧画面的保存,和png图片的存储都很耗费空间。
另外程序设计还是有许多不合理的地方,像是ndarray对象和Image的区分度不高,另外有些函数选择传入路径,而有些函数选择传入文件对象也很容易让人糊涂。
最后说一下,我们用上面的方式不仅可以做静态的场景切换,还可以做动态的场景切换,这样我们就可以制作更加丰富的视频。当然,效率依旧是个问题。
完整代码已提交GitHub:
https://github.com/IronSpiderMan/VideoSpecialEffects
推荐阅读
AI修复100年前晚清影像喜提热搜,这两大算法立功了
CycleGan人脸转为漫画脸,牛掰的知识又增加了 | 附代码
作词家下岗系列:教你用 AI 做一个写歌词的软件
用大白话彻底搞懂 HBase RowKey 详细设计
为什么黑客无法攻击公开的区块链?
百万人学AI 万人在线大会, 15+场直播抢先看!
自动化神经网络理论进展缓慢,AutoML 算法的边界到底在哪?
你点的每个“在看”,我都认真当成了AI
相关文章:

第八章 VLSM
VSLM(variable length subnet mask)------------可变长长度子网掩码 对于点对点链路而言,最好的子网掩码是:255.255.255.252对于lan而言,好的子网掩码可能是255.255.255.192。vlsm的两个好处:在大型网络中高效地使用寻址ÿ…

Androidstudio下Generate signed apk提示Error: Expected resource of type id [ResourceType]解决办法...
只需要在报错位置所在的类上面添加: SuppressWarnings("ResourceType") 即可实现Generate signed apk。

对话框窗口最大最小化
mfc里,基于对话框的窗口,具有最大最小化的属性设置。在Border属性里选择Resizing,然后在Maximize和Minimize中选择true。在窗体当中随便拖几个控件,然后运行,此时点击最大化会发现,整个窗体的大小是变大了&…
4场直播,哈工大、亚马逊等大咖为你带来机器学习与知识图谱的内容盛宴
机器学习和知识图谱是当今技术领域的热门话题,随着相关技术的不断发展,无论是对两类技术单独的探讨,还是将机器学习和知识图谱相结合的尝试,都在吸引越来越多的关注。5月16日下午,来自亚马逊、墨奇科技、Second State、…

【失败的尝试】C++中使用string进行switch判断
贴出错误代码: #include <iostream>#include <string>using namespace std;void main(){ string str; cin>>str; switch(str) { case "ab": cout<<"one"<<endl; break; case &…

springmvc 拦截器、国际化、验证
2019独角兽企业重金招聘Python工程师标准>>> springmvc 拦截器 继承了HandlerIntercepter的类可以作为拦截器类: package com.yawn.intercepter;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import o…

由MessageBox和AfxMessageBox的使用异同所感
我记得刚开始学图形界面编程的时候,接触的最早的一个函数应该就是MessageBox,之前都一直是控制台程序,突然能运行蹦出一个对话框感觉还是很新鲜的。当时还利用MessageBox写一些恶搞程序,利用上面的yes or no 按钮进行判断等等。但是说实话感觉…
iRobot的30年成长史
作者 | Colin Angle译者 | 苏本如,编辑 | 郭芮题图视觉中国出品 | AI科技大本营(ID:rgznai100)建造一个漫游者,把它送上月球,出售电影版权。这是我们在1990年开始iRobot时的第一个商业模式,我们…

iPhone开发:通过NSURLRequest获得服务器返回的http header和http status
HTTP连接的头信息包括在NSHTPURLResponse类中。如果你拥有一个NSHTTPURLResponse变量,你可以通过发送allHeaderFields信息,轻而易举地获取以NSDictionary形式保存的头信息。对于一个同步请求 – 由于会引发阻塞所以不推荐使用 – 是很容易初始化一个NSHT…

今天开始记录自己苹果开发博客旅程!~
做ios开发也蛮久了,现在才想到要自己开个博客,然后记录点自己平时工作学习中遇到的各种问题以及解决后的心得。现在公司的app第一个版本已经上线了,更加期待以后的发展和更迭。还记得刚进公司接受项目时那种忐忑不安的心理,现在想…

一步一步实现扫雷游戏(C语言实现)(三)
使用WIN32API连接窗口 此项目相关博文链接 一步一步实现扫雷游戏(C语言实现)(一) 一步一步实现扫雷游戏(C语言实现)(二) 一步一步实现扫雷游戏(C语言实现)(三) 一步一步实现扫雷游戏(…

关于模态对话框和非模态对话框的创建、显示,以及和父对话框的传值
当然网上关于这方面的技术博文非常多,此处我只是进行一下小记,再加一点自己的体会,方便以后查询。 一、模态对话框 1.创建及显示 模态对话框是一种阻塞式的对话框,即没有处理完该对话框,不能对其他地方进行操作。比…

《评人工智能如何走向新阶段》后记(再续25)
415,开发近红外光激发的纳米探针,监测大脑深层活动,理解神经系统功能机制。 开发、设计电压敏感纳米探针一直是个技术难关。 群体神经元活动的在体监测是揭示神经系统功能机制的关键。 近日《美国化学会志》期刊报导一项新的研究成果&…

sftp 限制用户登陆指定目录(家目录)
sftp 限制用户登陆指定目录(家目录)本文源地址http://blog.chinaunix.net/uid-42741-id-3069880.html即限制 sftp 用户登陆后,只能在家目录下活动,不能到其他或上级目录该功能需要4.8以上版本[rootbackup ~]# ssh -VOpenSSH_5.3p1, OpenSSL 1.0.1e-fips …

C#多线程学习
任何程序在执行时,至少有一个主线程。在.net framework class library中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。如果你想在你的应用程序中使用多线程,就必须包含这个类。 Thread类有几个至关重要的方法&#x…

开启一个新的终端并执行特定的命令
我的项目中有利用到远程控制,从windows端远程控制linux端,那么也就是接收远程的命令并在本机执行并返回结果。在父进程中用到popen()函数,popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命…

《评人工智能如何走向新阶段》后记(再续26)
427,SNN机理性测试 SNN利用时空处理,脉冲稀疏性和较高的内部神经元带宽来最大化神经形态计算的能量效率。尽管可以在这种情况下使用常规的基于硅的技术,但最终的神经元突触电路需要多个晶体管和复杂的布局,从而限制了集成密度。论…
Android5.1.1源码 - zygote fork出的子进程如何权限降级
前言 如果不知道zygote是什么,或者好奇zygote如何启动,可以去看老罗的文章: Android系统进程Zygote启动过程的源代码分析所有Android应用进程都是zygote fork出来的,新fork出来的应用进程还保持着root权限,这显然是不被…

system函数
转载自此处 相关函数 fork,execve,waitpid,popen 头文件#includ”stdlib.h” 定义函数 int system(const char * string); 函数说明 system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串…

《评人工智能如何走向新阶段》后记(再续27)
439,彩虹一号无人机实现人类永不落地的追求 日媒:中国亮出杀手锏 世界各国一直在研究提高飞机的续航能力 国内研制的彩虹一号无人机采用人工智能和其他高新技术,飞行高度30000米,并终于研制成功实现人类永不落地的追求。 440&a…

使用unix工具监控cpu、内存等系统资源占用率
1)使用 sar -u 命令监控cpu使用$ sar -u 5 512:21:15 %usr %sys %wio %idle12:21:20 54 15 13 1912:21:25 41 18 15 2712:21:30 62 20 10 912:21:35 33 11 20 3612:21:40 38 13 17 31Average 45 15 15 24%usr--运行在用户模式下cpu的使用百分…

C# 获取图片的EXIF 信息
关于 EXIF 信息的介绍。 1 EXIF,是英文Exchangeable Image File(可交换图像文件)的缩写。EXIF是一种图像文件格式,只是文件的后缀名为jpg。EXIF信息是由数码相机在拍摄过程中采集一系列的信息,然后把信息放置在jpg文件的头部,也就…
ffmpeg录屏/摄像头/指定窗口;别名alias设置
关于ffmpeg的使用方法很多,我简单写一下今天我捣鼓的几个。因为我的项目中要用到录屏和录制摄像头,所以试了下。网上关于录制指定窗口的方法并不多,我也是找了好久,试了好久才试出来的。 好了,废话不多说,…

黄聪:BackGroundWorker解决“线程间操作无效: 从不是创建控件的线程访问它” (C# VS2008)...
在编程中经常会遇到在一个按钮中执行复杂操作,并将复杂操作最后返回的值加入一个ListView或ComboBox中候选。这个时候程序会卡,当程序员将这些卡代码放进线程(Thread)中后发现当对控件操作时出现“线程间操作无效: 从不是创建控件的线程访问它”异常。 …

AWS 中国宁夏和北京区正式上线 Amazon SageMaker,中国用户终于能用到新工具和功能!
2020年 5 月 12 日,亚马逊云服务 Amazon Web Services, Inc. (AWS) 宣布,Amazon SageMaker 在由西云数据运营的 AWS 中国 (宁夏) 区域和光环新网运营的 AWS 中国(北京)区域正式上线。 Amazon SageMaker 在中国的上线使中国用户获…

Ubuntu Vim YouCompleteMe 安装
0. 必要工具安装 sudo apt-get install build-essential cmake 1. 安装 vundle mkdir ~/.vim/bundle git clone https://github.com/gmarik/vundle.git ~/.vim/bundle/vundle 2.编辑 .vimrc set nocompatible " be iMproved, required filetype off …

ubuntu vsftpd虚拟用户配置/ubuntu12.04上搭建vsftpd服务示例linux
转自这里 在ubuntu中安装完vsftpd后,安装libdb4.6-util: 复制代码 代码示例: sudo apt-get install db4.6-util 在etc下面建立目录vsftpd, 创建一个txt文档,比如logins.txt,在其中输入用户名及密码,如: 复制代码 代码示例: test 12345…

云从完成超过18亿元新一轮融资,加快上市步伐
近日,云从科技完成新一轮融资,总规模超过18亿元人民币,投资方除了中国互联网投资基金、上海国盛、广州南沙金控、长三角产业创新基金等政府基金外,还包括工商银行、海尔金控等产业战略投资者,进一步强化“AI国家队”的…

input core input.c (1)
drivers/input/input.c 就是所谓的input的核心程序。 分析这个文件,先从input_init开始。 1: static int __init input_init(void) 2: { 3: err class_register(&input_class); 4: err input_proc_init(); 5: err register_chrdev(INPUT_MAJOR, "i…

Swift解读专题四——字符串与字符
2019独角兽企业重金招聘Python工程师标准>>> Swift解读专题四——字符串与字符 一、引言 Swift中提供了String类型与Characters类型来处理字符串和字符数据,Swift中的String类型除了提供了许多方便开发者使用的方法外,还可以与Foundation框架…