這裡示範如何使用 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()
將監視攝影機的影片輸入進去之後,就會自動篩選出有偵測到物體移動的畫面,輸出至指定的目錄,讓程式先篩選過之後,人再來看的話,就會方便很多。
參考資料:pyimagesearch、Robin David、Matthias Stein
JIUCAIJIUCAI
新手~学习起来~~~~~
Kevin
存好的影片出現 0xc10100be 無法撥放此檔案,有甚麼解決的辦法嗎
michael
跟你遇到同樣的問題,我是參考這篇解決的
https://www.learnopencv.com/read-write-and-display-a-video-using-opencv-cpp-python/
Proms
謝謝您的教學!
不知是版本的關係,還是其它原因?
我需要把以下這行的 cntImg, cnts, _ 改成 cnts, cntImg 才能讓程式執行
cntImg, cnts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
Kai
是版本差異,我使用OpenCV 2.4,保留 cnts, _ 才可跑這程式窩~~
allen
cv2有再更新版本,要去官網看就知道改哪了~
Ken
run程式的時候出現
error: OpenCV(4.1.0) C:\projects\opencv-python\opencv\modules\core\src\matrix.cpp:757: error: (-215:Assertion failed) dims 0 in function ‘cv::Mat::locateROI’
是版本不符嗎?
fox
我也是不知道是版本的關係,還是其它原因?
需要把cap = cv2.VideoCapture(0)換成
camera_number = 0
cap = cv2.VideoCapture( camera_number + cv2.CAP_DSHOW)
才能執行
Show
不好意思我改了跟您@fox一樣卻無法執行,也是跑出跟 @Ken大大一樣,是不是還缺了什麼?!
fox
Proms
謝謝您的教學!
不知是版本的關係,還是其它原因?
我需要把以下這行的 cntImg, cnts, _ 改成 cnts, cntImg 才能讓程式執行
cntImg, cnts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
這個也要改喔!
Sam
謝謝您的教學!
但是我有一個疑問,當我執行此代碼時出現了錯誤 cntImg, cnts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
ValueError: not enough values to unpack (expected 3, got 2)
Roy
請問有完整的原始碼檔案嗎?
還踏入不了Python,無法執行,
有心想學,但是力不從心。
disconct
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 cntimg:
# 忽略太小的區域
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, cntimg, -1, (0, 255, 255), 2)
# 顯示偵測結果影像
cv2.imshow('frame', frame)
if cv2.waitKey(1) == 27:
break
# 更新平均影像
cv2.accumulateWeighted(blur, avg_float, 0.01)
avg = cv2.convertScaleAbs(avg_float)
cap.release()
cv2.destroyAllWindows()
Renny
您好:
請問在”快速篩選監視器影像”可以設定成1分鐘內只截一張圖嗎?
完全沒有學過程式,在網路搜尋到您的文章才開始使用opencv,
但試了兩天實在試不出來一分鐘只截一張圖,所以才來麻煩您。
sakura
請問大大,
是否可以提供資料影片檔呢?謝謝><