detect_objects.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import os
  2. import cv2
  3. import imutils
  4. import time
  5. import datetime
  6. import ctypes
  7. import logging
  8. import multiprocessing as mp
  9. import threading
  10. import json
  11. from contextlib import closing
  12. import numpy as np
  13. from object_detection.utils import visualization_utils as vis_util
  14. from flask import Flask, Response, make_response
  15. import paho.mqtt.client as mqtt
  16. from frigate.util import tonumpyarray
  17. from frigate.mqtt import MqttMotionPublisher, MqttObjectPublisher
  18. from frigate.objects import ObjectParser, ObjectCleaner
  19. from frigate.motion import detect_motion
  20. from frigate.video import fetch_frames, FrameTracker
  21. from frigate.object_detection import detect_objects
  22. RTSP_URL = os.getenv('RTSP_URL')
  23. MQTT_HOST = os.getenv('MQTT_HOST')
  24. MQTT_TOPIC_PREFIX = os.getenv('MQTT_TOPIC_PREFIX')
  25. MQTT_OBJECT_CLASSES = os.getenv('MQTT_OBJECT_CLASSES')
  26. # REGIONS = "350,0,300,50:400,350,250,50:400,750,250,50"
  27. # REGIONS = "400,350,250,50"
  28. REGIONS = os.getenv('REGIONS')
  29. DEBUG = (os.getenv('DEBUG') == '1')
  30. def main():
  31. DETECTED_OBJECTS = []
  32. recent_motion_frames = {}
  33. # Parse selected regions
  34. regions = []
  35. for region_string in REGIONS.split(':'):
  36. region_parts = region_string.split(',')
  37. region_mask_image = cv2.imread("/config/{}".format(region_parts[5]), cv2.IMREAD_GRAYSCALE)
  38. region_mask = np.where(region_mask_image==[0])
  39. regions.append({
  40. 'size': int(region_parts[0]),
  41. 'x_offset': int(region_parts[1]),
  42. 'y_offset': int(region_parts[2]),
  43. 'min_person_area': int(region_parts[3]),
  44. 'min_object_size': int(region_parts[4]),
  45. 'mask': region_mask,
  46. # Event for motion detection signaling
  47. 'motion_detected': mp.Event(),
  48. # create shared array for storing 10 detected objects
  49. # note: this must be a double even though the value you are storing
  50. # is a float. otherwise it stops updating the value in shared
  51. # memory. probably something to do with the size of the memory block
  52. 'output_array': mp.Array(ctypes.c_double, 6*10)
  53. })
  54. # capture a single frame and check the frame shape so the correct array
  55. # size can be allocated in memory
  56. video = cv2.VideoCapture(RTSP_URL)
  57. ret, frame = video.read()
  58. if ret:
  59. frame_shape = frame.shape
  60. else:
  61. print("Unable to capture video stream")
  62. exit(1)
  63. video.release()
  64. # compute the flattened array length from the array shape
  65. flat_array_length = frame_shape[0] * frame_shape[1] * frame_shape[2]
  66. # create shared array for storing the full frame image data
  67. shared_arr = mp.Array(ctypes.c_uint16, flat_array_length)
  68. # create shared value for storing the frame_time
  69. shared_frame_time = mp.Value('d', 0.0)
  70. # Lock to control access to the frame
  71. frame_lock = mp.Lock()
  72. # Condition for notifying that a new frame is ready
  73. frame_ready = mp.Condition()
  74. # Condition for notifying that motion status changed globally
  75. motion_changed = mp.Condition()
  76. # Condition for notifying that objects were parsed
  77. objects_parsed = mp.Condition()
  78. # Queue for detected objects
  79. object_queue = mp.Queue()
  80. # shape current frame so it can be treated as an image
  81. frame_arr = tonumpyarray(shared_arr).reshape(frame_shape)
  82. # start the process to capture frames from the RTSP stream and store in a shared array
  83. capture_process = mp.Process(target=fetch_frames, args=(shared_arr,
  84. shared_frame_time, frame_lock, frame_ready, frame_shape, RTSP_URL))
  85. capture_process.daemon = True
  86. # for each region, start a separate process for motion detection and object detection
  87. detection_processes = []
  88. motion_processes = []
  89. for region in regions:
  90. detection_process = mp.Process(target=detect_objects, args=(shared_arr,
  91. object_queue,
  92. shared_frame_time,
  93. frame_lock, frame_ready,
  94. region['motion_detected'],
  95. frame_shape,
  96. region['size'], region['x_offset'], region['y_offset'],
  97. region['min_person_area'],
  98. DEBUG))
  99. detection_process.daemon = True
  100. detection_processes.append(detection_process)
  101. motion_process = mp.Process(target=detect_motion, args=(shared_arr,
  102. shared_frame_time,
  103. frame_lock, frame_ready,
  104. region['motion_detected'],
  105. motion_changed,
  106. frame_shape,
  107. region['size'], region['x_offset'], region['y_offset'],
  108. region['min_object_size'], region['mask'],
  109. DEBUG))
  110. motion_process.daemon = True
  111. motion_processes.append(motion_process)
  112. # start a thread to store recent motion frames for processing
  113. frame_tracker = FrameTracker(frame_arr, shared_frame_time, frame_ready, frame_lock,
  114. recent_motion_frames, motion_changed, [region['motion_detected'] for region in regions])
  115. frame_tracker.start()
  116. # start a thread to parse objects from the queue
  117. object_parser = ObjectParser(object_queue, objects_parsed, DETECTED_OBJECTS)
  118. object_parser.start()
  119. # start a thread to expire objects from the detected objects list
  120. object_cleaner = ObjectCleaner(objects_parsed, DETECTED_OBJECTS)
  121. object_cleaner.start()
  122. # connect to mqtt and setup last will
  123. client = mqtt.Client()
  124. client.will_set(MQTT_TOPIC_PREFIX+'/available', payload='offline', qos=1, retain=True)
  125. client.connect(MQTT_HOST, 1883, 60)
  126. client.loop_start()
  127. # publish a message to signal that the service is running
  128. client.publish(MQTT_TOPIC_PREFIX+'/available', 'online', retain=True)
  129. # start a thread to publish object scores (currently only person)
  130. mqtt_publisher = MqttObjectPublisher(client, MQTT_TOPIC_PREFIX, objects_parsed,
  131. MQTT_OBJECT_CLASSES.split(','), DETECTED_OBJECTS)
  132. mqtt_publisher.start()
  133. # start thread to publish motion status
  134. mqtt_motion_publisher = MqttMotionPublisher(client, MQTT_TOPIC_PREFIX, motion_changed,
  135. [region['motion_detected'] for region in regions])
  136. mqtt_motion_publisher.start()
  137. # start the process of capturing frames
  138. capture_process.start()
  139. print("capture_process pid ", capture_process.pid)
  140. # start the object detection processes
  141. for detection_process in detection_processes:
  142. detection_process.start()
  143. print("detection_process pid ", detection_process.pid)
  144. # start the motion detection processes
  145. for motion_process in motion_processes:
  146. motion_process.start()
  147. print("motion_process pid ", motion_process.pid)
  148. # create a flask app that encodes frames a mjpeg on demand
  149. app = Flask(__name__)
  150. @app.route('/')
  151. def index():
  152. # return a multipart response
  153. return Response(imagestream(),
  154. mimetype='multipart/x-mixed-replace; boundary=frame')
  155. def imagestream():
  156. while True:
  157. # max out at 5 FPS
  158. time.sleep(0.2)
  159. # make a copy of the current detected objects
  160. detected_objects = DETECTED_OBJECTS.copy()
  161. # lock and make a copy of the current frame
  162. with frame_lock:
  163. frame = frame_arr.copy()
  164. # convert to RGB for drawing
  165. frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
  166. # draw the bounding boxes on the screen
  167. for obj in detected_objects:
  168. vis_util.draw_bounding_box_on_image_array(frame,
  169. obj['ymin'],
  170. obj['xmin'],
  171. obj['ymax'],
  172. obj['xmax'],
  173. color='red',
  174. thickness=2,
  175. display_str_list=["{}: {}%".format(obj['name'],int(obj['score']*100))],
  176. use_normalized_coordinates=False)
  177. for region in regions:
  178. color = (255,255,255)
  179. if region['motion_detected'].is_set():
  180. color = (0,255,0)
  181. cv2.rectangle(frame, (region['x_offset'], region['y_offset']),
  182. (region['x_offset']+region['size'], region['y_offset']+region['size']),
  183. color, 2)
  184. # convert back to BGR
  185. frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
  186. # encode the image into a jpg
  187. ret, jpg = cv2.imencode('.jpg', frame)
  188. yield (b'--frame\r\n'
  189. b'Content-Type: image/jpeg\r\n\r\n' + jpg.tobytes() + b'\r\n\r\n')
  190. app.run(host='0.0.0.0', debug=False)
  191. capture_process.join()
  192. for detection_process in detection_processes:
  193. detection_process.join()
  194. for motion_process in motion_processes:
  195. motion_process.join()
  196. frame_tracker.join()
  197. object_parser.join()
  198. object_cleaner.join()
  199. mqtt_publisher.join()
  200. if __name__ == '__main__':
  201. main()