Python實現高級電影特效,CXK也能影分身

一、前言

前幾天寫了個實現特效的博客,感覺有點差強人意,只是簡簡單單的換背景應用場景不是非常多,今天就來實現一個更加複雜的特效“影分身”。下面有請我們本場的主演,坤製作人爲我們表演他拿手的雞你太美。
在這裏插入圖片描述
關於實現原理,和上一篇沒有本質區別,同樣是逐幀處理,但是這裏還是詳細說一下。

二、實現原理

首先我們要準備一個視頻,作爲我們的素材。然後我們要逐幀提取視頻中的圖像,接下來我們利用paddlehub逐幀摳取人像。這樣就有了我們的主體,和分身了。最後我們需要在寫入視頻的時候對圖像進行處理,我直接在原圖像上粘貼了兩個人物分身,最後合成的視頻效果就是上面的效果了。當然我們還需要添加音頻,所以最後我們需要讀取音頻並將新視頻同音頻混流。我們將整個過程分爲以下幾個步驟:

  1. 逐幀提取圖像
  2. 批量摳圖
  3. 合成圖像(影分身)
  4. 寫入視頻
  5. 讀取音頻
  6. 混流

最終我們就能實現一個完整的視頻了。

三、模塊安裝

爲了方便,我們全都使用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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章