Python厲害已經不是一天兩天的事了,但是我怎麼也沒想到,Python還能做特效!
因為之前接觸過Python一個批量摳圖的模型庫,然後最近看視頻突然就靈光一現,為啥不用Python給視頻摳圖換個背景?於是就有了下面這個操作:
先看看效果,這是原場景:
這是換過背景的樣子:
看起來還不錯吧。
再看另一種效果,是不是要狂放許多:
重點來了!
1、具體步驟
讀取視頻,獲取每一幀畫面批量摳圖讀取場景圖片對每一幀畫面進行場景切換寫入視頻讀取原視頻的音頻給新視頻設置音頻
2、模塊安裝
我們需要使用到的模塊主要有如下幾個:
pillowopencvmoviepypaddlehub
都可以直接用pip安裝:
pip install pillowpip install opencv-pythonpip install moviepy
其中OpenCV有一些適配問題,建議選取3.0以上版本。
在我們使用paddlehub之前,我們需要安裝paddlepaddle。
用paddlehub摳圖。
我們這裡直接用pip安裝cpu版本的:
# 安裝paddlepaddlepython -m pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple# 安裝paddlehubpip install -i https://mirror.baidu.com/pypi/simple paddlehub
有了這些準備工作就可以開始我們功能的實現了。
3、具體實現
我們導入如下包:
import cv2 # opencvimport mail # 自定義包,用於發郵件import mathimport numpy as npfrom PIL import Image # pillowimport paddlehub as hubfrom 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)讀取視頻,獲取每一幀畫面
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))
這樣做是為了讓畫面逐幀排序,這樣讀取的時候就不會亂。
(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)對每一幀畫面進行場景切換
簡單來說就是將摳好的圖片和背景圖片合併
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轉換成bgrbgr_im_array = im_array[:, :, ::-1]
最後bgr_im_array就是我們最終的返回結果。
(5)寫入視頻
為了節約空間,我並非等將寫入圖片放在合併場景後面,而是邊合併場景邊寫入視頻:
def writeVideo(humanseg, bg_im, fps, size):""" :param humanseg: png圖片的路徑 :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)
最後就是將整個流程整合一下。
(9)整合
我們將上面完整的流程合併成一個函數:
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()
(10)在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'ifnot 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__())
這樣我們就完成了完整的流程。
只是簡簡單單換個背景算什麼,下面來實現一個稍微複雜的特效——「影分身」。
有請我們本場的主演,坤製作人為我們表演他拿手的雞你太美。
實現原理和上一個操作沒有本質區別,同樣是逐幀處理,但是這裡還是詳細說一下。
首先我們要準備一個視頻,作為我們的素材。
然後我們要逐幀提取視頻中的圖像,接下來我們利用paddlehub逐幀摳取人像。這樣就有了我們的主體,和分身了。
最後我們需要在寫入視頻的時候對圖像進行處理,我直接在原圖像上粘貼了兩個人物分身,最後合成的視頻效果就是上面的效果了。
當然我們還需要添加音頻,所以最後我們需要讀取音頻並將新視頻同音頻混流。我們將整個過程分為以下幾個步驟:
1.逐幀提取圖像
2.批量摳圖
3.合成圖像(影分身)
4.寫入視頻
5.讀取音頻
6.混流
最終我們就能實現一個完整的視頻了。
具體操作:
為了方便,我們全都使用pip安裝:
pip install pillowpip install opencv-pythonpip install moviepy# 安裝paddlepaddlepython -m pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple# 安裝paddlehubpip install -i https://mirror.baidu.com/pypi/simple paddlehub
也就不廢話了,如果安裝過程中出了什麼問題可以自行百度。
我們先看看導入的一些模塊:
import cv2import mathimport numpy as npfrom PIL import Imageimport paddlehub as hubfrom moviepy.editor import *
我們按照上面的步驟,一步一步來。
逐幀提取圖像:
這就需要使用到我們的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以上,如果是低版本的話會出現兼容問題。
批量摳圖:
批量摳圖需要使用到我們的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目錄,摳好的圖像就在裡面。
合成圖像(影分身):
這裡需要使用到我們的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函數。
寫入視頻:
寫入視頻的操作同樣是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進行音頻的混流了。
混流:
我們混流的操作就是先獲取音頻,然後再混流,而音頻我們只需要讀取原視頻的音頻即可:
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是我們自己定義的一個存放文件保存路徑的變量,需要注意,該全路徑(路徑+名稱)不能和原視頻相同。
實現特效:
也就是將整個流程整合到一起:
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')
這樣就實現了我們完整的特效。
下面再來一個更牛的,幾行代碼實現黑客電影經典鏡頭,先看效果:
實時視頻錄製效果:
簡單示範一下:
為了顯得更加逼真,通常我不直接運行原始碼文本,一種操作如下:
在文本編輯器中寫好原始碼,將文本命名保存在某個硬碟位置中,此處假設命名為try.py,文件路徑為E:Test\go\try.py;在桌面新建一個文本文件,在文件裡編輯如下內容:
E:cd Test\gopython try.py
cd 後面所接的為原始碼try.py文件所在文件目錄路徑。
完成以上操作後,保存該文件為go.bat;雙擊該文件,完成。注意,此方法一定要先配置python的系統環境變量設置,一般安裝python後都會進行此操作,如未配置環境變量,可按照如下設置:簡要方法:在環境變量中添加Python目錄(左右滑動)(1) 安裝好Python後,右鍵點擊"計算機",然後點擊"屬性"; (2) 然後點擊"高級系統設置"; (3) 選擇"系統變量"窗口下面的"Path",雙擊打開或點擊右側添加選項; (4) 然後在"Path"行,添加python安裝路徑; (5) 更詳細教程請自行搜索。python文件原始碼
from fractions import Fractionimport timex = Fraction(3, 5)y = Fraction(1, 5)count = 0time.sleep(10)while x < 10:print(x) time.sleep(0.05) x = x * y - 1 count = count + 1 if count == 100000: breaktime.sleep(10)
一直覺得電影特效,動畫製作這些都很那什麼,其實還是有一些開發電影特效的不錯的軟體,像Houdini (電影特效魔術師) 、NUKE ,都支持python腳本開發。
學習python完全可以玩轉電影特效,影視合成,python的價值還需要被更多地去開發挖掘。