這裡介紹如何使用 Python 與 OpenCV 擷取網路攝影機影像,處理與顯示即時的畫面影像,並將連續的畫面影像寫入影片檔案中儲存起來。

若要使用 Python 取的網路攝影機的串流影像,可以透過 OpenCV 模組的 VideoCapture 影片擷取功能來達成,至於寫入影片檔則可使用 VideoWriter,操作方式非常簡單,以下是使用教學與簡單的入門範例。

擷取網路攝影機串流影像

在準備擷取攝影機的影像之前,要先呼叫 cv2.VideoCapture 建立一個 VideoCapture 物件,這個 VideoCapture 物件會連接到一隻網路攝影機,我們可以靠著它的參數來指定要使用那一隻攝影機(0 代表第一隻、1 代表第二隻)。

建立好 VideoCapture 物件之後,就可以使用它的 read 函數來擷取一張張連續的畫面影像了,以下是一個即時擷取與顯示畫面的範例:

import cv2

# 選擇第二隻攝影機
cap = cv2.VideoCapture(1)

while(True):
  # 從攝影機擷取一張影像
  ret, frame = cap.read()

  # 顯示圖片
  cv2.imshow('frame', frame)

  # 若按下 q 鍵則離開迴圈
  if cv2.waitKey(1) & 0xFF == ord('q'):
    break

# 釋放攝影機
cap.release()

# 關閉所有 OpenCV 視窗
cv2.destroyAllWindows()

在這個無窮迴圈中,每次呼叫 cap.read() 就會讀取一張畫面,其第一個傳回值 ret代表成功與否(True 代表成功,False 代表失敗),而第二個傳回值 frame 就是攝影機的單張畫面。

在某些情況下網路攝影機可能不會自動打開,這樣的程式執行後就會出錯,若遇到這種狀況的話可以用 cap.isOpened() 檢查攝影機是否有啟動,若沒有啟動則呼叫 cap.open() 啟動它。

將畫面擷取下來之後,馬上使用 cv2.imshow 來顯示,接著再擷取下一張,這樣執行起來就會是串流影像的效果。

OpenCV 擷取網路攝影機影像

串流影像處理

如果要即時處理從網路攝影機擷取的串流影像,就把處理圖片的程式碼放在這個迴圈中即可,例如將彩色的 RGB 圖片轉為灰階:

import cv2

cap = cv2.VideoCapture(1)
while(True):
  ret, frame = cap.read()

  # 將圖片轉為灰階
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

  cv2.imshow('frame', gray)

  if cv2.waitKey(1) & 0xFF == ord('q'):
    break

cap.release()
cv2.destroyAllWindows()

執行後的畫面會像這樣:

灰階影像

除了簡單的灰階處理,我們也可以加入各式各樣的影像處理演算法(例如物件偵測等),實作出各種應用。

影片相關資訊

我們可以透過 cap.get(propId) 來取得影片的一些資訊,其中 propId 是屬性的 ID,可用的值是從 018,詳細的說明可參考 OpenCV 的文件

以下是查詢畫面大小以及編碼方式的範例:

import cv2

cap = cv2.VideoCapture(1)

# 解析 Fourcc 格式資料的函數
def decode_fourcc(v):
  v = int(v)
  return "".join([chr((v >> 8 * i) & 0xFF) for i in range(4)])

# 取得影像的尺寸大小
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
print("Image Size: %d x %d" % (width, height))

# 取得 Codec 名稱
fourcc = cap.get(cv2.CAP_PROP_FOURCC)
codec = decode_fourcc(fourcc)
print("Codec: " + codec)

cap.release()

執行後的輸出會類似這樣:

Image Size: 640 x 480
Codec: YUYV

若要更改這些影片的設定值,可以使用 cap.set(propId, value) 這個函數,以下是更改攝影機畫面解析度的範例:

import cv2

cap = cv2.VideoCapture(1)

# 設定影像的尺寸大小
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 960)

while(True):
  ret, frame = cap.read()
  cv2.imshow('frame', frame)
  if cv2.waitKey(1) & 0xFF == ord('q'):
    break

cap.release()
cv2.destroyAllWindows()

執行後,畫面就會變成 1280×960 的解析度:

1280×960 解析度畫面

讀取影片檔案

OpenCV 除了從網路攝影機擷取即時的影像之外,也可以從儲存於硬碟中的影片檔案來讀取影像畫面,進行各種處理,而使用方式跟上面網路攝影機的例子都差不多,只是在建立 VideoCapture 物件時,要指定影片檔案的位置。

import cv2

# 開啟影片檔案
cap = cv2.VideoCapture('my_video.avi')

# 以迴圈從影片檔案讀取影格,並顯示出來
while(cap.isOpened()):
  ret, frame = cap.read()

  cv2.imshow('frame',frame)
  if cv2.waitKey(1) & 0xFF == ord('q'):
    break

cap.release()
cv2.destroyAllWindows()

這裡我拿一個從 YouTube 上面下載的卡通影片作為示範:

讀取影片檔案

寫入影片檔案

如果想要將網路攝影機所擷取到的串流影像儲存下來,最簡單的方就是呼叫 cv2.imwrite 將每一張畫面都儲存成個別的圖片檔,這種方式就跟一般圖片的處理方式相同。

另一種儲存串流影像的方式就是使用 VideoWriter 輸出成影片檔案(例如 MP4、AVI 等格式的影片),使用時先建立一個 VideoWriter 物件,並指定影片的輸出檔案名稱、解析度、FPS 值以及編碼格式,再將擷取到的每張畫面依序寫入即可,以下是一個簡單的範例:

import cv2

cap = cv2.VideoCapture(1)

# 設定擷取影像的尺寸大小
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 360)

# 使用 XVID 編碼
fourcc = cv2.VideoWriter_fourcc(*'XVID')

# 建立 VideoWriter 物件,輸出影片至 output.avi
# FPS 值為 20.0,解析度為 640x360
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640, 360))

while(cap.isOpened()):
  ret, frame = cap.read()
  if ret == True:
    # 寫入影格
    out.write(frame)

    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
      break
  else:
    break

# 釋放所有資源
cap.release()
out.release()
cv2.destroyAllWindows()

這個程式執行之後,就會將擷取到的串流影像以 XVID 編碼寫入 output.avi 中。

不同的平台會支援不同的編碼格式,影片常見的編碼格式有:DIVXXVIDMJPGX264WMV1WMV2 等,使用時可以自己嘗試看看。

參考資料:OpenCV