歡迎您光臨本站 註冊首頁

Python實現電視裡的5毛特效實例代碼詳解

←手機掃碼閱讀     retouched @ 2020-06-10 , reply:0

前段時間接觸了一個批量摳圖的模型庫,而後在一些視頻中找到靈感,覺得應該可以通過摳圖的方式,給視頻換一個不同的場景,於是就有了今天的文章。

我們先看看能實現什麼效果,先來個正常版的,先看看原場景:

Python教程:竟然用Python實現了電視裡的5毛特效

下面是我們切換場景後的樣子:

Python教程:竟然用Python實現了電視裡的5毛特效

看起來效果還是不錯的,有了這個我們就可以隨意切換場景,墳頭蹦迪不是夢。另外,我們再來看看另外一種效果,相比之下要狂放許多:

Python教程:竟然用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 + '
esult.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 + '
esult.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的區分度不高,另外有些函數選擇傳入路徑,而有些函數選擇傳入文件對象也很容易讓人糊塗。

最後說一下,我們用上面的方式不僅可以做靜態的場景切換,還可以做動態的場景切換,這樣我們就可以製作更加豐富的視頻。當然,效率依舊是個問題!


[retouched ] Python實現電視裡的5毛特效實例代碼詳解已經有280次圍觀

http://coctec.com/docs/python/shhow-post-237836.html