# Python code for Background subtraction using OpenCV
import numpy as np
import cv2
from skimage.measure import label, regionprops
from utils_ import resize
import os
from config import configs
[docs]class BG_subtractor():
"""
A class to perform background subtraction on videos
based on opencv implementation.
...
Attributes
----------
history : int
Number of frames to calculate the background from.
thresh : int
Threshold on the squared distance between the pixel
and the sample to decide whether a pixel is close to that
sample. This parameter does not affect the background
update.
shadows : bool
whether to detect the shadows or not.
fgbg : class instance
The background subtraction object of type KNN
bg : numpy array
The current calculated background
kernel : numpy array
The kernel array to do erosion process on the resulting foreground
Methods
-------
bg_substract(numpy array) -> numpy array
Process a new frame to find the foreground
get_big_objects(numpy array,numpy array) -> (numpy array,list)
Process a foreground with its frame to get the group
of the different background objects.
"""
def __init__(self,bg,config=configs()):
"""
Parameters
----------
bg : numpy array
The background object for the first time
config : config instance
A class instance of all the configuration parameters
"""
self.history = config.bgs_history
self.thresh = config.bgs_threshold
self.shadows = config.bgs_shadows
self.min_area= config.bgs_min_area
self.fgbg = cv2.createBackgroundSubtractorKNN(self.history,self.thresh,self.shadows)
self.bg = bg.copy()
kernel_size = config.bgs_erosion_size
self.kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(kernel_size,kernel_size))
[docs] def bg_substract(self,frame):
"""Find out the background and foreground and post process them
It applies the background subtraction object several times on
the background to focus more on the last changes. Then it erode
the resulting foreground.
Parameters
----------
frame : numpy array
The image whose foreground should be found.
Returns
------
numpy array
The foreground of the frame as grayscale image
"""
# bg is the old background
# frame is the image
#TODO use class methods instead
out = np.array([])
frame_rate ,bg_rate = 0.3, 0.5
for _ in range(4):
# 4 times to learn bg more
fgmask = self.fgbg.apply(self.bg,out,learningRate = bg_rate)
fgmask = self.fgbg.apply(frame,out,learningRate = frame_rate)
#_,fgmask = cv2.threshold(fgmask,254,255,cv2.THRESH_BINARY)
fgmask = cv2.erode(fgmask,self.kernel,iterations = 1)
#fgmask = cv2.filter2D(fgmask,-1,smoothing_kernel)
#fgmask = cv2.medianBlur(fgmask,5)
if self.shadows:
fgmask[fgmask<255] = 0
self.bg = self.fgbg.getBackgroundImage()
return fgmask
[docs] def get_big_objects(self,fg_mask,frame):
"""Find the foreground objects based on the foreground image
Parameters
----------
fg_mask : numpy array
The forground as grayscale image
frame : numpy array
The input image related to the foreground
Returns
-------
(numpy array, list)
A tuple of a new foreground image, and list of foreground
objects.
"""
label_image = label(fg_mask)
regs_str = regionprops(label_image,frame)
new_regions = []
for r in regs_str:
if r.area < self.min_area:
fg_mask[r.slice] = 0
elif r.extent < 0.1:
fg_mask[r.slice] = 0
else:
new_regions.append(r)
return fg_mask,new_regions
if __name__ == '__main__':
cap = cv2.VideoCapture(os.path.join(configs.cwd,'model','sample.mp4'))
ret, bg = cap.read()
frame_id = 1
cap.set(1, frame_id-1)
BG_s = BG_subtractor(bg)
ret, frame = cap.read()
while ret:
frame_id += 1
I_com = BG_s.bg_substract(frame)
cv2.imshow('fgmask', resize(I_com,0.2))
k = cv2.waitKey(30) & 0xff
#prv_regions = []
if k == 27:
break
ret, frame = cap.read()
cap.release()
cv2.destroyAllWindows()