-
Notifications
You must be signed in to change notification settings - Fork 0
/
trailcam_motion.py
170 lines (145 loc) · 6.88 KB
/
trailcam_motion.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""Trailcam motion detection with OpenCV and mask"""
import os
import time
import cv2
import csv
from datetime import datetime
MIN_CONTOUR_AREA = 4000
MASK_BASE_DIR = r"/tmp/plotwatcher_masks/framesNmasks/"
RAWVID_BASE_DIR = r"/tmp/plotwatcher_rawvids/"
MOTION_BASE_DIR = r"/tmp/plotwatcher_motionframes/"
OUTPUT_BASE_DIR = r"/tmp/motionframes_TOSIALIA/"
def main():
"""Entry point."""
# make an output folder
print "processing " + video_path
basename = os.path.basename(video_path)
output_dir = os.path.join(OUTPUT_BASE_DIR, sitedate, os.path.splitext(basename)[0])
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Load video object
video = cv2.VideoCapture(video_path)
last_filter_frame = None
image_index = -1
aoi_mask = cv2.imread(mask_path)
consecutive_start = -1
series_list = []
n_detected = 0
# Number of vid frames
video_length = int(video.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT))
while True:
image_index += 1
valid, frame = video.read()
if not valid:
break
# save first frame to disk (for vid start-time data)
if image_index == 0:
image_path_first = os.path.join(output_dir, basename + '_first.jpg')
cv2.imwrite(image_path_first, frame)
# save last image to disk (for vid end-time data)
if image_index == video_length-1:
image_path_last = os.path.join(output_dir, basename + '_last.jpg')
cv2.imwrite(image_path_last, frame)
masked_frame = cv2.bitwise_and(frame, aoi_mask)
# resize the frame, convert it to grayscale, and blur it
gray_frame = cv2.cvtColor(masked_frame, cv2.COLOR_BGR2GRAY)
filter_frame = cv2.GaussianBlur(gray_frame, (31, 31), 0)
if last_filter_frame is None:
last_filter_frame = filter_frame
delta = cv2.absdiff(last_filter_frame, filter_frame)
thresholded_frame = cv2.threshold(
delta, 25, 255, cv2.THRESH_BINARY)[1]
# dilate the thresholded image to fill in holes, then find contours
# on thresholded image
dilated_frame = cv2.dilate(thresholded_frame, None, iterations=3)
(contour_list, _) = cv2.findContours(
dilated_frame.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# loop over the contours and construct the largest bounding box
detected_motion = False
x, y, w, h = None, None, None, None
for contour in contour_list:
# if the contour is too small, ignore it
if cv2.contourArea(contour) < MIN_CONTOUR_AREA:
continue
if x is None:
# base bounding box
(x, y, w, h) = cv2.boundingRect(contour)
xp = x + w
yp = y + h
else:
# calculate how this bounding box fits in with the global one
(local_x, local_y, local_w, local_h) = cv2.boundingRect(
contour)
local_xp = local_x + local_w
local_yp = local_y + local_h
x = min(x, local_x)
xp = max(xp, local_xp)
y = min(y, local_y)
yp = max(yp, local_yp)
detected_motion = True
if detected_motion:
# recalculate width and height based on BB cooridnates
w = xp - x
h = yp - y
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.putText(frame, str(w*h), (x + w, y + h), cv2.FONT_HERSHEY_SIMPLEX, .85, (0, 0, 255), 2)
cv2.putText(frame, str(w*h), (1, 25), cv2.FONT_HERSHEY_SIMPLEX, .65, (0, 0, 255), 2)
image_path = os.path.join(
output_dir, basename + '_%d.jpg' % image_index)
# save that frame to disk
cv2.imwrite(image_path, frame)
# start tracking a new consecutive sequence
if consecutive_start == -1:
consecutive_start = image_index
n_detected += 1
else:
if consecutive_start != -1:
# if end of consecutive sequence, calculate the results
series_list.append((consecutive_start, image_index-1))
print 'consecutive motion series: %d - %d, %d' % (
consecutive_start, image_index - 1,
image_index - consecutive_start)
consecutive_start = -1
last_filter_frame = filter_frame
video.release()
cv2.destroyAllWindows()
# save a nice stats file and print result
stats_path = os.path.join(OUTPUT_BASE_DIR, sitedate, basename + '_stats.txt')
with open(stats_path, 'w') as statsfile:
statsfile.write("run finished at " + str(datetime.now()) + " UTC\n")
statsfile.write("running on host " + str(os.uname()) + "\n")
statsfile.write("video " + video_path + "\n")
statsfile.write("mask " + mask_path + "\n")
statsfile.write("MIN_CONTOUR_AREA " + str(MIN_CONTOUR_AREA) + "\n")
statsfile.write("MASK_BASE_DIR " + MASK_BASE_DIR + "\n")
statsfile.write("RAWVID_BASE_DIR " + RAWVID_BASE_DIR + "\n")
statsfile.write("MOTION_BASE_DIR " + MOTION_BASE_DIR + "\n")
statsfile.write("OUTPUT_BASE_DIR " + OUTPUT_BASE_DIR + "\n")
statsfile.write("%d frames analysed\n" % image_index)
statsfile.write("%d motion frames detected\n" % n_detected)
statsfile.write("%d series detected\n" % len(series_list))
statsfile.write("consecutive motion series:\n")
for start_index, end_index in series_list:
statsfile.write(
"%d - %d, %d\n" % (
start_index, end_index, end_index - start_index + 1))
print open(stats_path, 'r').read()
with open('/tmp/plotwatcher_motionframes/gfa_plotwatch-samples_batch3_20170726.csv', 'rb') as csvfile:
csvreader = csv.reader(csvfile, delimiter=',')
csvreader.next()
for row in csvreader:
if not os.path.exists(os.path.join(MOTION_BASE_DIR, row[1], row[2] + '_stats.txt')):
print(row)
sitedate = row[1]
vid = row[2]
video_path = RAWVID_BASE_DIR + sitedate + "/" + vid
mask_path = MASK_BASE_DIR + sitedate + "_mask.png"
if os.path.exists(mask_path):
try:
main()
except:
print("ERROR: skipping " + sitedate + "/" + vid + " because opencv couldnt process it.")
else:
print("ERROR: skipping " + sitedate + "/" + vid + " because mask " + mask_path + " not found")
else:
print("Skipping " + row[1] + "/" + row[2] + ": already processed")