detect_objects.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import cv2
  2. import time
  3. import queue
  4. import yaml
  5. import threading
  6. import multiprocessing as mp
  7. import subprocess as sp
  8. import numpy as np
  9. from flask import Flask, Response, make_response, jsonify
  10. import paho.mqtt.client as mqtt
  11. from frigate.video import track_camera
  12. from frigate.object_processing import TrackedObjectProcessor
  13. from frigate.util import EventsPerSecond
  14. from frigate.edgetpu import EdgeTPUProcess
  15. with open('/config/config.yml') as f:
  16. CONFIG = yaml.safe_load(f)
  17. MQTT_HOST = CONFIG['mqtt']['host']
  18. MQTT_PORT = CONFIG.get('mqtt', {}).get('port', 1883)
  19. MQTT_TOPIC_PREFIX = CONFIG.get('mqtt', {}).get('topic_prefix', 'frigate')
  20. MQTT_USER = CONFIG.get('mqtt', {}).get('user')
  21. MQTT_PASS = CONFIG.get('mqtt', {}).get('password')
  22. MQTT_CLIENT_ID = CONFIG.get('mqtt', {}).get('client_id', 'frigate')
  23. # Set the default FFmpeg config
  24. FFMPEG_CONFIG = CONFIG.get('ffmpeg', {})
  25. FFMPEG_DEFAULT_CONFIG = {
  26. 'global_args': FFMPEG_CONFIG.get('global_args',
  27. ['-hide_banner','-loglevel','panic']),
  28. 'hwaccel_args': FFMPEG_CONFIG.get('hwaccel_args',
  29. []),
  30. 'input_args': FFMPEG_CONFIG.get('input_args',
  31. ['-avoid_negative_ts', 'make_zero',
  32. '-fflags', 'nobuffer',
  33. '-flags', 'low_delay',
  34. '-strict', 'experimental',
  35. '-fflags', '+genpts+discardcorrupt',
  36. '-vsync', 'drop',
  37. '-rtsp_transport', 'tcp',
  38. '-stimeout', '5000000',
  39. '-use_wallclock_as_timestamps', '1']),
  40. 'output_args': FFMPEG_CONFIG.get('output_args',
  41. ['-f', 'rawvideo',
  42. '-pix_fmt', 'rgb24'])
  43. }
  44. GLOBAL_OBJECT_CONFIG = CONFIG.get('objects', {})
  45. WEB_PORT = CONFIG.get('web_port', 5000)
  46. DEBUG = (CONFIG.get('debug', '0') == '1')
  47. # TODO: make CPU/Coral switching more seamless
  48. # MODEL_PATH = CONFIG.get('tflite_model', '/lab/mobilenet_ssd_v2_coco_quant_postprocess_edgetpu.tflite')
  49. MODEL_PATH = CONFIG.get('tflite_model', '/lab/detect.tflite')
  50. LABEL_MAP = CONFIG.get('label_map', '/lab/labelmap.txt')
  51. class CameraWatchdog(threading.Thread):
  52. def __init__(self, camera_processes, config, tflite_process, tracked_objects_queue):
  53. threading.Thread.__init__(self)
  54. self.camera_processes = camera_processes
  55. self.config = config
  56. self.tflite_process = tflite_process
  57. self.tracked_objects_queue = tracked_objects_queue
  58. def run(self):
  59. time.sleep(10)
  60. while True:
  61. # wait a bit before checking
  62. time.sleep(10)
  63. for name, camera_process in self.camera_processes.items():
  64. process = camera_process['process']
  65. if not process.is_alive():
  66. print(f"Process for {name} is not alive. Starting again...")
  67. camera_process['fps'].value = float(self.config[name]['fps'])
  68. camera_process['skipped_fps'].value = 0.0
  69. process = mp.Process(target=track_camera, args=(name, self.config[name], FFMPEG_DEFAULT_CONFIG, GLOBAL_OBJECT_CONFIG,
  70. self.tflite_process.detect_lock, self.tflite_process.detect_ready, self.tflite_process.frame_ready, self.tracked_objects_queue,
  71. camera_process['fps'], camera_process['skipped_fps']))
  72. process.daemon = True
  73. camera_process['process'] = process
  74. process.start()
  75. print(f"Camera_process started for {name}: {process.pid}")
  76. def main():
  77. # connect to mqtt and setup last will
  78. def on_connect(client, userdata, flags, rc):
  79. print("On connect called")
  80. if rc != 0:
  81. if rc == 3:
  82. print ("MQTT Server unavailable")
  83. elif rc == 4:
  84. print ("MQTT Bad username or password")
  85. elif rc == 5:
  86. print ("MQTT Not authorized")
  87. else:
  88. print ("Unable to connect to MQTT: Connection refused. Error code: " + str(rc))
  89. # publish a message to signal that the service is running
  90. client.publish(MQTT_TOPIC_PREFIX+'/available', 'online', retain=True)
  91. client = mqtt.Client(client_id=MQTT_CLIENT_ID)
  92. client.on_connect = on_connect
  93. client.will_set(MQTT_TOPIC_PREFIX+'/available', payload='offline', qos=1, retain=True)
  94. if not MQTT_USER is None:
  95. client.username_pw_set(MQTT_USER, password=MQTT_PASS)
  96. client.connect(MQTT_HOST, MQTT_PORT, 60)
  97. client.loop_start()
  98. # start plasma store
  99. plasma_cmd = ['plasma_store', '-m', '400000000', '-s', '/tmp/plasma']
  100. plasma_process = sp.Popen(plasma_cmd, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
  101. time.sleep(1)
  102. rc = plasma_process.poll()
  103. if rc is not None:
  104. raise RuntimeError("plasma_store exited unexpectedly with "
  105. "code %d" % (rc,))
  106. ##
  107. # Setup config defaults for cameras
  108. ##
  109. for name, config in CONFIG['cameras'].items():
  110. config['snapshots'] = {
  111. 'show_timestamp': config.get('snapshots', {}).get('show_timestamp', True)
  112. }
  113. # Queue for cameras to push tracked objects to
  114. tracked_objects_queue = mp.Queue()
  115. # Start the shared tflite process
  116. tflite_process = EdgeTPUProcess(MODEL_PATH)
  117. # start the camera processes
  118. camera_processes = {}
  119. for name, config in CONFIG['cameras'].items():
  120. camera_processes[name] = {
  121. 'fps': mp.Value('d', float(config[name]['fps'])),
  122. 'skipped_fps': mp.Value('d', 0.0)
  123. }
  124. camera_process = mp.Process(target=track_camera, args=(name, config, FFMPEG_DEFAULT_CONFIG, GLOBAL_OBJECT_CONFIG,
  125. tflite_process.detect_lock, tflite_process.detect_ready, tflite_process.frame_ready, tracked_objects_queue,
  126. camera_processes[name]['fps'], camera_processes[name]['skipped_fps']))
  127. camera_process.daemon = True
  128. camera_processes[name]['process'] = camera_process
  129. for name, camera_process in camera_processes.items():
  130. camera_process['process'].start()
  131. print(f"Camera_process started for {name}: {camera_process['process'].pid}")
  132. camera_watchdog = CameraWatchdog(camera_processes, CONFIG['cameras'], tflite_process, tracked_objects_queue)
  133. camera_watchdog.start()
  134. object_processor = TrackedObjectProcessor(CONFIG['cameras'], client, MQTT_TOPIC_PREFIX, tracked_objects_queue)
  135. object_processor.start()
  136. # create a flask app that encodes frames a mjpeg on demand
  137. app = Flask(__name__)
  138. @app.route('/')
  139. def ishealthy():
  140. # return a healh
  141. return "Frigate is running. Alive and healthy!"
  142. @app.route('/debug/stats')
  143. def stats():
  144. stats = {
  145. 'coral': {
  146. 'fps': tflite_process.fps.value,
  147. 'inference_speed': round(tflite_process.avg_inference_speed.value*1000, 2)
  148. }
  149. }
  150. for name, camera_stats in camera_processes.items():
  151. stats[name] = {
  152. 'fps': camera_stats['fps'].value,
  153. 'skipped_fps': camera_stats['skipped_fps'].value
  154. }
  155. return jsonify(stats)
  156. @app.route('/<camera_name>/<label>/best.jpg')
  157. def best(camera_name, label):
  158. if camera_name in CONFIG['cameras']:
  159. best_frame = object_processor.get_best(camera_name, label)
  160. if best_frame is None:
  161. best_frame = np.zeros((720,1280,3), np.uint8)
  162. best_frame = cv2.cvtColor(best_frame, cv2.COLOR_RGB2BGR)
  163. ret, jpg = cv2.imencode('.jpg', best_frame)
  164. response = make_response(jpg.tobytes())
  165. response.headers['Content-Type'] = 'image/jpg'
  166. return response
  167. else:
  168. return "Camera named {} not found".format(camera_name), 404
  169. @app.route('/<camera_name>')
  170. def mjpeg_feed(camera_name):
  171. if camera_name in CONFIG['cameras']:
  172. # return a multipart response
  173. return Response(imagestream(camera_name),
  174. mimetype='multipart/x-mixed-replace; boundary=frame')
  175. else:
  176. return "Camera named {} not found".format(camera_name), 404
  177. def imagestream(camera_name):
  178. while True:
  179. # max out at 1 FPS
  180. time.sleep(1)
  181. frame = object_processor.get_current_frame(camera_name)
  182. if frame is None:
  183. frame = np.zeros((720,1280,3), np.uint8)
  184. frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
  185. ret, jpg = cv2.imencode('.jpg', frame)
  186. yield (b'--frame\r\n'
  187. b'Content-Type: image/jpeg\r\n\r\n' + jpg.tobytes() + b'\r\n\r\n')
  188. app.run(host='0.0.0.0', port=WEB_PORT, debug=False)
  189. camera_watchdog.join()
  190. plasma_process.terminate()
  191. if __name__ == '__main__':
  192. main()