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正则表达式匹配HTML页面编码
Apr 08 Python
在Python中使用第三方模块的教程
Apr 27 Python
Django项目实战之用户头像上传与访问的示例
Apr 21 Python
python实现根据指定字符截取对应的行的内容方法
Oct 23 Python
Python二进制文件读取并转换为浮点数详解
Jun 25 Python
教你一步步利用python实现贪吃蛇游戏
Jun 27 Python
关于PyTorch 自动求导机制详解
Aug 18 Python
Python监控服务器实用工具psutil使用解析
Dec 19 Python
Flask框架搭建虚拟环境的步骤分析
Dec 21 Python
Matplotlib.pyplot 三维绘图的实现示例
Jul 28 Python
python读取pdf格式文档的实现代码
Apr 01 Python
利用Pycharm连接服务器的全过程记录
Jul 01 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
德生PL450的电路分析和低放电路的改进办法
2021/03/02 无线电
PHP中设置时区,记录日志文件的实现代码
2013/01/07 PHP
PHP将回调函数作用到给定数组单元的方法
2014/08/19 PHP
Adnroid 微信内置浏览器清除缓存
2016/07/11 PHP
laravel配置Redis多个库的实现方法
2019/04/10 PHP
laravel实现简单用户权限的示例代码
2019/05/28 PHP
tp5框架前台无限极导航菜单类实现方法分析
2020/03/29 PHP
jQuery中filter(),not(),split()使用方法
2010/07/06 Javascript
javascript一些实用技巧小结
2011/03/18 Javascript
用js格式化金额可设置保留的小数位数
2014/05/09 Javascript
如何使用HTML5地理位置定位功能
2015/04/27 Javascript
jQuery实现多级下拉菜单jDropMenu的方法
2015/08/28 Javascript
jquery轮播的实现方式 附完整实例
2016/07/28 Javascript
JQuery之proxy实现绑定代理方法
2016/08/01 Javascript
基于jQuery实现手风琴菜单、层级菜单、置顶菜单、无缝滚动效果
2017/07/20 jQuery
使用JS和canvas实现gif动图的停止和播放代码
2017/09/01 Javascript
微信、QQ、微博、Safari中使用js唤起App
2018/01/24 Javascript
layer弹出层 iframe层去掉滚动条的实例代码
2018/08/17 Javascript
深入理解JavaScript 中的匿名函数((function() {})();)与变量的作用域
2018/08/28 Javascript
在Vue methods中调用filters里的过滤器实例
2018/08/30 Javascript
代码分析vue中如何配置less
2018/09/28 Javascript
JS使用new操作符创建对象的方法分析
2019/05/30 Javascript
js图片查看器插件用法示例
2019/06/22 Javascript
浅谈layui 数据表格前后台传值的问题
2019/09/12 Javascript
javascript读取本地文件和目录方法详解
2020/08/06 Javascript
wxPython学习之主框架实例
2014/09/28 Python
Python 12306抢火车票脚本 Python京东抢手机脚本
2018/02/06 Python
python 实现矩阵上下/左右翻转,转置的示例
2019/01/23 Python
python获取依赖包和安装依赖包教程
2020/02/13 Python
美国社交购物市场:MassGenie
2019/02/18 全球购物
Fanatics法国官网:美国体育电商
2019/08/27 全球购物
护士辞职信模板
2014/01/20 职场文书
“四风”问题对照检查材料思想汇报
2014/09/16 职场文书
孕妇离婚协议书范本
2014/11/20 职场文书
2015年小学师德师风建设工作总结
2015/10/23 职场文书
MySQL 数据丢失排查案例
2021/05/08 MySQL