Skip to content

Commit 4665bf8

Browse files
committed
files added
1 parent 1a3e374 commit 4665bf8

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

examples/motion-heatmap/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Motion Heatmap
2+
3+
4+
5+
## What you’ll learn
6+
* background subtraction
7+
* application of a threshold
8+
* accumulation of changed pixels over time
9+
* add a color/heat map
10+
11+
## Gather your materials
12+
* Python 2.7 or greater
13+
* OpenCV version 3.3.0 or greater
14+
* The vtest.avi video from https://github.com/opencv/opencv/blob/master/samples/data/vtest.avi
15+
16+
## Setup
17+
1. 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.
18+
2.
19+
20+
Original image:
21+
[](/images/gauge-1.jpg)
22+
23+
## Get the Code
24+
The application can be downloaded as a .zip at the end of this article.
25+
26+
## How it works
27+
The main functions used in OpenCV are HoughCircles (to detect the outline of the gauge and center point) and HoughLines (to detect the dial).
28+
29+
Basic filtering is done as follows:
30+
For cirles (this happens in calibrate_gauge() )
31+
* only return circles from HoughCircles that are within reasonable range of the image height (this assumes the gauge takes up most of the view)
32+
* average the resulting circles and use the average for the center point and radius
33+
For lines (this happens in get_current_value() )
34+
* apply a threshold using cv2.threshold. cv2.THRESH_BINARY_INV with threshold of 175 and maxValue of 255 work fine
35+
* remove all lines outside a given radius
36+
* check if a line is within an acceptable range of the radius
37+
* use the first acceptable line as the dial
38+
39+
There is a considerable amount of triginomotry involved to create the calibration image, mainly sin and cos to plot the calibration image lines and arctan to get the angle
40+
of the dial. This approach sets 0/360 to be the -y axis (if the image has a cartesian grid in the middle) and it goes clock-wise. There is a slight
41+
modification to make the 0/360 degrees be at the -y axis, by an addition (i+9) in the calculation of p_text[i][j]. Without this +9 the 0/360 point would be on the +x axis. So this
42+
implementation assumes the gauge is aligned in the image, but it can be adjusted by changing the value of 9 to something else.
43+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import numpy as np
2+
import cv2
3+
import copy
4+
5+
cap = cv2.VideoCapture('vtest.avi')
6+
#pip install opencv-contrib-python
7+
fgbg = cv2.bgsegm.createBackgroundSubtractorMOG()
8+
9+
#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
10+
num_frames = 350
11+
12+
first_iteration_indicator = 1
13+
for i in range(0,num_frames):
14+
'''
15+
There are some important reasons this if statement exists:
16+
-in the first run there is no previous frame, so this accounts for that
17+
-the first frame is saved to be used for the overlay after the accumulation has occurred
18+
-the height and width of the video are used to create an empty image for accumulation (accum_image)
19+
'''
20+
if (first_iteration_indicator == 1):
21+
ret, frame = cap.read()
22+
first_frame = copy.deepcopy(frame)
23+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
24+
height, width = gray.shape[:2]
25+
accum_image = np.zeros((height,width), np.uint8)
26+
first_iteration_indicator = 0
27+
previous_frame = copy.deepcopy(gray)
28+
else:
29+
ret, frame = cap.read() #read a frame
30+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #convert to grayscale
31+
32+
fgmask = fgbg.apply(gray) #remove the background
33+
34+
#for testing purposes, show the result of the background subtraction
35+
# cv2.imshow('diff-bkgnd-frame', fgmask)
36+
37+
# apply a binary threshold only keeping pixels above thresh and setting the result to maxValue. If you want
38+
# motion to be picked up more, increase the value of maxValue. To pick up the least amount of motion over time, set maxValue = 1
39+
thresh = 2
40+
maxValue = 2
41+
ret, th1 = cv2.threshold(fgmask, 2, 2, cv2.THRESH_BINARY)
42+
#for testing purposes, show the threshold image
43+
#cv2.imwrite('diff-th1.jpg', th1)
44+
45+
#add to the accumulated image
46+
accum_image = cv2.add(accum_image, th1)
47+
#for testing purposes, show the accumulated image
48+
#cv2.imwrite('diff-accum.jpg', accum_image)
49+
50+
#for testing purposes, control frame by frame
51+
# raw_input("press any key to continue")
52+
53+
previous_frame = copy.deepcopy(gray)
54+
55+
#for testing purposes, show the current frame
56+
# cv2.imshow('frame', gray)
57+
58+
if cv2.waitKey(1) & 0xFF == ord('q'):
59+
break
60+
61+
previous_frame = copy.deepcopy(gray)
62+
63+
# apply a color map
64+
# COLORMAP_PINK also works well, COLORMAP_BONE is acceptable if the background is dark
65+
color_image = im_color = cv2.applyColorMap(accum_image, cv2.COLORMAP_HOT)
66+
# for testing purposes, show the colorMap image
67+
#cv2.imwrite('diff-color.jpg', color_image)
68+
69+
#overlay the color mapped image to the first frame
70+
result_overlay = cv2.addWeighted(first_frame, 0.7, color_image, 0.7, 0)
71+
72+
#save the final overlay image
73+
cv2.imwrite('diff-overlay.jpg', result_overlay)
74+
75+
#cleanup
76+
cap.release()
77+
cv2.destroyAllWindows()

0 commit comments

Comments
 (0)