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

0653dbfaa594e4efa02515cbff49cc59.png

作者 | ZackSock

来源 | ZackSock(ID:ZackSock)

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

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

655d7d86eab04d69d86f1cd0a8d15c0e.gif

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

de8a78d48f1358861a6598a8034b1294.gif

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

ce18d4a37b1035cc898474dd2a87e5d5.gif17543124f761ab13d4a57eed47bb4b73.png

实现步骤

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

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

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

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

具体步骤如下:

  1. 读取视频,获取每一帧画面
  2. 批量抠图
  3. 读取场景图片
  4. 对每一帧画面进行场景切换
  5. 写入视频
  6. 读取原视频的音频
  7. 给新视频设置音频

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

e042dab42ea8aaf73b15fa5b04f3be58.png

模块安装

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

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

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

209e54b5c12b063b608c8f0cee7ccb79.png

具体实现

我们导入如下包:

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 + 'esult.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 += 1
video.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 + 'esult.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__)

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

de71b2f1ef8a693e596ae5051f315a86.png

发送邮件

邮件的发送又是属于另外的内容了,我定义了一个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'] = subject
em['From'] = sender
em['To'] =

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/504283.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【前端知识学习】HTML5 学习笔记

文章目录一. 简介与基本信息1. W3C 标准2. HTML基本结构3. 网页基本信息4. 网页基本标签5. 媒体元素二. 网页结构与框架1. 页面结构2. iframe 内联框架3. 表单这是狂神的HTML教学的笔记。从今天开始转行前端 主要是为了把简历写得更好看 ,因此部分地方会比较省略&am…

高大上的集团名字_那些刚改了“高大上”名字的学校,你知道都有哪些吗?蜻蜓AI小编来帮你科普一下...

升学心里没底,蜻蜓探长帮你!家长和考生想必在报考之前都会对院校进行一定的了解,所谓的了解,不过是在官网上查一查学校的院校最低分数和专业最低分数。最容易看到的往往是这个院校最表面的东西,然而我们对院校的了解只…

【LeetCode笔记】剑指Offer 19. 正则表达式匹配(Java、动态规划)

文章目录题目描述思路 && 代码二刷打卡第十二天~ 题目描述 拖了超级久的一道题 ,懒得看正则表达式,但是其实和正则表达式相关的地方也不多 思路 && 代码 参照这篇题解写的,dalao属实万物皆可动态规划。主要是…

怎么制作游戏脚本_精彩的游戏视频混剪怎么做?录屏剪辑一站式制作

英雄联盟LOL这么多年深受广大玩家喜爱,而且各大平台上更不乏游戏精彩视频集锦,更有一些视频创作者通过小视频一鸣惊人。那么,这些精彩的游戏视频是怎么制作出来的呢?你离他们只是缺了这个工具而已。今天,小编给大家推荐…

【LeetCode笔记】剑指Offer 37. 序列化二叉树(Java、二叉树、序列化、BFS、队列)

文章目录题目描述思路 && 代码二刷题目描述 这道题涉及到不少 String、StringBuilder、Integer的转换、处理。 思路 && 代码 序列化:迭代进行一个层序遍历,逐个加入结果字符串中。反序列化:根据序列化得到的结果字符串&a…

【LeetCode笔记】72. 编辑距离(Java、字符串、动态规划)

文章目录题目描述思路 && 代码 O(n2n^2n2)、O(n2n^2n2)二刷打卡第十三天~ 题目描述 感觉和正则表达式匹配这道题很像:同样的两个字符串,同样的二维数组dp,同样的hard。。 思路 && 代码 O(n2n^2n2)、O(n2n^2n2…

超级外链工具_哪些SEO排名工具是有效的呢?

SEO的发展到现在已经有十多年的时间,出现的大大小小的SEO工具也有上百种,这些SEO工具的背景和作用是不同的。大致可以分为两类:一类是SEO建议工具,帮助诊断网站给出优化分析建议;另一类是可以直接帮助网站排名的工具&a…

【LeetCode笔记】416. 分割等和子集(Java、动态规划、背包问题、滚动数组)

文章目录题目描述思路 && 代码1. 动态规划 O(nc) 、O(nc)2. 结合滚动数组 O(nc)、O(c)二刷打卡第十四天~熬夜也得把题目补上 题目描述 初看题目,想到的思路是用记忆化DFS来找结果来着。。看了题解才知道是背包问题 思路 && 代码 1…

【LeetCode笔记】494. 目标和(Java、动态规划、背包问题、滚动数组)

文章目录题目描述思路 && 代码1. 动态规划 O(n2n^2n2)、O(n2n^2n2)(最方便理解,初版)2. 转换成 01 背包问题 O(n2n^2n2)、O(nnn)二刷离谱!添加了测试用例,上面的代码需要添加负数条件了(见下面的代…

2007年上半年软件测试_洪恩教育成功登陆纽交所,上市首日大涨超33%!成2020年首家美股上市的中国教育公司...

洪恩教育(IH)在美国纽交所上市,发行价为12美元,位于发行区间11到13美元的中间位置。洪恩教育此次发行700万股ADS,募资资金总额为8400万美元(假设超额配售权未获行使)。洪恩教育此次绿鞋前估值为…

高德地图画带箭头的线_精选 | 这款充电线逆天了!一条顶三条,让其它线都“下岗”吧...

随着电子设备越来越多越来越多越来越乱的充电线成了我们每天面临的烦恼各种线材多起来真心闹心缠绕打结,一团乱麻外出携带更是不方便。。。而人生更悲惨的事莫过于3人开黑时手机突然没电且只有一个电源一款可彻底改变这些烦恼的充电神器——倍思可伸缩一拖三数据线。…

【LeetCode笔记】581. 最短无序连续子数组(Java、数组)

文章目录题目描述思路 && 代码1. 排序法 O(nlogn)、O(n)2. 记录 max[ ]、min[ ] 的写法 O(n)、O(n)3. 记录 max、min 的写法 O(n)、O(1)二刷打卡第十五天~ 前两天有事断签了,要继续加油噢! 题目描述 主要是,需…

华为usg6000配置手册_带你了解防火墙安全区域的作用及简单的配置,小白不要错过了...

上一篇文章《防火墙入门基础之登录Web配置界面》已经简单的介绍了关于华为防火墙的如何配置Web登录,也开始接触了关于防火墙安全区域的基本概念。其实防火墙安全区域是一个非常重要的概念,简称为区域(Zone)。安全区域是一个或多个接口的集合,…

【LeetCode笔记】621. 任务调度器(Java、桶)

文章目录题目描述代码 && 思路1. 直白的 ac 做法 O(n)、O(n)2. 桶排思想的做法 O(n)、O(n)二刷打卡第十六天~ 题目描述 有点阅读题的意思,可以结合例子理解一下题干~ 代码 && 思路 1. 直白的 ac 做法 O(n)、O(n) 同样是…

【LeetCode笔记】162. 寻找峰值(Java、二分、偏数学)

文章目录题目描述思路 && 代码1. 暴力法 O(n)2. 二分法 O(logN)二刷打卡第十七天~ 题目描述 难点在于 logN 复杂度 思路 && 代码 1. 暴力法 O(n) 最简单的做法,直接遍历判断即可。 class Solution {public int findPeakElement(in…

【知识补充】对称加密、非对称加密、数字签名与DDoS攻击

文章目录一. 对称加密1)知识点2)具体算法1. DES2. 3DES3. AES二. 非对称加密(公钥密码)1)知识点2)具体算法RSA三. 数字签名1)知识点四. DDoS 攻击1)直接 DDos 攻击2)反射…

【LeetCode笔记】76. 最小覆盖子串(字符串、滑动窗口)

文章目录题目描述思路 && 代码二刷最近一直在充电基础知识、维护 leetcode 总结博客 今天继续摸一摸新题目吧~ 题目描述 感觉和 03. 无重复的最长子串 有点像,都是在字符串上用滑动窗口来找子串。并且都是属于那种,解决一次以后&…

【LeetCode笔记】84. 柱状图中最大的矩形(字符串、单调栈)

文章目录题目描述思路 && 代码二刷题目描述 和接雨水有点像,但是具体做法还是有点不同。 思路 && 代码 暴力法只能过 94 / 96 的样例:复杂度O(n^2) & O(1),一个双重循环遍历所有可行矩阵。 这里就直接用 O(n) & O…

毫秒值转换为日期工具_为机器学习准备数据

将数据转化为见解并不是神奇的事情。您必须首先了解您的数据,并使用它来创建驱动操作的报告。如果您的竞争对手使用机器学习和人工智能来自动推动行动,而您却没有,那您​​将处于不利地位。为ML和AI准备好数据涉及将结构化和半结构化数据集组…

【LeetCode笔记】剑指 Offer 55 - II. 平衡二叉树(递归、二叉树)

文章目录题目描述思路 && 代码二刷一脸懵逼,居然没写这道题的题解。。 题目描述 思路 && 代码 思路:平衡二叉树判断公式 左子树满足 右子树满足 左右子树高度差不超过1 /*** Definition for a binary tree node.* public class Tre…