這裡示範如何使用 Python 與 OpenCV 實作移動偵測程式,分析攝影機影片,自動挑選出有物體移動的畫面。
OpenCV 是一個很好用的影像處理函式庫,裡面有非常多在影像處理上常會用到的工具函數,我們只需要拿幾個簡單的函數組合起來,就可以打造一個效果還不錯的智慧型監視器,自動且即時的分析攝影機的影像,當偵測到有人或物體在移動時,讓程式自動觸發某些動作(例如送出通知的 Email 等),以下是幾個簡單的實作範例。
攝影機畫面移動偵測
我們先示範從網路攝影機擷取即時的畫面,偵測畫面中移動的物體,偵測物體的方法是先以移動平均(moving average)演算法計算出背景影像,然後將每幅影格都跟這幅背景影像比較,只要有差異的話,就判斷是有物體在移動。
而在影像處理的過程,有加入一些模糊化、擴張、縮減等處理,目的就是要減少雜訊干擾、突顯主體,這些步驟與其中的參數都要自己調整。以下是完整的程式碼:
import cv2
import numpy as np
# 開啟網路攝影機
cap = cv2.VideoCapture(0)
# 設定影像尺寸
width = 1280
height = 960
# 設定擷取影像的尺寸大小
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
# 計算畫面面積
area = width * height
# 初始化平均影像
ret, frame = cap.read()
avg = cv2.blur(frame, (4, 4))
avg_float = np.float32(avg)
while(cap.isOpened()):
# 讀取一幅影格
ret, frame = cap.read()
# 若讀取至影片結尾,則跳出
if ret == False:
break
# 模糊處理
blur = cv2.blur(frame, (4, 4))
# 計算目前影格與平均影像的差異值
diff = cv2.absdiff(avg, blur)
# 將圖片轉為灰階
gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
# 篩選出變動程度大於門檻值的區域
ret, thresh = cv2.threshold(gray, 25, 255, cv2.THRESH_BINARY)
# 使用型態轉換函數去除雜訊
kernel = np.ones((5, 5), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
# 產生等高線
cntImg, cnts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
# 忽略太小的區域
if cv2.contourArea(c) < 2500:
continue
# 偵測到物體,可以自己加上處理的程式碼在這裡...
# 計算等高線的外框範圍
(x, y, w, h) = cv2.boundingRect(c)
# 畫出外框
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
# 畫出等高線(除錯用)
cv2.drawContours(frame, cnts, -1, (0, 255, 255), 2)
# 顯示偵測結果影像
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 更新平均影像
cv2.accumulateWeighted(blur, avg_float, 0.01)
avg = cv2.convertScaleAbs(avg_float)
cap.release()
cv2.destroyAllWindows()
這個程式執行之後,就會開啟一個視窗,顯示即時的物體偵測結果。
![]()
儲存畫面
如果想要把偵測的結果儲存下來,可以參考 OpenCV 的圖片寫入與 OpenCV 影片寫入教學,把想要紀錄的畫面儲存下來。
以下示範把偵測的結果寫入影片檔。在一開始的地方先設定輸出檔案:
# 使用 XVID 編碼
fourcc = cv2.VideoWriter_fourcc(*'XVID')
# 建立 VideoWriter 物件,輸出影片至 output.avi
out = cv2.VideoWriter('output.avi', fourcc, 16.0, (width, height))
然後在顯示偵測結果影像的地方,把結果寫入檔案:
# 寫入影格
out.write(frame)
這樣就可以產生偵測結果的影片了。
若只想要儲存有物體移動的畫面,只要把輸出影片的程式碼移動到有偵測到物體的地方就可以了,上面這段程式碼只要稍加修改,就可以變化出許多應用,例如當偵測到物體移動時,送出 Email 通知管理者等。
快速篩選監視器影像
現在許多社區或大樓都會裝設監視攝影機,若發生特別的事件時(例如遭小偷)就可以調閱這些攝影機的影片,協助警方辦案,而這類的攝影機所錄製下來的影片中,通常大部分都是沒有人的畫面,可能一小時中只有幾十秒是真的有拍到人,其餘都是空的。
最常遇到的問題就是如何在這些大量監視攝影機的影片中,找出有人或是物體移動的畫面,在以前這種狀況是真的用人眼把上百小時的影片看完,非常費時又費力。
若遇到這樣的狀況,就可以使用 Python 與 OpenCV 幫助我們快速篩選出有物體在移動的畫面,先把空拍的部份去除,用這種方式來分析影片,可以省下非常大量的時間。
以下是用來篩選大量監視器影像的指令稿,其實整個大架構跟上面的程式差不多,只是畫面來源改從影片檔案讀取,而將有變動的畫面輸出成為單張的圖檔,沒有變動的部分就直接捨棄。
import cv2
import numpy as np
import os
# 影片檔案
videoFile = "my_video.m4v"
# 輸出目錄
outputFolder = "my_output"
# 自動建立目錄
if not os.path.exists(outputFolder):
os.makedirs(outputFolder)
# 開啟影片檔
cap = cv2.VideoCapture(videoFile)
# 取得畫面尺寸
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
# 計算畫面面積
area = width * height
# 初始化平均畫面
ret, frame = cap.read()
avg = cv2.blur(frame, (4, 4))
avg_float = np.float32(avg)
# 輸出圖檔用的計數器
outputCounter = 0
while(cap.isOpened()):
# 讀取一幅影格
ret, frame = cap.read()
# 若讀取至影片結尾,則跳出
if ret == False:
break
# 模糊處理
blur = cv2.blur(frame, (4, 4))
# 計算目前影格與平均影像的差異值
diff = cv2.absdiff(avg, blur)
# 將圖片轉為灰階
gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
# 篩選出變動程度大於門檻值的區域
ret, thresh = cv2.threshold(gray, 25, 255, cv2.THRESH_BINARY)
# 使用型態轉換函數去除雜訊
kernel = np.ones((5, 5), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
# 產生等高線
cntImg, cnts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
hasMotion = False
for c in cnts:
# 忽略太小的區域
if cv2.contourArea(c) < 2500:
continue
hasMotion = True
# 計算等高線的外框範圍
(x, y, w, h) = cv2.boundingRect(c)
# 畫出外框
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
if hasMotion:
# 儲存有變動的影像
cv2.imwrite("%s/output_%04d.jpg" % (outputFolder, outputCounter), frame)
outputCounter += 1
# 更新平均影像
cv2.accumulateWeighted(blur, avg_float, 0.01)
avg = cv2.convertScaleAbs(avg_float)
cap.release()
將監視攝影機的影片輸入進去之後,就會自動篩選出有偵測到物體移動的畫面,輸出至指定的目錄,讓程式先篩選過之後,人再來看的話,就會方便很多。
![]()
