Last active
October 8, 2022 15:40
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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