一、前言
我们先看看能实现什么效果,先来个正常版的,先看看原场景:
下面是我们切换场景后的样子:
看起来效果还是不错的,有了这个我们就可以随意切换场景,坟头蹦迪不是梦。另外,我们再来看看另外一种效果,相比之下要狂放许多:
二、实现步骤
视频是有一帧一帧的画面组成的,每一帧都是一张图片,我们要实现对视频的修改就需要对视屏中每一帧画面进行修改。所以在最开始,我们需要获取视频每一帧画面。
具体步骤如下:
读取视频,获取每一帧画面
批量抠图
读取场景图片
对每一帧画面进行场景切换
写入视频
读取原视频的音频
给新视频设置音频
因为上面的步骤还是比较耗时的,所以我在视频完成后通过邮箱发送通知,告诉我视频制作完成。
三、模块安装
我们需要使用到的模块主要有如下几个:
pillow
opencv
moviepy
paddlehub
其中OpenCV建议选取3.0以上版本。
在我们使用paddlehub之前,我们需要安装paddlepaddle:具体安装步骤可以参见大佬教你,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
import math
import numpy as np
from PIL import Image
import paddlehub as hub
from moviepy.editor import *
#将视频逐帧保存为图片
def getFrame(video_name, save_path):
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 = video.read()
while ret:
cv2.imwrite(save_path + str(name) + '.jpg', frame)
ret, frame = video.read()
name += 1
video.release()
return fps, size
#对帧图片进行批量抠图
def getHumanseg(frames):
humanseg = hub.Module(name='deeplabv3p_xception65_humanseg')
files = [frames + i for i in os.listdir(frames)]
humanseg.segmentation(data={'image': files}) # 抠图
def setGreenBg(humanseg):
"""
给抠好的图设置绿幕
:param humanseg: 抠好的png图片
:return: 返回图片的ndarray对象
"""
im = Image.open(humanseg).convert('RGBA')
# 遍历图片的每个像素
for i in range(im.size[0]):
for j in range(im.size[1]):
r, g, b, a = im.getpixel((i, j))
if a == 0:
im.putpixel((i, j), (0, 255, 0, 0))
im = im.convert('RGB')
im_array = np.array(im)
im_array = im_array[:, :, ::-1]
return im_array
def readBg(bgname, size):
"""
读取背景图片,并修改尺寸
:param bgname: 背景图片名称
:param size: 视频分辨率
:return: Image对象
"""
im = Image.open(bgname)
return im.resize(size)
#将抠好的图和背景图片合并
def setImageBg(humanseg, bg_im):
# 读取透明图片
im = Image.open(humanseg)
# 分离色道
r, g, b, a = im.split()
# 将合成图转换成RGB,这样A通道就没了
#bg_im = bg_im.copy()#少了这一步的话,生成“千手观音效果”
bg_im.paste(im, (0, 0), mask=a)
return np.array(bg_im.convert('RGB'))[:, :, ::-1]
def writeVideo(humanseg, bg_im, fps, size):
"""
:param frames: 帧的路径
: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()
#获取指定视频的音频
def getMusic(video_name):
"""
:param video_name:
:return:
"""
# 读取视频文件
video = VideoFileClip(video_name)
# 返回音频
return video.audio
#实现混流,给video_name添加音频
def addMusic(video_name, audio):
# 读取视频
video = VideoFileClip(video_name)
# 设置视频的音频
video = video.set_audio(audio)
# 保存新的视频文件
video.write_videofile(output_video)
"""删除过渡文件"""
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)
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()
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 + '\\result2.mp4'
if not os.path.exists(frame_path):
os.makedirs(frame_path)
changeVideoScene('155小分队.mp4', 'ba.jpg')