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 相关文章推荐
zookeeper python接口实例详解
Jan 18 Python
python实现NB-IoT模块远程控制
Jun 20 Python
Python Unittest根据不同测试环境跳过用例的方法
Dec 16 Python
python矩阵/字典实现最短路径算法
Jan 17 Python
python3实现指定目录下文件sha256及文件大小统计
Feb 25 Python
基于Django ORM、一对一、一对多、多对多的全面讲解
Jul 26 Python
tensorflow2.0保存和恢复模型3种方法
Feb 03 Python
Centos7下源码安装Python3 及shell 脚本自动安装Python3的教程
Mar 07 Python
详解python内置模块urllib
Sep 09 Python
python爬虫筛选工作实例讲解
Nov 23 Python
python os.listdir()乱码解决方案
Jan 31 Python
python图像处理基本操作总结(PIL库、Matplotlib及Numpy)
Jun 08 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
第九节 绑定 [9]
2006/10/09 PHP
基于PHP文件操作的详细诠释
2013/06/21 PHP
PHP实现定时执行任务的方法
2014/10/05 PHP
php基础设计模式大全(注册树模式、工厂模式、单列模式)
2015/08/31 PHP
PHP删除二维数组中相同元素及数组重复值的方法示例
2017/05/05 PHP
yii2局部关闭(开启)csrf的验证的实例代码
2017/07/10 PHP
Laravel框架控制器的middleware中间件用法分析
2019/09/30 PHP
JavaScript中的闭包(Closure)详细介绍
2014/12/30 Javascript
JavaScript中的Truthy和Falsy介绍
2015/01/01 Javascript
jQuery使用hide方法隐藏页面上指定元素的方法
2015/03/30 Javascript
JQuery记住用户名密码实现下次自动登录功能
2015/04/27 Javascript
JavaScript给input的value赋值引发的关于基本类型值和引用类型值问题
2015/12/07 Javascript
jQuery页面刷新(局部、全部)问题分析
2016/01/09 Javascript
深入浅析JavaScript的API设计原则
2016/06/14 Javascript
JS实现评价的星星功能
2017/08/20 Javascript
js Element Traversal规范中的元素遍历方法
2018/04/19 Javascript
vue自定义js图片碎片轮播图切换效果的实现代码
2019/04/28 Javascript
ios中视频的最后一桢问题解决
2019/05/14 Javascript
ES6 Map结构的应用实例分析
2019/06/26 Javascript
解决Layui当中的导航条动态添加后渲染失败的问题
2019/09/25 Javascript
python机器学习案例教程——K最近邻算法的实现
2017/12/28 Python
python 2.7.13 安装配置方法图文教程
2018/09/18 Python
详解python路径拼接os.path.join()函数的用法
2019/10/09 Python
Python爬虫之urllib基础用法教程
2019/10/12 Python
python获取网络图片方法及整理过程详解
2019/12/20 Python
canvas实现扭蛋机动画效果的示例代码
2018/10/17 HTML / CSS
台湾生鲜宅配:大口市集
2017/10/14 全球购物
JD Sports德国官网:英国领先的运动鞋和运动服饰零售商
2018/02/26 全球购物
Sneaker Studio匈牙利:购买运动鞋
2018/03/26 全球购物
Habitat家居英国官方网站:沙发、家具、照明、厨房和户外
2019/12/12 全球购物
英国Iceland杂货店:网上食品购物
2020/12/16 全球购物
新闻专业个人自我评价
2013/09/21 职场文书
新闻编辑求职信
2014/07/13 职场文书
上课说话检讨书500字
2014/11/01 职场文书
详解Python函数print用法
2021/06/18 Python
Golang MatrixOne使用介绍和汇编语法
2022/04/19 Golang