detect_objects.py 9.6 KB

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