process_clip.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import datetime
  2. import json
  3. import logging
  4. import multiprocessing as mp
  5. import os
  6. import subprocess as sp
  7. import sys
  8. from unittest import TestCase, main
  9. import click
  10. import cv2
  11. import numpy as np
  12. from frigate.config import FRIGATE_CONFIG_SCHEMA, FrigateConfig
  13. from frigate.edgetpu import LocalObjectDetector
  14. from frigate.motion import MotionDetector
  15. from frigate.object_processing import COLOR_MAP, CameraState
  16. from frigate.objects import ObjectTracker
  17. from frigate.util import (DictFrameManager, EventsPerSecond,
  18. SharedMemoryFrameManager, draw_box_with_label)
  19. from frigate.video import (capture_frames, process_frames,
  20. start_or_restart_ffmpeg)
  21. logging.basicConfig()
  22. logging.root.setLevel(logging.DEBUG)
  23. logger = logging.getLogger(__name__)
  24. def get_frame_shape(source):
  25. ffprobe_cmd = " ".join([
  26. 'ffprobe',
  27. '-v',
  28. 'panic',
  29. '-show_error',
  30. '-show_streams',
  31. '-of',
  32. 'json',
  33. '"'+source+'"'
  34. ])
  35. p = sp.Popen(ffprobe_cmd, stdout=sp.PIPE, shell=True)
  36. (output, err) = p.communicate()
  37. p_status = p.wait()
  38. info = json.loads(output)
  39. video_info = [s for s in info['streams'] if s['codec_type'] == 'video'][0]
  40. if video_info['height'] != 0 and video_info['width'] != 0:
  41. return (video_info['height'], video_info['width'], 3)
  42. # fallback to using opencv if ffprobe didnt succeed
  43. video = cv2.VideoCapture(source)
  44. ret, frame = video.read()
  45. frame_shape = frame.shape
  46. video.release()
  47. return frame_shape
  48. class ProcessClip():
  49. def __init__(self, clip_path, frame_shape, config: FrigateConfig):
  50. self.clip_path = clip_path
  51. self.camera_name = 'camera'
  52. self.config = config
  53. self.camera_config = self.config.cameras['camera']
  54. self.frame_shape = self.camera_config.frame_shape
  55. self.ffmpeg_cmd = [c['cmd'] for c in self.camera_config.ffmpeg_cmds if 'detect' in c['roles']][0]
  56. self.frame_manager = SharedMemoryFrameManager()
  57. self.frame_queue = mp.Queue()
  58. self.detected_objects_queue = mp.Queue()
  59. self.camera_state = CameraState(self.camera_name, config, self.frame_manager)
  60. def load_frames(self):
  61. fps = EventsPerSecond()
  62. skipped_fps = EventsPerSecond()
  63. current_frame = mp.Value('d', 0.0)
  64. frame_size = self.camera_config.frame_shape_yuv[0] * self.camera_config.frame_shape_yuv[1]
  65. ffmpeg_process = start_or_restart_ffmpeg(self.ffmpeg_cmd, logger, sp.DEVNULL, frame_size)
  66. capture_frames(ffmpeg_process, self.camera_name, self.camera_config.frame_shape_yuv, self.frame_manager,
  67. self.frame_queue, fps, skipped_fps, current_frame)
  68. ffmpeg_process.wait()
  69. ffmpeg_process.communicate()
  70. def process_frames(self, objects_to_track=['person'], object_filters={}):
  71. mask = np.zeros((self.frame_shape[0], self.frame_shape[1], 1), np.uint8)
  72. mask[:] = 255
  73. motion_detector = MotionDetector(self.frame_shape, mask, self.camera_config.motion)
  74. object_detector = LocalObjectDetector(labels='/labelmap.txt')
  75. object_tracker = ObjectTracker(self.camera_config.detect)
  76. process_info = {
  77. 'process_fps': mp.Value('d', 0.0),
  78. 'detection_fps': mp.Value('d', 0.0),
  79. 'detection_frame': mp.Value('d', 0.0)
  80. }
  81. stop_event = mp.Event()
  82. model_shape = (self.config.model.height, self.config.model.width)
  83. process_frames(self.camera_name, self.frame_queue, self.frame_shape, model_shape,
  84. self.frame_manager, motion_detector, object_detector, object_tracker,
  85. self.detected_objects_queue, process_info,
  86. objects_to_track, object_filters, mask, stop_event, exit_on_empty=True)
  87. def top_object(self, debug_path=None):
  88. obj_detected = False
  89. top_computed_score = 0.0
  90. def handle_event(name, obj, frame_time):
  91. nonlocal obj_detected
  92. nonlocal top_computed_score
  93. if obj.computed_score > top_computed_score:
  94. top_computed_score = obj.computed_score
  95. if not obj.false_positive:
  96. obj_detected = True
  97. self.camera_state.on('new', handle_event)
  98. self.camera_state.on('update', handle_event)
  99. while(not self.detected_objects_queue.empty()):
  100. camera_name, frame_time, current_tracked_objects, motion_boxes, regions = self.detected_objects_queue.get()
  101. if not debug_path is None:
  102. self.save_debug_frame(debug_path, frame_time, current_tracked_objects.values())
  103. self.camera_state.update(frame_time, current_tracked_objects, motion_boxes, regions)
  104. self.frame_manager.delete(self.camera_state.previous_frame_id)
  105. return {
  106. 'object_detected': obj_detected,
  107. 'top_score': top_computed_score
  108. }
  109. def save_debug_frame(self, debug_path, frame_time, tracked_objects):
  110. current_frame = cv2.cvtColor(self.frame_manager.get(f"{self.camera_name}{frame_time}", self.camera_config.frame_shape_yuv), cv2.COLOR_YUV2BGR_I420)
  111. # draw the bounding boxes on the frame
  112. for obj in tracked_objects:
  113. thickness = 2
  114. color = (0,0,175)
  115. if obj['frame_time'] != frame_time:
  116. thickness = 1
  117. color = (255,0,0)
  118. else:
  119. color = (255,255,0)
  120. # draw the bounding boxes on the frame
  121. box = obj['box']
  122. draw_box_with_label(current_frame, box[0], box[1], box[2], box[3], obj['id'], f"{int(obj['score']*100)}% {int(obj['area'])}", thickness=thickness, color=color)
  123. # draw the regions on the frame
  124. region = obj['region']
  125. draw_box_with_label(current_frame, region[0], region[1], region[2], region[3], 'region', "", thickness=1, color=(0,255,0))
  126. cv2.imwrite(f"{os.path.join(debug_path, os.path.basename(self.clip_path))}.{int(frame_time*1000000)}.jpg", current_frame)
  127. @click.command()
  128. @click.option("-p", "--path", required=True, help="Path to clip or directory to test.")
  129. @click.option("-l", "--label", default='person', help="Label name to detect.")
  130. @click.option("-t", "--threshold", default=0.85, help="Threshold value for objects.")
  131. @click.option("-s", "--scores", default=None, help="File to save csv of top scores")
  132. @click.option("--debug-path", default=None, help="Path to output frames for debugging.")
  133. def process(path, label, threshold, scores, debug_path):
  134. clips = []
  135. if os.path.isdir(path):
  136. files = os.listdir(path)
  137. files.sort()
  138. clips = [os.path.join(path, file) for file in files]
  139. elif os.path.isfile(path):
  140. clips.append(path)
  141. json_config = {
  142. 'mqtt': {
  143. 'host': 'mqtt'
  144. },
  145. 'cameras': {
  146. 'camera': {
  147. 'ffmpeg': {
  148. 'inputs': [
  149. { 'path': 'path.mp4', 'global_args': '', 'input_args': '', 'roles': ['detect'] }
  150. ]
  151. },
  152. 'height': 1920,
  153. 'width': 1080
  154. }
  155. }
  156. }
  157. results = []
  158. for c in clips:
  159. logger.info(c)
  160. frame_shape = get_frame_shape(c)
  161. json_config['cameras']['camera']['height'] = frame_shape[0]
  162. json_config['cameras']['camera']['width'] = frame_shape[1]
  163. json_config['cameras']['camera']['ffmpeg']['inputs'][0]['path'] = c
  164. config = FrigateConfig(config=FRIGATE_CONFIG_SCHEMA(json_config))
  165. process_clip = ProcessClip(c, frame_shape, config)
  166. process_clip.load_frames()
  167. process_clip.process_frames(objects_to_track=[label])
  168. results.append((c, process_clip.top_object(debug_path)))
  169. if not scores is None:
  170. with open(scores, 'w') as writer:
  171. for result in results:
  172. writer.write(f"{result[0]},{result[1]['top_score']}\n")
  173. positive_count = sum(1 for result in results if result[1]['object_detected'])
  174. print(f"Objects were detected in {positive_count}/{len(results)}({positive_count/len(results)*100:.2f}%) clip(s).")
  175. if __name__ == '__main__':
  176. process()