Skip to content

Instantly share code, notes, and snippets.

@Nico769
Last active October 8, 2022 15:40
Show Gist options
  • Save Nico769/8376860b2677c74bbd46e53da4ef85f0 to your computer and use it in GitHub Desktop.
Save Nico769/8376860b2677c74bbd46e53da4ef85f0 to your computer and use it in GitHub Desktop.
My implementation of Simple Frame Differencing, Naive Mean Background Model and EigenBackground Model for the task of background substraction in CV.
import numpy as np
import cv2 as cv
from sklearn.decomposition import PCA
# Instead of using opencv, we can use numpy
# def global_grayscale_threshold(input_image, threshold):
# return np.where(input_image > threshold, 255, 0).astype(np.uint8)
def simple_frame_differencing(curr_frame, bg_frame, threshold):
abs_difference = cv.absdiff(curr_frame, bg_frame)
_, thres_frame = cv.threshold(abs_difference, threshold, 255, cv.THRESH_BINARY)
return thres_frame.astype(np.uint8)
# Instead of using opencv, we can use numpy
# abs_difference = np.abs(curr_frame - bg_frame)
# return global_grayscale_threshold(abs_difference, threshold)
def naive_mean_background_model(curr_frame, past_frames, threshold):
mean_bg_model = np.ceil(np.sum(past_frames, axis=0) / len(past_frames))
return simple_frame_differencing(curr_frame, mean_bg_model, threshold)
# Instead of using opencv, we can use numpy
# return global_grayscale_threshold(np.abs(curr_frame - mean_bg_model), threshold)
def eigenbackground_model(curr_frame, past_frames, threshold, capture_properties):
n_past_frames_stored = len(past_frames)
flattened_frame_size = capture_properties[cv.CAP_PROP_FRAME_WIDTH] * capture_properties[cv.CAP_PROP_FRAME_HEIGHT]
# Eigenbackground training
input_mat = np.array([np.ravel(frame) for frame in past_frames], dtype=np.float64)
assert input_mat.shape == (n_past_frames_stored, flattened_frame_size)
pca = PCA(n_components=n_past_frames_stored // 2, random_state=1)
pca.fit(input_mat)
# Eigenbackground testing
flattened_frame = np.ravel(curr_frame).reshape(1, -1)
assert flattened_frame.shape == (1, flattened_frame_size)
projected_frame = pca.transform(flattened_frame)
flat_eigenbackground_model = pca.inverse_transform(projected_frame)
assert flat_eigenbackground_model.shape == (1, flattened_frame_size)
bg_model = flat_eigenbackground_model.reshape(capture_properties[cv.CAP_PROP_FRAME_HEIGHT],
capture_properties[cv.CAP_PROP_FRAME_WIDTH])
return simple_frame_differencing(curr_frame, bg_model, threshold)
def preprocess_frames(frames: list) -> list:
gray_frames = [cv.cvtColor(frame, cv.COLOR_BGR2GRAY) for frame in frames]
# Works ok even with a 5x5 box filter
# return [cv.blur(frame, (5, 5)).astype(np.float64) for frame in gray_frames]
return [cv.GaussianBlur(frame, (19, 19), 3).astype(np.float64) for frame in gray_frames]
def store_n_preprocessed_frames(video_cap, n):
ret_n_frames = [video_cap.read() for _ in range(n)]
# TODO Ignore the true/false read()'s flag for now
return preprocess_frames([ret_frame_tup[1] for ret_frame_tup in ret_n_frames])
def set_cap_props(video_cap, props_dict):
for k, v in props_dict.items():
video_cap.set(k, v)
return video_cap
def erode_and_dilate(foreground_mask):
er_elem = cv.getStructuringElement(cv.MORPH_RECT, (11, 11))
#return cv.erode(foreground_mask, er_elem)
eroded = cv.erode(foreground_mask, er_elem)
dil_elem = cv.getStructuringElement(cv.MORPH_RECT, (55, 55))
return cv.dilate(eroded, dil_elem)
if __name__ == '__main__':
cap = cv.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
exit()
cap_props = {cv.CAP_PROP_FRAME_WIDTH: 320,
cv.CAP_PROP_FRAME_HEIGHT: 240,
cv.CAP_PROP_FPS: 30}
cap = set_cap_props(cap, cap_props)
# Tune the number of frames to store for the mean background model and the eigenbackground model
n_past_frames_to_store = 5
while True:
# + 1 to account for the frame at the current time instant t
captured_frames = store_n_preprocessed_frames(cap, n_past_frames_to_store + 1)
frame_diff_threshold_val = 80
mean_bg_threshold_val = 80
eigen_bg_threshold_val = 15
# Take the frame at the current time instant t
current_frame = captured_frames[-1]
# Take all frames starting from the frame at time instant t - 1
prev_frames = captured_frames[:-1]
# Frame differencing takes the frame at the current time instant t and the frame at t-1
#frame_diff_mask = simple_frame_differencing(current_frame, prev_frames[-1], frame_diff_threshold_val)
#naive_mean_bg_mask = naive_mean_background_model(current_frame, prev_frames, mean_bg_threshold_val)
eigen_bg_mask = eigenbackground_model(current_frame, prev_frames, eigen_bg_threshold_val, cap_props)
#postproc_mean_bg_mask = erode_and_dilate(eigen_bg_mask)
# Display several foreground masks
frame_diff_window_name = "Simple Frame Differencing"
naive_mean_bg_window_name = "Naive Mean Background Model"
eigen_bg_window_name = "EigenBackground Model"
#cv.imshow(frame_diff_window_name, frame_diff_mask)
#cv.imshow(naive_mean_bg_window_name, naive_mean_bg_mask)
#cv.imshow("Post-processed mean bg model", postproc_mean_bg_mask)
cv.imshow(eigen_bg_window_name, eigen_bg_mask)
#cv.imshow("Post-processed EigenBackground Model", postproc_mean_bg_mask)
#frame_diff_window_rect = cv.getWindowImageRect(frame_diff_window_name)
#naive_mean_bg_window_rect = cv.getWindowImageRect(naive_mean_bg_window_name)
#cv.moveWindow(naive_mean_bg_window_name, frame_diff_window_rect[0] + 400, frame_diff_window_rect[1])
#cv.moveWindow(eigen_bg_window_name, naive_mean_bg_window_rect[0] + 400, naive_mean_bg_window_rect[1])
if cv.waitKey(1) == ord('q'):
break
# When everything done, release the capture
cap.release()
cv.destroyAllWindows()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment