Python-OpenCV錄製H264編碼的MP4視訊
前言
因最近專案需求涉及計算機視覺相關內容,需要實現在Python錄製視訊,並且錄製完成後可在瀏覽器前端中進行視訊回放的功能;特寫下此篇文章以記錄整體實現過程。
2019-08-02 更新
之前一直在忙別的事,沒有繼續深入探究,這篇文章也暫時擱置了;但是最近發現之前的實現方式(錄製avi視訊後由Java呼叫FFmpeg轉換為mp4)會影響到系統的效能,原因為呼叫FFmpeg轉換視訊時CPU佔用較高QAQ,於是在此前的基礎上繼續尋找解決方式。
降低FFmpeg的CPU佔用
既然FFmpeg的CPU佔用較高,那麼我們首先嚐試如何降低對CPU的佔用,搜尋發現可以在FFmpeg命令中新增-threads
引數來指定CPU的使用
FFmpeg轉換測試
此次測試均使用相同avi視訊檔案,大小為113
1. 原始轉換命令
ffmpeg -i test.avi -vcodec libx264 -f mp4 test.mp4
# 轉換用時 30s~31s
# CPU佔用 950%~1000%
複製程式碼
2. 新增-threads 6
引數
ffmpeg -i test.avi -threads 6 -vcodec libx264 -f mp4 test.mp4
# 轉換用時 45s~46s
# CPU 佔用490%~550%
複製程式碼
3. 新增-threads 2
引數
ffmpeg -i test.avi -threads 2 -vcodec libx264 -f mp4 test.mp4
# 轉換用時 87s~88s
# CPU佔用 205%~230%
複製程式碼
可以看出,新增-threads
引數後CPU的佔用確實少了,但相應的視訊轉換耗時也增加了,顯然這不是我們想要的效果;所以還是逃避不了錄製H264視訊的問題
編譯安裝OpenCV錄製視訊
之前一直無法錄製H264編碼的MP4視訊是因為使用的為pip安裝的opencv-python,這個庫中自帶FFmpeg,所以不論我們如何折騰系統的FFmpeg都不會有任何作用;如果我們想要呼叫系統的FFmpeg則需要手動編譯安裝OpenCV。具體原因可以參考下圖:
如何編譯安裝OpenCV就不過多敘述了,這也不是此篇文章的重點,但還是給懶癌患者放個連結吧! Ubuntu16.04 install OpenCV with ffmpeg
編譯安裝後import cv2
正常引入即可,程式碼就不放了,原文和網上都有,只是改個fourcc。
至此在Python中呼叫OpenCV錄製H264編碼的MP4視訊已經可以實現,沒有特殊需求的同學看到這裡就可以了~撒花!
使用vidgear庫錄製視訊
因為專案原因我們還不能使用手動編譯的OpenCV(WTF!!!),所以不得不繼續尋找解決方案QAQ
vidgear-github官方連結,這個方案已經脫離主題,只是由於專案原因而採用,在此就不過多敘述了,感興趣的同學可以看一下。
以下內容為原文
Python-OpenCV錄製視訊
環境
python 3.7.1
opencv-python 3.4.4.19
引入庫支援
import cv2
複製程式碼
呼叫攝像頭
入參傳入“0”、“1”、“2”等數字為攝像頭索引,0為自帶攝像頭,可按順序呼叫攝像頭,也可傳入視訊檔案路徑
cap = cv2.VideoCapture(0)
複製程式碼
獲取攝像頭寬高
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
複製程式碼
使用攝像頭幀率錄製視訊後播放存在快進情況,暫時寫死在VideoWriter中
不知道是否與攝像頭有關,此處未進行深入瞭解
fps = cap.get(cv2. CV_CAP_PROP_FPS)
指定視訊編解碼
需要傳入fourcc(four character code)四字元編解碼程式碼: fourcc參考
encode = cv2.VideoWriter_fourcc(*'mp4v')
複製程式碼
初始化VideoWriter
入參參考:官方檔案
out = cv2.VideoWriter( './test.mp4',encode,10,(width,height),True)
複製程式碼
獲取影象幀並寫入視訊檔案
- 迴圈從攝像頭/視訊中獲取單幀影象
- 新開一個視窗展示影象幀,每隔25毫秒播放下一幀,鍵入“q”跳出迴圈
- 將影象幀寫入視訊檔案
while True:
if cv2.waitKey(25) & 0xFF == ord('q'):
break
ret,frame = cap.read()
cv2.imshow('test',frame)
out.write(frame)
複製程式碼
釋放資源
- 釋放VideoWriter
- 釋放攝像頭
- 關閉視窗
out.release()
cap.release()
cv2.destroyAllWindows()
複製程式碼
完整程式碼
此處程式碼為演示demo,僅供參考
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2
# 呼叫攝像頭
cap = cv2.VideoCapture(0)
# 獲取攝像頭寬高
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 獲取攝像頭幀率
#fps = cap.get(cv2.CAP_PROP_FPS)
# 指定fourcc編解碼
encode = cv2.VideoWriter_fourcc(*'mp4v')
# 初始化VideoWriter
out = cv2.VideoWriter('./test.mp4',True)
while True:
# 每隔25毫秒播放下一幀,若鍵入“q”跳出迴圈
if cv2.waitKey(25) & 0xFF == ord('q'):
break
# 從攝像頭獲取下一幀
ret,frame = cap.read()
# 新開視窗展示影象
cv2.imshow('test',frame)
# 將當前幀寫入視訊檔案
out.write(frame)
# 釋放VideoWriter
out.release()
# 釋放攝像頭
cap.release()
# 關閉視窗
cv2.destroyAllWindows()
複製程式碼
瀏覽器中播放視訊
環境
macOS Mojave 10.14.3
Ubuntu 16.04
vue 2.9.6
nginx 1.15.5
前端為vue專案,打包後部署在nginx,配置server塊/location塊提供圖片/視訊等靜態資源訪問
h5中video無法播放視訊問題
問題排查
- 程式碼錯誤
python錄製視訊是否成功
前端中video的src是否正確
- 網路請求
瀏覽器控制檯是否報錯
nginx服務是否啟動
請求路徑是否正確
是否跨域問題
- 瀏覽器支援
格式 | IE | Firefox | Opera | Chrome | Safari |
---|---|---|---|---|---|
Ogg | - | 3.5+ | 10.5+ | 5.0+ | - |
MPEG 4 | 9.0+ | - | - | 5.0+ | 3.0+ |
WebM | - | 4.0+ | 10.6+ | 6.0+ | - |
- 視訊編解碼
格式 | 視訊編碼 | 音訊編碼 |
---|---|---|
Ogg | Theora | Vorbis |
MPEG 4 | H.264 | AAC |
WebM | VP8 | Vorbis |
問題定位
排除程式碼及網路請求問題後,可以將問題定位在瀏覽器,我使用的瀏覽器為Chrome,排除版本問題,因此可以確定是視訊編解碼問題,在python中錄製視訊時未使用H.264編解碼:
encode = cv2.VideoWriter_fourcc(*'mp4v')
複製程式碼
檢視視訊簡介可以發現該視訊也確實非H.264編解碼,因此造成該視訊可以在視訊播放軟體中正常播放卻無法在h5的video中播放,見下圖:
嘗試更改fourcc重新錄製視訊
encode = cv2.VideoWriter_fourcc(*'X264')
複製程式碼
貌似不支援這個編解碼QAQ,好像需要FFmpeg的庫,Ubuntu下在終端輸入:
$sudo apt-get install ffmpeg x264 libx264-dev
複製程式碼
安裝完成後Ubuntu上無法錄製(視訊檔案都無法生成),但是在我自己的電腦不影響錄製:
Java使用FFmpeg轉換視訊
因暫時未能實現錄製H.264編解碼的MP4視訊,所以採用迂迴戰術:在python中錄製.avi格式視訊後,前端請求後臺,在java中使用FFmpeg將.avi格式視訊轉換為.mp4格式視訊
首先安裝FFmpeg (Ubuntu下我沒有安裝,好像是自帶的?) macOS安裝FFmpeg Ubuntu安裝FFmpeg java這邊就不再詳述了,直接上程式碼~(同樣為演示demo,僅供參考)
// FFmpeg轉換命令
String transferCommand = "ffmpeg -i filePath/fileName.avi -vcodec libx264 -f mp4 filePath/fileName.mp4";
Process process = Runtime.getRuntime().exec("/bin/bash");
printWriter = new PrintWriter(new BufferedWriter(new
OutputStreamWriter(process.getOutputStream())),true);
printWriter.println(transferCommand);
// 這個命令必須執行,否則in流不結束。
printWriter.println("exit");
printWriter.close();
process.waitFor();
複製程式碼
轉換過程需要些許時間,採取方案為啟一條執行緒完成視訊轉換,不影響當前介面響應時間,在使用者無感知的情況下完成視訊轉換。
總結
以上內容為本次實現過程記錄,程式碼均為演示demo,非實際應用程式碼,如有需要可根據實際需求加以調整。因為時間原因未能在錄製H.264視訊上投入過多精力,可能未來會繼續嘗試~