Python实现电视里的5毛特效实例代码详解


Posted in Python onMay 15, 2020

前段时间接触了一个批量抠图的模型库,而后在一些视频中找到灵感,觉得应该可以通过抠图的方式,给视频换一个不同的场景,于是就有了今天的文章。

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

Python实现电视里的5毛特效实例代码详解

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

Python实现电视里的5毛特效实例代码详解

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

Python实现电视里的5毛特效实例代码详解

实现步骤

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

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

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

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

具体步骤如下:

  • 读取视频,获取每一帧画面
  • 批量抠图
  • 读取场景图片
  • 对每一帧画面进行场景切换
  • 写入视频
  • 读取原视频的音频
  • 给新视频设置音频

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

模块安装

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

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 += 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: jpg图片的路径
  :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'] = 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邮箱。另外在登录的时候直接使用密码比较方便,但是有安全隐患。

总结

老实说上述程序的效率非常低,不仅占空间,而且耗时也比较长。在最开始我切换场景选择的是遍历图片每一个像素,而后找到了更加高效的方式取代了。但是帧画面的保存,和jpg图片的存储都很耗费空间。

另外程序设计还是有许多不合理的地方,像是ndarray对象和Image的区分度不高,另外有些函数选择传入路径,而有些函数选择传入文件对象也很容易让人糊涂。

最后说一下,我们用上面的方式不仅可以做静态的场景切换,还可以做动态的场景切换,这样我们就可以制作更加丰富的视频。当然,效率依旧是个问题!

到此这篇关于Python实现了电视里的5毛特效的文章就介绍到这了,更多相关Python 5毛特效内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
跟老齐学Python之复习if语句
Oct 02 Python
python实现超简单端口转发的方法
Mar 13 Python
通过Python来使用七牛云存储的方法详解
Aug 07 Python
Python编程之基于概率论的分类方法:朴素贝叶斯
Nov 11 Python
python3+PyQt5重新实现QT事件处理程序
Apr 19 Python
Python中的单继承与多继承实例分析
May 10 Python
django的settings中设置中文支持的实现
Apr 28 Python
Python如何优雅获取本机IP方法
Nov 10 Python
python 函数嵌套及多函数共同运行知识点讲解
Mar 03 Python
python torch.utils.data.DataLoader使用方法
Apr 02 Python
pytorch 如何使用float64训练
May 24 Python
在Python 中将类对象序列化为JSON
Apr 06 Python
python中wx模块的具体使用方法
May 15 #Python
使用pymysql查询数据库,把结果保存为列表并获取指定元素下标实例
May 15 #Python
python随机模块random的22种函数(小结)
May 15 #Python
将pymysql获取到的数据类型是tuple转化为pandas方式
May 15 #Python
python 数据库查询返回list或tuple实例
May 15 #Python
Python基于gevent实现高并发代码实例
May 15 #Python
Django bulk_create()、update()与数据库事务的效率对比分析
May 15 #Python
You might like
海贼王动画变成“真人”后,凯多神还原,雷利太帅了!
2020/04/09 日漫
PHP.MVC的模板标签系统(二)
2006/09/05 PHP
php ZipArchive压缩函数详解实例
2013/11/06 PHP
php使用date和strtotime函数输出指定日期的方法
2014/11/14 PHP
深入理解PHP中的empty和isset函数
2016/05/26 PHP
php常用正则函数实例小结
2016/12/29 PHP
仿163填写邮件地址自动显示下拉(无优化)
2008/11/05 Javascript
JavaScript面向对象(极简主义法minimalist approach)
2012/07/17 Javascript
jquery索引在使用中的一些困惑
2013/10/24 Javascript
jQuery中DOM操作实例分析
2015/01/23 Javascript
JS实现网页表格自动变大缩小的方法
2015/03/09 Javascript
纯js实现重发验证码按钮倒数功能
2015/04/21 Javascript
高性能JavaScript循环语句和条件语句
2016/01/20 Javascript
JavaScript易错知识点整理
2016/12/05 Javascript
EditPlus 正则表达式 实战(3)
2016/12/15 Javascript
JavaScript实现获取远程的html到当前页面中
2017/03/26 Javascript
Node.js中的require.resolve方法使用简介
2017/04/23 Javascript
微信小程序图片宽100%显示并且不变形
2017/06/21 Javascript
基于vue.js的分页插件详解
2017/11/27 Javascript
基于jQuery Ajax实现下拉框无刷新联动
2017/12/06 jQuery
js推箱子小游戏步骤代码解析
2018/01/10 Javascript
基于js实现抽红包并分配代码实例
2019/09/19 Javascript
JavaScript Event Loop相关原理解析
2020/06/10 Javascript
Python内建数据结构详解
2016/02/03 Python
浅谈Python处理PDF的方法
2017/11/10 Python
基于Django实现日志记录报错信息
2019/12/17 Python
Python参数传递实现过程及原理详解
2020/05/14 Python
Django通过json格式收集主机信息
2020/05/29 Python
CSS3中animation实现流光按钮效果
2020/12/21 HTML / CSS
正规的求职信范文分享
2013/12/11 职场文书
医药营销个人求职信范文
2014/02/07 职场文书
市场营销战略计划书
2014/05/06 职场文书
法定代表人证明书
2014/11/28 职场文书
2014年安置帮教工作总结
2014/12/11 职场文书
家长评语怎么写
2014/12/30 职场文书
社会心理学学习心得体会
2016/01/22 职场文书