一、前言
前幾天寫了個實現特效的博客,感覺有點差強人意,只是簡簡單單的換背景應用場景不是非常多,今天就來實現一個更加複雜的特效“影分身”。下面有請我們本場的主演,坤製作人爲我們表演他拿手的雞你太美。
關於實現原理,和上一篇沒有本質區別,同樣是逐幀處理,但是這裏還是詳細說一下。
二、實現原理
首先我們要準備一個視頻,作爲我們的素材。然後我們要逐幀提取視頻中的圖像,接下來我們利用paddlehub逐幀摳取人像。這樣就有了我們的主體,和分身了。最後我們需要在寫入視頻的時候對圖像進行處理,我直接在原圖像上粘貼了兩個人物分身,最後合成的視頻效果就是上面的效果了。當然我們還需要添加音頻,所以最後我們需要讀取音頻並將新視頻同音頻混流。我們將整個過程分爲以下幾個步驟:
- 逐幀提取圖像
- 批量摳圖
- 合成圖像(影分身)
- 寫入視頻
- 讀取音頻
- 混流
最終我們就能實現一個完整的視頻了。
三、模塊安裝
爲了方便,我們全都使用pip安裝:
pip install pillow
pip install opencv-python
pip install moviepy
# 安裝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 *
我們按照上面的步驟,一步一步來。
4.1、逐幀提取圖像
這就需要使用到我們的opencv了,具體代碼如下:
def getFrame(video_name, save_path):
"""
傳入視頻名稱,將圖像幀保存到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
這裏我們只需要注意OpenCV版本需要在3.0以上,如果是低版本的話會出現兼容問題。
4.2、批量摳圖
批量摳圖需要使用到我們的paddhub模型庫,而摳圖的實現也只需要幾行代碼:
def getHumanseg(frames):
"""
對frames路徑下所以圖片進行摳圖
"""
# 加載模型庫
humanseg = hub.Module(name='deeplabv3p_xception65_humanseg')
# 遍歷路徑下文件
files = [frames + i for i in os.listdir(frames)]
# 摳圖
humanseg.segmentation(data={'image': files})
我們調用該方法後會在目錄下生成humanseg_output目錄,摳好的圖像就在裏面。
4.3、合成圖像(影分身)
這裏需要使用到我們的Pillow模塊,該模塊中提供了圖像粘貼的函數:
def setImageBg(humanseg, bg_im):
"""
將摳好的圖和背景圖片合併
:param humanseg:
:param bg_im:
:return:
"""
# 讀取透明圖片
im = Image.open(humanseg)
# 分離色道
r, g, b, a = im.split()
# 在圖片右邊粘貼一個人物分身
bg_im.paste(im, (bg_im.size[0]//3, 0), mask=a)
# 在圖片左邊粘貼一個人物分身
bg_im.paste(im, (-bg_im.size[0]//3, 0), mask=a)
# 將圖形轉換成opencv能正常讀取的類型,並返回
return np.array(bg_im.convert('RGB'))[:, :, ::-1]
上面主要就是使用paste函數。
4.4、寫入視頻
寫入視頻的操作同樣是OpenCV來實現的:
def writeVideo(humanseg_path, frames, fps, size):
"""
傳入摳好的人像,和原圖像,以及原視頻幀率,大小,寫入新視頻
"""
# 寫入視頻
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('green.mp4', fourcc, fps, size)
# 將每一幀設置背景
humanseg = [humanseg_path + i for i in os.listdir(humanseg_path)]
frames = [frames + i for i in os.listdir(frames)]
for i in range(humanseg.__len__()):
# 讀取原圖像
bg_im = Image.open(frames[i])
# 設置分身
im_array = setImageBg(humanseg[i], bg_im)
# 寫入視頻
out.write(im_array)
out.release()
到這裏我們就實現了一個視頻,但是現在還沒有聲音,接下來就需要我們用moviepy進行音頻的混流了。
4.5、混流
我們混流的操作就是先獲取音頻,然後再混流,而音頻我們只需要讀取原視頻的音頻即可:
def getMusic(video_name):
"""
獲取指定視頻的音頻
"""
# 讀取視頻文件
video = VideoFileClip(video_name)
# 返回音頻
return video.audio
其中VideoFileClip是moviepy中的一個視頻處理的類。下面我們來添加音樂:
def addMusic(video_name, audio):
"""實現混流,給video_name添加音頻"""
# 讀取視頻
video = VideoFileClip(video_name)
# 設置視頻的音頻
video = video.set_audio(audio)
# 保存新的視頻文件
video.write_videofile(output_video)
output_video是我們自己定義的一個存放文件保存路徑的變量,需要注意,該全路徑(路徑+名稱)不能和原視頻相同。
4.6、實現特效
也就是將整個流程整合到一起:
def changeVideoScene(video_name):
"""
:param video_name: 視頻的文件
:param bgname: 背景圖片
:return:
"""
# 讀取視頻中每一幀畫面
fps, size = getFrame(video_name, frames)
# 批量摳圖
getHumanseg(frames)
# 將畫面一幀幀寫入視頻
writeVideo(humanseg_path, frames, fps, size)
# 混流
addMusic('green.mp4', getMusic(video_name))
在上面有些變量我們還沒有定義,我們在main函數中定義一下:
if __name__ == '__main__':
# 當前項目根目錄
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "."))
# 每一幀畫面保存的地址
frames = BASE_DIR + '\\frames\\'
# 摳好的圖片位置
humanseg_path = BASE_DIR + '\\humanseg_output\\'
# 最終視頻的保存路徑
output_video = BASE_DIR + '\\result.mp4'
# 創建文件夾
if not os.path.exists(frames):
os.makedirs(frames)
if not os.path.exists(background_path):
os.makedirs(background_path)
# 給視頻添加特效
changeVideoScene('jntm.mp4')
這樣就實現了我們完整的特效。感興趣的讀者可以參照相關博客https://blog.csdn.net/ZackSock/article/details/105558172,另外大家可以關注我的個人公衆號:ZackSock。