您好,登錄后才能下訂單哦!
這篇文章主要講解了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毛特效的方法有進一步的了解,如果還想學習更多內容,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。