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毛特效内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python实现PS滤镜特效之扇形变换效果示例

    本文实例讲述了Python实现PS滤镜特效之扇形变换效果.分享给大家供大家参考,具体如下: 这里用 Python 实现 PS 滤镜中的一种几何变换特效,称为扇形变换,将图像扭曲成一个扇形,具体的算法原理和效果图可以参考附录说明 import numpy as np from skimage import img_as_float import matplotlib.pyplot as plt from skimage import io import math import numpy.matl

  • Python实现PS滤镜碎片特效功能示例

    本文实例讲述了Python实现PS滤镜碎片特效功能.分享给大家供大家参考,具体如下: 这里用 Python 实现 PS 滤镜中的碎片特效,这个特效简单来说就是将图像在 上,下,左,右 四个方向做平移,然后将四个方向的平移的图像叠加起来做平均.具体的效果图与说明可参考附录说明 from skimage import img_as_float import matplotlib.pyplot as plt from skimage import io file_name='D:/Visual Eff

  • 用Python PIL实现几个简单的图片特效

    导入 numpy .PIL numpy用来做矩阵运算,PIL用来读取图片. import numpy as np from PIL import Image 读取图片,然后转换成RGB模式存在矩阵里 im = Image.open(imagename).convert('RGB') arr = np.array(im) 查看arr的shape,可以看到arr是个3维的数组,数组大小等于 长*宽*3 In [566]: arr.shape Out[566]: (313, 450, 3) 每个像素有

  • Python实现PS滤镜功能之波浪特效示例

    本文实例讲述了Python实现PS滤镜功能之波浪特效.分享给大家供大家参考,具体如下: 这里用 Python 实现 PS 滤镜的波浪特效,具体效果可以参考附录说明 import numpy as np from skimage import img_as_float import matplotlib.pyplot as plt from skimage import io import numpy.matlib import math file_name2='D:/Visual Effects

  • Python实现PS滤镜特效Marble Filter玻璃条纹扭曲效果示例

    本文实例讲述了Python实现PS滤镜特效Marble Filter玻璃条纹扭曲效果.分享给大家供大家参考,具体如下: 这里用 Python 实现 PS 滤镜特效,Marble Filter, 这种滤镜使图像产生不规则的扭曲,看起来像某种玻璃条纹, 具体的代码如下: import numpy as np import math import numpy.matlib from skimage import io import random from skimage import img_as_f

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

    前段时间接触了一个批量抠图的模型库,而后在一些视频中找到灵感,觉得应该可以通过抠图的方式,给视频换一个不同的场景,于是就有了今天的文章. 我们先看看能实现什么效果,先来个正常版的,先看看原场景: 下面是我们切换场景后的样子: 看起来效果还是不错的,有了这个我们就可以随意切换场景,坟头蹦迪不是梦.另外,我们再来看看另外一种效果,相比之下要狂放许多: 实现步骤 我们都知道,视频是由一帧一帧的画面组成的,每一帧都是一张图片,我们要实现对视频的修改就需要对视频中每一帧画面进行修改.所以在最开始,我们需要

  • python获取时间及时间格式转换问题实例代码详解

    整理总结一下python中最常用的一些时间戳和时间格式的转换 第一部分:获取当前时间和10位13位时间戳 import datetime, time '''获取当前时间''' n = datetime.datetime.now() print(n) '''获取10位时间戳''' now = time.time() print(int(now)) '''获取13位时间戳''' now2 = round(now*1000) print(now2) 运行结果为: 2018-12-06 11:00:30

  • python 获取当前目录下的文件目录和文件名实例代码详解

    os模块下有两个函数: os.walk() os.listdir() # -*- coding: utf-8 -*- import os def file_name(file_dir): for root, dirs, files in os.walk(file_dir): print(root) #当前目录路径 print(dirs) #当前路径下所有子目录 print(files) #当前路径下所有非目录子文件 输出格式为: 当前文件目录路径 当前路径下子文件目录(若存在, 不存在则为 []

  • Python自然语言处理之词干,词形与最大匹配算法代码详解

    本文主要对词干提取及词形还原以及最大匹配算法进行了介绍和代码示例,Python实现,下面我们一起看看具体内容. 自然语言处理中一个很重要的操作就是所谓的stemming和lemmatization,二者非常类似.它们是词形规范化的两类重要方式,都能够达到有效归并词形的目的,二者既有联系也有区别. 1.词干提取(stemming) 定义:Stemmingistheprocessforreducinginflected(orsometimesderived)wordstotheirstem,base

  • Python 带有参数的装饰器实例代码详解

    demo.py(装饰器,带参数的装饰器): def set_level(level_num): def set_func(func): def call_func(*args, **kwargs): if level_num == 1: print("----权限级别1,验证----") elif level_num == 2: print("----权限级别2,验证----") return func() return call_func return set_f

  • Python生成验证码、计算具体日期是一年中的第几天实例代码详解

    1.约瑟夫环问题 <幸运的基督徒> 有15个基督徒和15个非基督徒在海上遇险,为了能让一部分人活下来不得不将其中15个人扔到海里面去,有个人想了个办法就是大家围成一个圈,由某个人开始从1报数,报到9的人就扔到海里面,他后面的人接着从1开始报数,报到9的人继续扔到海里面,直到扔掉15个人.由于上帝的保佑,15个基督徒都幸免于难,问这些人最开始是怎么站的,哪些位置是基督徒哪些位置是非基督徒. def main(): ''' 先用列表中每个数字代表每个人,然后通过循环替换列表中的数字 用@代表基督徒

  • python多进程使用及线程池的使用方法代码详解

    多进程:主要运行multiprocessing模块 import os,time import sys from multiprocessing import Process class MyProcess(Process): """docstring for MyProcess""" def __init__(self, arg, callback): super(MyProcess, self).__init__() self.arg = a

  • Python 八个数据清洗实例代码详解

    如果你经历过数据清洗的过程,你就会明白我的意思.而这正是撰写这篇文章的目的——让读者更轻松地进行数据清洗工作. 事实上,我在不久前意识到,在进行数据清洗时,有一些数据具有相似的模式.也正是从那时起,我开始整理并编译了一些数据清洗代码,我认为这些代码也适用于其它的常见场景. 由于这些常见的场景涉及到不同类型的数据集,因此本文更加侧重于展示和解释这些代码可以用于完成哪些工作,以便读者更加方便地使用它们. 数据清洗小工具箱 在下面的代码片段中,数据清洗代码被封装在了一些函数中,代码的目的十分直观.你可

  • Python实现多态、协议和鸭子类型的代码详解

    多态 问起面向对象的三大特性,几乎每个人都能对答如流:封装.继承.多态.今天我们就要来说一说 Python 中的多态. 所谓多态:就是指一个类实例的相同方法在不同情形有不同表现形式.多态机制使具有不同内部结构的对象可以共享相同的外部接口.这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用. 我在<Python 中的设计模式详解之:策略模式>一文中详细描述了策略模式的实现,而策略模式就是典型的多态应用. 之前的代码我就不贴了,大家可以去原文中

  • Python中变量的输入输出实例代码详解

    1.变量的输入: input函数: input() input("请输入银行卡密码") password = input("请输入银行卡密码") 变量名 = input("XXX") # 用输入函数给变量赋值 输入函数给变量赋值举例: 注:所有input()得到的数据类型都是str字符串类型 2.变量类型的转换函数: • int(x) # str转整数 • float(x) # str转小数 转换举例: 3.输入综合练习: # 1.输入苹果的单价

随机推荐