diff --git a/examples/motion-heatmap/README.md b/examples/motion-heatmap/README.md index 695e3d0..d67efd0 100644 --- a/examples/motion-heatmap/README.md +++ b/examples/motion-heatmap/README.md @@ -12,13 +12,16 @@ This sample application is useful to see movement patterns over time. For exampl * Python\* 2.7 or greater * OpenCV version 3.3.0 or greater * The vtest.avi video from https://github.com/opencv/opencv/blob/master/samples/data/vtest.avi + * Directly use camera input `cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)` ## Setup 1. You need the extra modules installed for the MOG background subtractor. This tutorial was tested on Windows\*, and the easiest way to install it was using: ``` pip install opencv-contrib-python ``` -2. Download the vtest.avi video from https://github.com/opencv/opencv/blob/master/samples/data/vtest.avi and put it in the same folder as the python script. +`NOTE`: Not required to install opencv-contrib-python, if using opencv-python v4.2.0. + +2. Download the vtest.avi video from https://github.com/opencv/opencv/blob/master/samples/data/vtest.avi and put it in the same folder as the python script. `cap = cv2.VideoCapture('vtest.avi', cv2.CAP_DSHOW)` 3. Run the python script. You should see a diff-overlay.jpg when it's done. ![](images/diff-overlay.jpg) @@ -33,11 +36,15 @@ Note: the docs are out of date, and the propoer way to initialize is ``` cv2.bgsegm.createBackgroundSubtractorMOG() ``` +`for opencv-python v4.2.0` +``` +cv2.createBackgroundSubtractorMOG2() +``` * cv2.threshold() - https://docs.opencv.org/3.3.1/d7/d4d/tutorial_py_thresholding.html * cv2.add() - https://docs.opencv.org/3.2.0/d0/d86/tutorial_py_image_arithmetics.html * cv2.applyColorMap() - https://docs.opencv.org/3.0-beta/modules/imgproc/doc/colormaps.html * cv2.addWeighted() - https://docs.opencv.org/3.2.0/d0/d86/tutorial_py_image_arithmetics.html -The application takes each frame and first applies background subtraction using the cv2.bgsegm.createBackgroundSubtractorMOG() object to create a mask. A threshold is then applied to the mask to remove small amounts of movement, and also to set the accumulation value for each iteration. The result of the threshold is added to an accumulation image (one that starts out at all zero and gets added to each iteration without removing anything), which is what records the motion. At the very end, a color map is applied to the accumulated image so it's easier to see the motion. This colored imaged is then combined with a copy of the first frame using cv2.addWeighted to accomplish the overlay. +The application takes each frame and first applies background subtraction using the cv2.createBackgroundSubtractorMOG2() object to create a mask. A threshold is then applied to the mask to remove small amounts of movement, and also to set the accumulation value for each iteration. The result of the threshold is added to an accumulation image (one that starts out at all zero and gets added to each iteration without removing anything), which is what records the motion. At the very end, a color map is applied to the accumulated image so it's easier to see the motion. This colored imaged is then combined with a copy of the first frame using cv2.addWeighted to accomplish the overlay. IMPORTANT NOTICE: This software is sample software. It is not designed or intended for use in any medical, life-saving or life-sustaining systems, transportation systems, nuclear systems, or for any other mission-critical application in which the failure of the system could lead to critical injury or death. The software may not be fully tested and may contain bugs or errors; it may not be intended or suitable for commercial release. No regulatory approvals for the software have been obtained, and therefore software may not be certified for use in certain countries or environments. diff --git a/examples/motion-heatmap/motion-heatmap.py b/examples/motion-heatmap/motion-heatmap.py index 613a623..d6624bd 100644 --- a/examples/motion-heatmap/motion-heatmap.py +++ b/examples/motion-heatmap/motion-heatmap.py @@ -1,81 +1,81 @@ -''' +''' Copyright (c) 2017 Intel Corporation. Licensed under the MIT license. See LICENSE file in the project root for full license information. ''' -import numpy as np -import cv2 import copy +import cv2 +import numpy as np -def main(): - cap = cv2.VideoCapture('vtest.avi') - # pip install opencv-contrib-python - fgbg = cv2.bgsegm.createBackgroundSubtractorMOG() - # number of frames is a variable for development purposes, you can change the for loop to a while(cap.isOpened()) instead to go through the whole video - num_frames = 350 +def main(): + """ + This method is used to see movement patterns over time. It creates a heatmap of the movements. + For example, it could be used to see the usage of entrances to a factory floor over time, + or patterns of shoppers in a store. + """ + cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) + out = cv2.VideoWriter('motion_heatmap_output.avi', cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'), 30, (int(cap.get(3)), int(cap.get(4)))) + # out = cv2.VideoWriter('output.avi', cv2.VideoWriter_fourcc(*'XVID'), 40, (int(cap.get(3)), int(cap.get(4)))) # Also works + fgbg = cv2.createBackgroundSubtractorMOG2() first_iteration_indicator = 1 - for i in range(0, num_frames): - ''' - There are some important reasons this if statement exists: - -in the first run there is no previous frame, so this accounts for that - -the first frame is saved to be used for the overlay after the accumulation has occurred - -the height and width of the video are used to create an empty image for accumulation (accum_image) - ''' - if (first_iteration_indicator == 1): - ret, frame = cap.read() - first_frame = copy.deepcopy(frame) - gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - height, width = gray.shape[:2] - accum_image = np.zeros((height, width), np.uint8) - first_iteration_indicator = 0 - else: - ret, frame = cap.read() # read a frame - gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # convert to grayscale - - fgmask = fgbg.apply(gray) # remove the background - # for testing purposes, show the result of the background subtraction - # cv2.imshow('diff-bkgnd-frame', fgmask) - - # apply a binary threshold only keeping pixels above thresh and setting the result to maxValue. If you want - # motion to be picked up more, increase the value of maxValue. To pick up the least amount of motion over time, set maxValue = 1 - thresh = 2 - maxValue = 2 - ret, th1 = cv2.threshold(fgmask, thresh, maxValue, cv2.THRESH_BINARY) - # for testing purposes, show the threshold image - # cv2.imwrite('diff-th1.jpg', th1) - - # add to the accumulated image - accum_image = cv2.add(accum_image, th1) - # for testing purposes, show the accumulated image - # cv2.imwrite('diff-accum.jpg', accum_image) - - # for testing purposes, control frame by frame - # raw_input("press any key to continue") - - # for testing purposes, show the current frame - # cv2.imshow('frame', gray) + while cap.isOpened(): + ret, frame = cap.read() + if ret: + ''' + There are some important reasons this if statement exists: + -in the first run there is no previous frame, so this accounts for that + -the first frame is saved to be used for the overlay after the accumulation has occurred + -the height and width of the video are used to create an empty image for accumulation (accum_image) + ''' + + if first_iteration_indicator == 1: + first_frame = copy.deepcopy(frame) + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + height, width = gray.shape[:2] + accum_image = np.zeros((height, width), np.uint8) + first_iteration_indicator = 0 + else: + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # convert to grayscale + fgmask = fgbg.apply(gray) # remove the background + cv2.imshow('diff-bkgnd-frame', fgmask) # result of the background subtraction + + # apply a binary threshold only keeping pixels above thresh and setting the result to maxValue. + # If you want motion to be picked up more, increase the value of maxValue. + # To pick up the least amount of motion over time, set maxValue = 1 + thresh = 1 + max_value = 5 + ret, th_img = cv2.threshold(fgmask, thresh, max_value, cv2.THRESH_BINARY) + + # add to the accumulated image + accum_image = cv2.add(accum_image, th_img) + cv2.imshow('diff-accum', accum_image) # accumulated frame + + cv2.imshow('frame', gray) # current frame + + # apply a color map + # COLORMAP_PINK also works well, COLORMAP_BONE is acceptable if the background is dark + color_image = cv2.applyColorMap(accum_image, cv2.COLORMAP_INFERNO) + cv2.imshow('diff-color', color_image) # colorMap frame + + # overlay the color mapped image to the first frame + result_overlay = cv2.addWeighted(first_frame, 0.7, color_image, 0.7, 0) + cv2.imshow('diff-overlay', result_overlay) # final overlay frame + cv2.imwrite('diff-overlay.png', result_overlay) # final overlay image + out.write(result_overlay) + else: + cap.set(cv2.CAP_PROP_POS_FRAMES, 0) - if cv2.waitKey(1) & 0xFF == ord('q'): + ch_ = cv2.waitKey(1) + if ch_ == 27 or ch_ == ord('q') or ch_ == ord('Q'): break - # apply a color map - # COLORMAP_PINK also works well, COLORMAP_BONE is acceptable if the background is dark - color_image = im_color = cv2.applyColorMap(accum_image, cv2.COLORMAP_HOT) - # for testing purposes, show the colorMap image - # cv2.imwrite('diff-color.jpg', color_image) - - # overlay the color mapped image to the first frame - result_overlay = cv2.addWeighted(first_frame, 0.7, color_image, 0.7, 0) - - # save the final overlay image - cv2.imwrite('diff-overlay.jpg', result_overlay) - - # cleanup cap.release() + out.release() cv2.destroyAllWindows() -if __name__=='__main__': - main() \ No newline at end of file + +if __name__ == '__main__': + main()