進行運動物體檢測就是將動態的前景從靜態的背景中分離出來。將當前畫面與假設是靜態背景進行比較發現有明顯的變化的區域,就可以認為該區域出現移動的物體。在實際情況中由於光照陰影等因素干擾比較大,通過像素直接進行比較往往很容易造成誤檢。因此有不少算法被開發出來在進行前后景分離的時候對運動和其他因素造成的變動進行區分。opencv中提供了多種背景減除的算法,其中基於高斯混合模型(GMM)的cv2.BackgroundSubtractorMOG()
和cv2.BackgroundSubtractorMOG2()
已經基於貝葉斯模型的cv2.bgsegm.createBackgroundSubtractorGMG()
最為常用。
1) GMM法
GMM進行前后景分離最早是在2001年的文章An improved adaptive background mixture model for real-time tracking with shadow detection
中提出的。其設計思路為:
- 在不知道圖像歷史的時候,假設每個像素點的值都是可以分解為一組adaptive Gaussian。adaptive是由於需要跟隨光照條件的變化而變化。
- 像素值的歷史由一組高斯分布進行建模,包括每個分布的權重。
- 每次新圖像輸入的時候都會用這一組高斯分布進行評估,如果像素匹配上其中一個分布就會認為這個像素屬於背景,而高斯分布的均值和方差等參數會用當前像素的值進行更新。
- 標記為前景的像素通過connected component analysis進行分組。
opencv的cv2.BackgroundSubtractorMOG()
的函數就是對次方法的實現。
基於2004年提出的Improved adaptive Gausian mixture model for background subtractio
和2006年提出的Efficient Adaptive Density Estimation per Image Pixel for the Task of Background Subtraction
算法對上述GMM算法進行改良的就是opencv的cv2.BackgroundSubtractorMOG2()
的函數。主要的提升是對每個像素都選擇合適數量的高斯分布而非原來的全部相同的個數。此外,這個函數還允許是否檢測陰影。
使用方法(以MOG2為例)
import cv2
cam = cv2.VideoCapture(0)
fgbg = cv2.createBackgroundSubtractorMOG()
while cam.isOpened():
ret, frame = cam.read()
if ret:
fgmask = fgbg.apply(frame)
# 通過腐蝕和膨脹過濾一些噪聲
erode = cv2.erode(fgmask, (21, 21), iterations=1)
dilate = cv2.dilate(fgmask, (21, 21), iterations=1)
(_, cnts, _) = cv2.findContours(dilate.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
c_area = cv2.contourArea(c)
if c_area < 1600 or c_area > 16000: # 過濾太小或太大的運動物體,這類誤檢概率比較高
continue
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 2)
cv2.imshow("origin", frame)
if cv2.waitKey(1) == ord('q'):
break
cv2.destroyAllWindows()
2)GMG法
根據2012年的文章Visual Tracking of Human Visitors under Variable-Lighting Conditions for a Responsive Audio Art Installation
, opencv開發了相應的函數cv2.bgsegm.createBackgroundSubtractorGMG()
(好像在3.2中被放到了contrib中,在之前可以直接用cv2.createBackgroundSubtractorGMG()
進行調用)。這個方法默認使用前120張圖片進行背景的建模,並使用概率前景分割算法找到可能的前景(基於貝葉斯推測)。為了更好適應不同光照變化的影響,新的圖片的權重比舊圖片要高。
使用方法
cam = cv2.VideoCapture(0)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (8, 8))
fgbg = cv2.bgsegm.createBackgroundSubtractorGMG(initializationFrames=10)
while cam.isOpened():
ret, frame = cam.read()
if ret:
fgmask = fgbg.apply(frame)
fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel) # 過濾噪聲
(_, cnts, _) = cv2.findContours(dilate.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
c_area = cv2.contourArea(c)
if c_area < 1600 or c_area > 16000: # 過濾太小或太大的運動物體,這類誤檢概率比較高
continue
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 2)
cv2.imshow("origin", frame)
if cv2.waitKey(1) == ord('q'):
break
cv2.destroyAllWindows()
參考:
- https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_video/py_bg_subtraction/py_bg_subtraction.html
- http://android4arduino.com/computer-vision/motion-detection/
- https://www.pyimagesearch.com/2015/05/25/basic-motion-detection-and-tracking-with-python-and-opencv/
- https://docs.opencv.org/3.0-beta/modules/video/doc/motion_analysis_and_object_tracking.html?highlight=createbackgroundsubtractormog