removing motion detection

This commit is contained in:
blakeblackshear 2019-03-27 06:17:00 -05:00
parent 48aa245914
commit 200d769003
4 changed files with 89 additions and 165 deletions

View File

@ -37,22 +37,16 @@ DEBUG = (os.getenv('DEBUG') == '1')
def main(): def main():
DETECTED_OBJECTS = [] DETECTED_OBJECTS = []
recent_motion_frames = {} recent_frames = {}
# Parse selected regions # Parse selected regions
regions = [] regions = []
for region_string in REGIONS.split(':'): for region_string in REGIONS.split(':'):
region_parts = region_string.split(',') region_parts = region_string.split(',')
region_mask_image = cv2.imread("/config/{}".format(region_parts[5]), cv2.IMREAD_GRAYSCALE)
region_mask = np.where(region_mask_image==[0])
regions.append({ regions.append({
'size': int(region_parts[0]), 'size': int(region_parts[0]),
'x_offset': int(region_parts[1]), 'x_offset': int(region_parts[1]),
'y_offset': int(region_parts[2]), 'y_offset': int(region_parts[2]),
'min_person_area': int(region_parts[3]), 'min_person_area': int(region_parts[3]),
'min_object_size': int(region_parts[4]),
'mask': region_mask,
# Event for motion detection signaling
'motion_detected': mp.Event(),
# array for prepped frame with shape (1, 300, 300, 3) # array for prepped frame with shape (1, 300, 300, 3)
'prepped_frame_array': mp.Array(ctypes.c_uint8, 300*300*3), 'prepped_frame_array': mp.Array(ctypes.c_uint8, 300*300*3),
# shared value for storing the prepped_frame_time # shared value for storing the prepped_frame_time
@ -81,14 +75,13 @@ def main():
frame_lock = mp.Lock() frame_lock = mp.Lock()
# Condition for notifying that a new frame is ready # Condition for notifying that a new frame is ready
frame_ready = mp.Condition() frame_ready = mp.Condition()
# Condition for notifying that motion status changed globally # Shared memory array for passing prepped frame to tensorflow
motion_changed = mp.Condition()
prepped_frame_array = mp.Array(ctypes.c_uint8, 300*300*3) prepped_frame_array = mp.Array(ctypes.c_uint8, 300*300*3)
# create shared value for storing the frame_time # create shared value for storing the frame_time
prepped_frame_time = mp.Value('d', 0.0) prepped_frame_time = mp.Value('d', 0.0)
# Event for notifying that object detection needs a new frame # Event for notifying that object detection needs a new frame
prepped_frame_grabbed = mp.Event() prepped_frame_grabbed = mp.Event()
# Event for notifying that new frame is ready for detection
prepped_frame_ready = mp.Event() prepped_frame_ready = mp.Event()
# Condition for notifying that objects were parsed # Condition for notifying that objects were parsed
objects_parsed = mp.Condition() objects_parsed = mp.Condition()
@ -96,6 +89,7 @@ def main():
object_queue = mp.Queue() object_queue = mp.Queue()
# Queue for prepped frames # Queue for prepped frames
prepped_frame_queue = queue.Queue(len(regions)*2) prepped_frame_queue = queue.Queue(len(regions)*2)
# Array for passing original region box to compute object bounding box
prepped_frame_box = mp.Array(ctypes.c_uint16, 3) prepped_frame_box = mp.Array(ctypes.c_uint16, 3)
# shape current frame so it can be treated as an image # shape current frame so it can be treated as an image
@ -106,32 +100,18 @@ def main():
shared_frame_time, frame_lock, frame_ready, frame_shape, RTSP_URL)) shared_frame_time, frame_lock, frame_ready, frame_shape, RTSP_URL))
capture_process.daemon = True capture_process.daemon = True
# for each region, start a separate process for motion detection and object detection # for each region, start a separate thread to resize the region and prep for detection
detection_prep_threads = [] detection_prep_threads = []
motion_processes = []
for region in regions: for region in regions:
detection_prep_threads.append(FramePrepper( detection_prep_threads.append(FramePrepper(
frame_arr, frame_arr,
shared_frame_time, shared_frame_time,
frame_ready, frame_ready,
frame_lock, frame_lock,
region['motion_detected'],
region['size'], region['x_offset'], region['y_offset'], region['size'], region['x_offset'], region['y_offset'],
prepped_frame_queue prepped_frame_queue
)) ))
motion_process = mp.Process(target=detect_motion, args=(shared_arr,
shared_frame_time,
frame_lock, frame_ready,
region['motion_detected'],
motion_changed,
frame_shape,
region['size'], region['x_offset'], region['y_offset'],
region['min_object_size'], region['mask'],
DEBUG))
motion_process.daemon = True
motion_processes.append(motion_process)
prepped_queue_processor = PreppedQueueProcessor( prepped_queue_processor = PreppedQueueProcessor(
prepped_frame_array, prepped_frame_array,
prepped_frame_time, prepped_frame_time,
@ -157,24 +137,22 @@ def main():
# start a thread to store recent motion frames for processing # start a thread to store recent motion frames for processing
frame_tracker = FrameTracker(frame_arr, shared_frame_time, frame_ready, frame_lock, frame_tracker = FrameTracker(frame_arr, shared_frame_time, frame_ready, frame_lock,
recent_motion_frames, motion_changed, [region['motion_detected'] for region in regions]) recent_frames)
frame_tracker.start() frame_tracker.start()
# start a thread to store the highest scoring recent person frame # start a thread to store the highest scoring recent person frame
best_person_frame = BestPersonFrame(objects_parsed, recent_motion_frames, DETECTED_OBJECTS, best_person_frame = BestPersonFrame(objects_parsed, recent_frames, DETECTED_OBJECTS)
motion_changed, [region['motion_detected'] for region in regions])
best_person_frame.start() best_person_frame.start()
# start a thread to parse objects from the queue # start a thread to parse objects from the queue
object_parser = ObjectParser(object_queue, objects_parsed, DETECTED_OBJECTS) object_parser = ObjectParser(object_queue, objects_parsed, DETECTED_OBJECTS)
object_parser.start() object_parser.start()
# start a thread to expire objects from the detected objects list # start a thread to expire objects from the detected objects list
object_cleaner = ObjectCleaner(objects_parsed, DETECTED_OBJECTS, object_cleaner = ObjectCleaner(objects_parsed, DETECTED_OBJECTS)
motion_changed, [region['motion_detected'] for region in regions])
object_cleaner.start() object_cleaner.start()
# connect to mqtt and setup last will # connect to mqtt and setup last will
def on_connect(client, userdata, flags, rc): def on_connect(client, userdata, flags, rc):
print("On connect called") print("On connect called")
# publish a message to signal that the service is running # publish a message to signal that the service is running
client.publish(MQTT_TOPIC_PREFIX+'/available', 'online', retain=True) client.publish(MQTT_TOPIC_PREFIX+'/available', 'online', retain=True)
@ -191,32 +169,16 @@ def main():
mqtt_publisher = MqttObjectPublisher(client, MQTT_TOPIC_PREFIX, objects_parsed, DETECTED_OBJECTS) mqtt_publisher = MqttObjectPublisher(client, MQTT_TOPIC_PREFIX, objects_parsed, DETECTED_OBJECTS)
mqtt_publisher.start() mqtt_publisher.start()
# start thread to publish motion status
mqtt_motion_publisher = MqttMotionPublisher(client, MQTT_TOPIC_PREFIX, motion_changed,
[region['motion_detected'] for region in regions])
mqtt_motion_publisher.start()
# start the process of capturing frames # start the process of capturing frames
capture_process.start() capture_process.start()
print("capture_process pid ", capture_process.pid) print("capture_process pid ", capture_process.pid)
# start the object detection prep processes # start the object detection prep threads
for detection_prep_thread in detection_prep_threads: for detection_prep_thread in detection_prep_threads:
detection_prep_thread.start() detection_prep_thread.start()
detection_process.start() detection_process.start()
print("detection_process pid ", detection_process.pid) print("detection_process pid ", detection_process.pid)
# start the motion detection processes
# for motion_process in motion_processes:
# motion_process.start()
# print("motion_process pid ", motion_process.pid)
# TEMP: short circuit the motion detection
for region in regions:
region['motion_detected'].set()
with motion_changed:
motion_changed.notify_all()
# create a flask app that encodes frames a mjpeg on demand # create a flask app that encodes frames a mjpeg on demand
app = Flask(__name__) app = Flask(__name__)
@ -259,8 +221,6 @@ def main():
for region in regions: for region in regions:
color = (255,255,255) color = (255,255,255)
if region['motion_detected'].is_set():
color = (0,255,0)
cv2.rectangle(frame, (region['x_offset'], region['y_offset']), cv2.rectangle(frame, (region['x_offset'], region['y_offset']),
(region['x_offset']+region['size'], region['y_offset']+region['size']), (region['x_offset']+region['size'], region['y_offset']+region['size']),
color, 2) color, 2)
@ -277,8 +237,6 @@ def main():
capture_process.join() capture_process.join()
for detection_prep_thread in detection_prep_threads: for detection_prep_thread in detection_prep_threads:
detection_prep_thread.join() detection_prep_thread.join()
for motion_process in motion_processes:
motion_process.join()
detection_process.join() detection_process.join()
frame_tracker.join() frame_tracker.join()
best_person_frame.join() best_person_frame.join()

View File

@ -47,7 +47,7 @@ def detect_objects(prepped_frame_array, prepped_frame_time,
objects = engine.DetectWithInputTensor(prepped_frame_copy, threshold=0.5, top_k=3) objects = engine.DetectWithInputTensor(prepped_frame_copy, threshold=0.5, top_k=3)
# time.sleep(0.1) # time.sleep(0.1)
# objects = [] # objects = []
print(engine.get_inference_time()) # print(engine.get_inference_time())
# put detected objects in the queue # put detected objects in the queue
if objects: if objects:
for obj in objects: for obj in objects:
@ -109,7 +109,7 @@ class PreppedQueueProcessor(threading.Thread):
# should this be a region class? # should this be a region class?
class FramePrepper(threading.Thread): class FramePrepper(threading.Thread):
def __init__(self, shared_frame, frame_time, frame_ready, def __init__(self, shared_frame, frame_time, frame_ready,
frame_lock, motion_detected, frame_lock,
region_size, region_x_offset, region_y_offset, region_size, region_x_offset, region_y_offset,
prepped_frame_queue): prepped_frame_queue):
@ -118,7 +118,6 @@ class FramePrepper(threading.Thread):
self.frame_time = frame_time self.frame_time = frame_time
self.frame_ready = frame_ready self.frame_ready = frame_ready
self.frame_lock = frame_lock self.frame_lock = frame_lock
self.motion_detected = motion_detected
self.region_size = region_size self.region_size = region_size
self.region_x_offset = region_x_offset self.region_x_offset = region_x_offset
self.region_y_offset = region_y_offset self.region_y_offset = region_y_offset
@ -129,9 +128,6 @@ class FramePrepper(threading.Thread):
while True: while True:
now = datetime.datetime.now().timestamp() now = datetime.datetime.now().timestamp()
# wait until motion is detected
self.motion_detected.wait()
with self.frame_ready: with self.frame_ready:
# if there isnt a frame ready for processing or it is old, wait for a new frame # if there isnt a frame ready for processing or it is old, wait for a new frame
if self.frame_time.value == frame_time or (now - self.frame_time.value) > 0.5: if self.frame_time.value == frame_time or (now - self.frame_time.value) > 0.5:

View File

@ -30,114 +30,92 @@ class ObjectParser(threading.Thread):
self._objects_parsed.notify_all() self._objects_parsed.notify_all()
class ObjectCleaner(threading.Thread): class ObjectCleaner(threading.Thread):
def __init__(self, objects_parsed, detected_objects, motion_changed, motion_regions): def __init__(self, objects_parsed, detected_objects):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self._objects_parsed = objects_parsed self._objects_parsed = objects_parsed
self._detected_objects = detected_objects self._detected_objects = detected_objects
self.motion_changed = motion_changed
self.motion_regions = motion_regions
def run(self): def run(self):
while True: while True:
# while there is motion # wait a bit before checking for expired frames
while len([r for r in self.motion_regions if r.is_set()]) > 0: time.sleep(0.2)
# wait a bit before checking for expired frames
time.sleep(0.2)
# expire the objects that are more than 1 second old # expire the objects that are more than 1 second old
now = datetime.datetime.now().timestamp() now = datetime.datetime.now().timestamp()
# look for the first object found within the last second # look for the first object found within the last second
# (newest objects are appended to the end) # (newest objects are appended to the end)
detected_objects = self._detected_objects.copy() detected_objects = self._detected_objects.copy()
#print([round(now-obj['frame_time'],2) for obj in detected_objects]) #print([round(now-obj['frame_time'],2) for obj in detected_objects])
num_to_delete = 0 num_to_delete = 0
for obj in detected_objects: for obj in detected_objects:
if now-obj['frame_time']<2: if now-obj['frame_time']<2:
break break
num_to_delete += 1 num_to_delete += 1
if num_to_delete > 0: if num_to_delete > 0:
del self._detected_objects[:num_to_delete] del self._detected_objects[:num_to_delete]
# notify that parsed objects were changed # notify that parsed objects were changed
with self._objects_parsed: with self._objects_parsed:
self._objects_parsed.notify_all() self._objects_parsed.notify_all()
# wait for the global motion flag to change
with self.motion_changed:
self.motion_changed.wait()
# Maintains the frame and person with the highest score from the most recent # Maintains the frame and person with the highest score from the most recent
# motion event # motion event
class BestPersonFrame(threading.Thread): class BestPersonFrame(threading.Thread):
def __init__(self, objects_parsed, recent_frames, detected_objects, motion_changed, motion_regions): def __init__(self, objects_parsed, recent_frames, detected_objects):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.objects_parsed = objects_parsed self.objects_parsed = objects_parsed
self.recent_frames = recent_frames self.recent_frames = recent_frames
self.detected_objects = detected_objects self.detected_objects = detected_objects
self.motion_changed = motion_changed
self.motion_regions = motion_regions
self.best_person = None self.best_person = None
self.best_frame = None self.best_frame = None
def run(self): def run(self):
motion_start = 0.0
motion_end = 0.0
while True: while True:
# while there is motion # wait until objects have been parsed
while len([r for r in self.motion_regions if r.is_set()]) > 0: with self.objects_parsed:
# wait until objects have been parsed self.objects_parsed.wait()
with self.objects_parsed:
self.objects_parsed.wait()
# make a copy of detected objects # make a copy of detected objects
detected_objects = self.detected_objects.copy() detected_objects = self.detected_objects.copy()
detected_people = [obj for obj in detected_objects if obj['name'] == 'person'] detected_people = [obj for obj in detected_objects if obj['name'] == 'person']
# make a copy of the recent frames # make a copy of the recent frames
recent_frames = self.recent_frames.copy() recent_frames = self.recent_frames.copy()
# get the highest scoring person # get the highest scoring person
new_best_person = max(detected_people, key=lambda x:x['score'], default=self.best_person) new_best_person = max(detected_people, key=lambda x:x['score'], default=self.best_person)
# if there isnt a person, continue # if there isnt a person, continue
if new_best_person is None: if new_best_person is None:
continue continue
# if there is no current best_person # if there is no current best_person
if self.best_person is None: if self.best_person is None:
self.best_person = new_best_person
# if there is already a best_person
else:
now = datetime.datetime.now().timestamp()
# if the new best person is a higher score than the current best person
# or the current person is more than 1 minute old, use the new best person
if new_best_person['score'] > self.best_person['score'] or (now - self.best_person['frame_time']) > 60:
self.best_person = new_best_person self.best_person = new_best_person
# if there is already a best_person
else:
now = datetime.datetime.now().timestamp()
# if the new best person is a higher score than the current best person
# or the current person is more than 1 minute old, use the new best person
if new_best_person['score'] > self.best_person['score'] or (now - self.best_person['frame_time']) > 60:
self.best_person = new_best_person
if not self.best_person is None and self.best_person['frame_time'] in recent_frames: if not self.best_person is None and self.best_person['frame_time'] in recent_frames:
best_frame = recent_frames[self.best_person['frame_time']] best_frame = recent_frames[self.best_person['frame_time']]
best_frame = cv2.cvtColor(best_frame, cv2.COLOR_BGR2RGB) best_frame = cv2.cvtColor(best_frame, cv2.COLOR_BGR2RGB)
# draw the bounding box on the frame # draw the bounding box on the frame
vis_util.draw_bounding_box_on_image_array(best_frame, vis_util.draw_bounding_box_on_image_array(best_frame,
self.best_person['ymin'], self.best_person['ymin'],
self.best_person['xmin'], self.best_person['xmin'],
self.best_person['ymax'], self.best_person['ymax'],
self.best_person['xmax'], self.best_person['xmax'],
color='red', color='red',
thickness=2, thickness=2,
display_str_list=["{}: {}%".format(self.best_person['name'],int(self.best_person['score']*100))], display_str_list=["{}: {}%".format(self.best_person['name'],int(self.best_person['score']*100))],
use_normalized_coordinates=False) use_normalized_coordinates=False)
# convert back to BGR # convert back to BGR
self.best_frame = cv2.cvtColor(best_frame, cv2.COLOR_RGB2BGR) self.best_frame = cv2.cvtColor(best_frame, cv2.COLOR_RGB2BGR)
motion_end = datetime.datetime.now().timestamp()
# wait for the global motion flag to change
with self.motion_changed:
self.motion_changed.wait()
motion_start = datetime.datetime.now().timestamp()

View File

@ -54,42 +54,34 @@ def fetch_frames(shared_arr, shared_frame_time, frame_lock, frame_ready, frame_s
# Stores 2 seconds worth of frames when motion is detected so they can be used for other threads # Stores 2 seconds worth of frames when motion is detected so they can be used for other threads
class FrameTracker(threading.Thread): class FrameTracker(threading.Thread):
def __init__(self, shared_frame, frame_time, frame_ready, frame_lock, recent_frames, motion_changed, motion_regions): def __init__(self, shared_frame, frame_time, frame_ready, frame_lock, recent_frames):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.shared_frame = shared_frame self.shared_frame = shared_frame
self.frame_time = frame_time self.frame_time = frame_time
self.frame_ready = frame_ready self.frame_ready = frame_ready
self.frame_lock = frame_lock self.frame_lock = frame_lock
self.recent_frames = recent_frames self.recent_frames = recent_frames
self.motion_changed = motion_changed
self.motion_regions = motion_regions
def run(self): def run(self):
frame_time = 0.0 frame_time = 0.0
while True: while True:
# while there is motion now = datetime.datetime.now().timestamp()
while len([r for r in self.motion_regions if r.is_set()]) > 0: # wait for a frame
now = datetime.datetime.now().timestamp() with self.frame_ready:
# wait for a frame # if there isnt a frame ready for processing or it is old, wait for a signal
with self.frame_ready: if self.frame_time.value == frame_time or (now - self.frame_time.value) > 0.5:
# if there isnt a frame ready for processing or it is old, wait for a signal self.frame_ready.wait()
if self.frame_time.value == frame_time or (now - self.frame_time.value) > 0.5:
self.frame_ready.wait() # lock and make a copy of the frame
with self.frame_lock:
# lock and make a copy of the frame frame = self.shared_frame.copy()
with self.frame_lock: frame_time = self.frame_time.value
frame = self.shared_frame.copy()
frame_time = self.frame_time.value # add the frame to recent frames
self.recent_frames[frame_time] = frame
# add the frame to recent frames
self.recent_frames[frame_time] = frame
# delete any old frames # delete any old frames
stored_frame_times = list(self.recent_frames.keys()) stored_frame_times = list(self.recent_frames.keys())
for k in stored_frame_times: for k in stored_frame_times:
if (now - k) > 2: if (now - k) > 2:
del self.recent_frames[k] del self.recent_frames[k]
# wait for the global motion flag to change
with self.motion_changed:
self.motion_changed.wait()