detect_objects.py 9.5 KB

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