来源 | ZackSock(ID:ZackSock)

图源 | 视觉中国

Python牛已经不是一天两天的事了,但是我开始也没想到,Python能这么牛。前段时间接触了一个批量抠图的模型库,而后在一些视频中找到灵感,觉得应该可以通过抠图的方式,给视频换一个不同的场景,于是就有了今天的文章。

我们先看看能实现什么效果,先来个正常版的,先看看原场景:

点击播放 GIF 0.0M

下面是我们切换场景后的样子:

点击播放 GIF 0.0M

看起来效果还是不错的,有了这个我们就可以随意切换场景,坟头蹦迪不是梦。另外,我们再来看看另外一种效果,相比之下要狂放许多:

点击播放 GIF 0.0M

实现步骤

我们都知道,视频是由一帧一帧的画面组成的,每一帧都是一张图片,我们要实现对视频的修改就需要对视频中每一帧画面进行修改。所以在最开始,我们需要获取视频每一帧画面。

在我们获取帧之后,需要抠取画面中的人物。

抠取人物之后,就需要读取我们的场景图片了,在上面的例子中背景都是静态的,所以我们只需要读取一次场景。在读取场景之后我们切换每一帧画面的场景,并写入新的视频。

这时候我们只是生成了一个视频,我们还需要添加音频。而音频就是我们的原视频中的音频,我们读取音频,并给新视频设置音频就好了。

具体步骤如下:

读取视频,获取每一帧画面

批量抠图

读取场景图片

对每一帧画面进行场景切换

写入视频

读取原视频的音频

给新视频设置音频

因为上面的步骤还是比较耗时的,所以在视频完成后通过邮箱发送通知,告诉我视频制作完成。

模块安装

我们需要使用到的模块主要有如下几个:

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

有了这些准备工作就可以开始我们功能的实现了。

具体实现

我们导入如下包:

importcv2# opencv

importmail# 自定义包,用于发邮件

importmath

importnumpyasnp

fromPILimportImage# pillow

importpaddlehubashub

frommoviepy.editorimport*

其中Pillow和opencv导入的名称不太一样,还有就是我自定义的mail模块。另外我们还要先准备一些路径:

接下来我们按照上面说的步骤一个一个实现。

(1)读取视频,获取每一帧画面

在OpenCV中提供了读取帧的函数,我们只需要使用VideoCapture类读取视频,然后调用read函数读取帧,read方法返回两个参数,ret为是否有下一帧,frame为当前帧的ndarray对象。完整代码如下:

defgetFrame(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()

whileret:

cv2.imwrite(save_path + str(name) +'.jpg', frame)

ret, frame = video.read()

name +=1

video.release()

returnfps, size

在标处,我获取了帧的总数,然后通过如下公式获取比帧数大的整十整百的数:

frame_name = math.pow(10, len(frame_num))

这样做是为了让画面逐帧排序,这样读取的时候就不会乱。另外我们获取了视频的帧率和分辨率,这两个参数在我们创建视频时需要用到。这里需要注意的是opencv3.0以下版本获取帧率和画面大小的写法有些许差别。

(2)批量抠图

批量抠图需要用到paddlehub中的模型库,代码很简单,这里就不多说了:

defgetHumanseg(frames):

"""

对帧图片进行批量抠图

:param frames: 帧的路径

:return:

"""

# 加载模型库

humanseg = hub.Module(name='deeplabv3p_xception65_humanseg')

# 准备文件列表

files = [frames + iforiinos.listdir(frames)]

# 抠图

humanseg.segmentation(data={'image': files})

我们执行上面函数后会在项目下生成一个humanseg_output目录,抠好的图片就在里面。

(3)读取场景图片

这也是简单的图片读取,我们使用pillow中的Image对象:

defreadBg(bgname, size):

"""

读取背景图片,并修改尺寸

:param bgname: 背景图片名称

:param size: 视频分辨率

:return: Image对象

"""

im = Image.open(bgname)

returnim.resize(size)

这里的返回的对象并非ndarray对象,而是Pillow中定义的类对象。

(4)对每一帧画面进行场景切换

简单来说就是将抠好的图片和背景图片合并,我们知道抠好的图片都在humanseg_output目录,这也就是为什么最开始要准备相应的变量存储该目录的原因:

defsetImageBg(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, (,), mask=a)

returnnp.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)写入视频

为了节约空间,我并非等将写入图片放在合并场景后面,而是边合并场景边写入视频:

defwriteVideo(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 + iforiinos.listdir(humanseg)]

forfileinfiles:

# 循环合并图片

im_array = setImageBg(file, bg_im)

# 逐帧写入视频

out.write(im_array)

out.release()

上面的代码也非常简单,执行完成后项目下会生成一个green.mp4,这是一个没有音频的视频,后面就需要我们获取音频然后混流了。

(6)读取原视频的音频

因为在opencv中没找到音频相关的处理,所以选用moviepy,使用起来也非常方便:

defgetMusic(video_name):

"""

获取指定视频的音频

:param video_name: 视频名称

:return: 音频对象

"""

# 读取视频文件

video = VideoFileClip(video_name)

# 返回音频

returnvideo.audio

然后就是混流了。

(7)给新视频设置音频

这里同样使用moviepy,传入视频名称和音频对象进行混流:

defaddMusic(video_name, audio):

"""实现混流,给video_name添加音频"""

# 读取视频

video = VideoFileClip(video_name)

# 设置视频的音频

video = video.set_audio(audio)

# 保存新的视频文件

video.write_videofile(output_video)

其中output_video是我们在最开始定义的变量。

(8)删除过渡文件

在我们生产视频时,会产生许多过渡文件,在视频合成后我们将它们删除:

defdeleteTransitionalFiles():

"""删除过渡文件"""

frames = [frame_path + iforiinos.listdir(frame_path)]

humansegs = [humanseg_path + iforiinos.listdir(humanseg_path)]

forframeinframes:

os.remove(frame)

forhumanseginhumansegs:

os.remove(humanseg)

最后就是将整个流程整合一下。

(8)整合

我们将上面完整的流程合并成一个函数:

defchangeVideoScene(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'

ifnotos.path.exists(frame_path):

os.makedirs(frame_path)

try:

# 调用函数制作视频

changeVideoScene('jljt.mp4','bg.jpg')

# 当制作完成发送邮箱

mail.sendMail('你的视频已经制作完成')

exceptExceptionase:

# 当发生错误,发送错误信息

mail.sendMail('在制作过程中遇到了问题'+ e.__str__())

这样我们就完成了完整的流程。

发送邮件

邮件的发送又是属于另外的内容了,我定义了一个mail.py文件,具体代码如下:

importsmtplib

fromemail.mime.textimportMIMEText

fromemail.mime.multipartimportMIMEMultipart# 一封邮件

defsendMail(msg):

#

sender ='发件人'

to_list = [

'收件人'

]

subject ='视频制作情况'

# 创建邮箱

em = MIMEMultipart()

em['subject'] = subject

em['From'] = sender

em['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

python可以做特效吗_真没想到,Python还能实现5毛特效相关推荐

  1. python合并视频和音频_真没想到,Python 还能实现 5 毛特效

    作者 | ZackSock 来源 | ZackSock(ID:ZackSock) Python牛已经不是一天两天的事了,但是我开始也没想到,Python能这么牛.前段时间接触了一个批量抠图的模型库,而 ...

  2. python能做些什么事_一起来看看Python能干什么?使用Python能做哪些事

    原标题:Python能干什么?使用Python能做哪些事情? 先来看看Python的定义: Python是一种动态的.面向对象的脚本语言,尤其近几年Python在TIOBE上的排名直线上升,那么Pyt ...

  3. python做动态课件素材_万万没想到,还能这么玩!用 Python 生成动态 PPT

    在工作的过程中,我们会发现那些能够把知识.成果讲透的人很多都会做动态图表. 这篇文章就介绍了 Python 中一种简单的动态图表制作方法,这样生成的动图就可以丰富我们的PPT啦~ 数据暴增的年代,数据 ...

  4. python能做机器人吗_最火的Python语言也能做机器人仿真,你会不?

    原标题:最火的Python语言也能做机器人仿真,你会不? 最近接到好多刚踏出大学的学弟学妹们的问候,面临人生中的第一次求职,可有感觉自己没在学校学到一技之长,但又不想随便找份工作将就,那这种情况下该怎 ...

  5. ImageMagick将多张图片拼接成一张图片_真没想到!照片加文字和照片拼接,微信就能做到,太方便了...

    照片加文字以及照片拼接,用微信就能做到,太方便了 相信大家在端午佳节的时候,都拍摄了很多照片,分享到朋友圈,但是拍摄的图片太多,微信朋友圈只能发9张. 今天就来教大家如何用微信就照片添加文字和拼接长图 ...

  6. python能做回归吗_有大神会看Python多元回归的结果吗?本人做出来了,但是不会看.....

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 码的代码: import statsmodels.formula.api as sm from pandas import read_excel df = ...

  7. char转成string_真没想到,一个小小的String居然还有这么多窍门?

    推荐学习 公司来了位阿里P8大神,看完他的手写"Kafka笔记",万分膜拜 牛掰!"基础-中级-高级"Java程序员面试集结,看完献出我的膝盖 真没想到,一个小 ...

  8. 真没想到,疫情让我实现了远程办公的夙愿

    昨天晚上,公号后台收到了一位读者小武的多条留言(内容大致如下),竟然让我心有戚戚焉. 二哥,真没想到,疫情让我实现了多年来的一个夙愿:远程办公.但说实话,心里挺不是滋味的. 放假前,我想着终于可以解放 ...

  9. 真没想到刚刚会把这个记录下来。嘿嘿。

    真没想到刚刚会把这个记录下来.嘿嘿. 等我们死去以后,会有人把这些收集整理起来,形成语录体著作吗? 哈哈!?. 脸太大了. http://www.uuzone.com/app/trackBack.do ...

最新文章

  1. RecycleView 动画实现
  2. Python-可视化Evoked数据
  3. iBatis学习网站推荐
  4. 笔记本网络计算机和设备不可见,WIN10局域网电脑和设备显示不完整
  5. Java开发的可扩展-高性能-响应式的API网关-soul v2.2.1
  6. cpuset(7) — Linux manual page
  7. java图片上传并解析,详解SpringMVC实现图片上传以及该注意的小细节
  8. k8s部署jar包_K8S部署SpringBoot应用_都超的博客-CSDN博客_k8s springboot
  9. Java异常处理机制(基础知识)
  10. 爬楼梯算法-java(递归与非递归)
  11. jdk官网下载账号登陆
  12. 我眼中的《APUE》
  13. 借助C++类结构计算矩形面积(矩形类)
  14. java英语单词查询,输入一个单词根据字典查询单词意思
  15. sofelf转jic
  16. 2021年中国工业互联网安全大赛
  17. line 1: syntax error: unexpected (
  18. 发布了每日一文应用,每日一文,每天阅读,简单生活。每天精选一篇不同的文章,也可以随机阅读往日精选文章...
  19. [CPNet]-理想亲和图的生成以及作用——Blank
  20. Java|Java中int的取值范围是多少

热门文章

  1. HTML5——3D立方体旋转动画
  2. Python工具之一:九宫格图片极致裁剪
  3. 最简单的免费修改PDF文件方法
  4. 冥想增加了大脑振荡活动的熵
  5. 8.Linux实操指令(搜索查找指令)
  6. Linux系统中的权限管理
  7. 拼团 html 模版,拼团设置.html
  8. Gateway的RemoteAddr与RemoteAddressResolver源码分析
  9. 智能电销机器人解决了传统电销行业哪些问题?
  10. notepad 的php,notepad是什么