程式設計

Python 與 OpenCV 實作移動偵測程式教學,打造智慧型監視器

這裡示範如何使用 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()

將監視攝影機的影片輸入進去之後,就會自動篩選出有偵測到物體移動的畫面,輸出至指定的目錄,讓程式先篩選過之後,人再來看的話,就會方便很多。

篩選過的影像

參考資料:pyimagesearchRobin DavidMatthias Stein

G. T. Wang

個人使用 Linux 經驗長達十餘年,樂於分享各種自由軟體技術與實作文章。

Share
Published by
G. T. Wang
標籤: OpenCVPython

Recent Posts

光陽 KYMCO GP 125 機車接電發動、更換電瓶記錄

本篇記錄我的光陽 KYMCO ...

1 年 ago

[開箱] YubiKey 5C NFC 實體金鑰

本篇是 YubiKey 5C ...

2 年 ago

[DIY] 自製竹火把

本篇記錄我拿竹子加上過期的苦茶...

2 年 ago