這裡示範如何使用 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()
將監視攝影機的影片輸入進去之後,就會自動篩選出有偵測到物體移動的畫面,輸出至指定的目錄,讓程式先篩選過之後,人再來看的話,就會方便很多。