Skip to content

Commit

Permalink
support multiple coral devices (fixes blakeblackshear#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeblackshear committed Oct 18, 2020
1 parent 49fca1b commit f946813
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 75 deletions.
53 changes: 29 additions & 24 deletions benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@
# print(f"Processed for {duration:.2f} seconds.")
# print(f"Average frame processing time: {mean(frame_times)*1000:.2f}ms")

######
# Separate process runner
######

def start(id, num_detections, detection_queue, event):
object_detector = RemoteObjectDetector(str(id), '/labelmap.txt', detection_queue, event)
start = datetime.datetime.now().timestamp()
Expand All @@ -51,38 +49,45 @@ def start(id, num_detections, detection_queue, event):
frame_times.append(datetime.datetime.now().timestamp()-start_frame)

duration = datetime.datetime.now().timestamp()-start
object_detector.cleanup()
print(f"{id} - Processed for {duration:.2f} seconds.")
print(f"{id} - FPS: {object_detector.fps.eps():.2f}")
print(f"{id} - Average frame processing time: {mean(frame_times)*1000:.2f}ms")

event = mp.Event()
edgetpu_process = EdgeTPUProcess({'1': event})
######
# Separate process runner
######
# event = mp.Event()
# detection_queue = mp.Queue()
# edgetpu_process = EdgeTPUProcess(detection_queue, {'1': event}, 'usb:0')

start(1, 1000, edgetpu_process.detection_queue, event)
print(f"Average raw inference speed: {edgetpu_process.avg_inference_speed.value*1000:.2f}ms")
# start(1, 1000, edgetpu_process.detection_queue, event)
# print(f"Average raw inference speed: {edgetpu_process.avg_inference_speed.value*1000:.2f}ms")

####
# Multiple camera processes
####
# camera_processes = []
camera_processes = []

# pipes = {}
# for x in range(0, 10):
# pipes[x] = mp.Pipe(duplex=False)
events = {}
for x in range(0, 10):
events[str(x)] = mp.Event()
detection_queue = mp.Queue()
edgetpu_process_1 = EdgeTPUProcess(detection_queue, events, 'usb:0')
edgetpu_process_2 = EdgeTPUProcess(detection_queue, events, 'usb:1')

# edgetpu_process = EdgeTPUProcess({str(key): value[1] for (key, value) in pipes.items()})
for x in range(0, 10):
camera_process = mp.Process(target=start, args=(x, 300, detection_queue, events[str(x)]))
camera_process.daemon = True
camera_processes.append(camera_process)

# for x in range(0, 10):
# camera_process = mp.Process(target=start, args=(x, 100, edgetpu_process.detection_queue, pipes[x][0]))
# camera_process.daemon = True
# camera_processes.append(camera_process)
start_time = datetime.datetime.now().timestamp()

# start = datetime.datetime.now().timestamp()
for p in camera_processes:
p.start()

# for p in camera_processes:
# p.start()
for p in camera_processes:
p.join()

# for p in camera_processes:
# p.join()

# duration = datetime.datetime.now().timestamp()-start
# print(f"Total - Processed for {duration:.2f} seconds.")
duration = datetime.datetime.now().timestamp()-start_time
print(f"Total - Processed for {duration:.2f} seconds.")
9 changes: 6 additions & 3 deletions config/config.example.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
web_port: 5000

################
## Tell frigate to look for a specific EdgeTPU device. Useful if you want to run multiple instances of frigate
## on the same machine with multiple EdgeTPUs. https://coral.ai/docs/edgetpu/multiple-edgetpu/#using-the-tensorflow-lite-python-api
## List of detectors.
## Currently supported types: cpu, edgetpu
## EdgeTPU requires device as defined here: https://coral.ai/docs/edgetpu/multiple-edgetpu/#using-the-tensorflow-lite-python-api
################
tensorflow_device: usb
detectors:
- type: edgetpu
device: usb

mqtt:
host: mqtt.server.com
Expand Down
70 changes: 40 additions & 30 deletions detect_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@
GLOBAL_OBJECT_CONFIG = CONFIG.get('objects', {})

WEB_PORT = CONFIG.get('web_port', 5000)
DEBUG = (CONFIG.get('debug', '0') == '1')
TENSORFLOW_DEVICE = CONFIG.get('tensorflow_device')
DETECTORS = CONFIG.get('detectors', [{'type': 'edgetpu', 'device': 'usb'}])

class CameraWatchdog(threading.Thread):
def __init__(self, camera_processes, config, tflite_process, tracked_objects_queue, stop_event):
def __init__(self, camera_processes, config, detectors, detection_queue, tracked_objects_queue, stop_event):
threading.Thread.__init__(self)
self.camera_processes = camera_processes
self.config = config
self.tflite_process = tflite_process
self.detectors = detectors
self.detection_queue = detection_queue
self.tracked_objects_queue = tracked_objects_queue
self.stop_event = stop_event

Expand All @@ -85,15 +85,16 @@ def run(self):

now = datetime.datetime.now().timestamp()

# check the detection process
detection_start = self.tflite_process.detection_start.value
if (detection_start > 0.0 and
now - detection_start > 10):
print("Detection appears to be stuck. Restarting detection process")
self.tflite_process.start_or_restart()
elif not self.tflite_process.detect_process.is_alive():
print("Detection appears to have stopped. Restarting detection process")
self.tflite_process.start_or_restart()
# check the detection processes
for detector in self.detectors:
detection_start = detector.detection_start.value
if (detection_start > 0.0 and
now - detection_start > 10):
print("Detection appears to be stuck. Restarting detection process")
detector.start_or_restart()
elif not detector.detect_process.is_alive():
print("Detection appears to have stopped. Restarting detection process")
detector.start_or_restart()

# check the camera processes
for name, camera_process in self.camera_processes.items():
Expand All @@ -104,9 +105,9 @@ def run(self):
camera_process['detection_fps'].value = 0.0
camera_process['read_start'].value = 0.0
process = mp.Process(target=track_camera, args=(name, self.config[name], camera_process['frame_queue'],
camera_process['frame_shape'], self.tflite_process.detection_queue, self.tracked_objects_queue,
camera_process['frame_shape'], self.detection_queue, self.tracked_objects_queue,
camera_process['process_fps'], camera_process['detection_fps'],
camera_process['read_start'], camera_process['detection_frame'], self.stop_event))
camera_process['read_start'], self.stop_event))
process.daemon = True
camera_process['process'] = process
process.start()
Expand All @@ -117,7 +118,7 @@ def run(self):
frame_size = frame_shape[0] * frame_shape[1] * frame_shape[2]
ffmpeg_process = start_or_restart_ffmpeg(camera_process['ffmpeg_cmd'], frame_size)
camera_capture = CameraCapture(name, ffmpeg_process, frame_shape, camera_process['frame_queue'],
camera_process['take_frame'], camera_process['camera_fps'], camera_process['detection_frame'], self.stop_event)
camera_process['take_frame'], camera_process['camera_fps'], self.stop_event)
camera_capture.start()
camera_process['ffmpeg_process'] = ffmpeg_process
camera_process['capture_thread'] = camera_capture
Expand Down Expand Up @@ -177,9 +178,15 @@ def on_connect(client, userdata, flags, rc):
out_events = {}
for name in CONFIG['cameras'].keys():
out_events[name] = mp.Event()

# Start the shared tflite process
tflite_process = EdgeTPUProcess(out_events=out_events, tf_device=TENSORFLOW_DEVICE)

detection_queue = mp.Queue()

detectors = []
for detector in DETECTORS:
if detector['type'] == 'cpu':
detectors.append(EdgeTPUProcess(detection_queue, out_events=out_events, tf_device='cpu'))
if detector['type'] == 'edgetpu':
detectors.append(EdgeTPUProcess(detection_queue, out_events=out_events, tf_device=detector['device']))

# create the camera processes
camera_processes = {}
Expand Down Expand Up @@ -233,10 +240,10 @@ def on_connect(client, userdata, flags, rc):
detection_frame = mp.Value('d', 0.0)

ffmpeg_process = start_or_restart_ffmpeg(ffmpeg_cmd, frame_size)
frame_queue = mp.Queue()
frame_queue = mp.Queue(maxsize=2)
camera_fps = EventsPerSecond()
camera_fps.start()
camera_capture = CameraCapture(name, ffmpeg_process, frame_shape, frame_queue, take_frame, camera_fps, detection_frame, stop_event)
camera_capture = CameraCapture(name, ffmpeg_process, frame_shape, frame_queue, take_frame, camera_fps, stop_event)
camera_capture.start()

camera_processes[name] = {
Expand Down Expand Up @@ -265,7 +272,7 @@ def on_connect(client, userdata, flags, rc):
}

camera_process = mp.Process(target=track_camera, args=(name, config, frame_queue, frame_shape,
tflite_process.detection_queue, out_events[name], tracked_objects_queue, camera_processes[name]['process_fps'],
detection_queue, out_events[name], tracked_objects_queue, camera_processes[name]['process_fps'],
camera_processes[name]['detection_fps'],
camera_processes[name]['read_start'], camera_processes[name]['detection_frame'], stop_event))
camera_process.daemon = True
Expand All @@ -282,7 +289,7 @@ def on_connect(client, userdata, flags, rc):
object_processor = TrackedObjectProcessor(CONFIG['cameras'], client, MQTT_TOPIC_PREFIX, tracked_objects_queue, event_queue, stop_event)
object_processor.start()

camera_watchdog = CameraWatchdog(camera_processes, CONFIG['cameras'], tflite_process, tracked_objects_queue, stop_event)
camera_watchdog = CameraWatchdog(camera_processes, CONFIG['cameras'], detectors, detection_queue, tracked_objects_queue, stop_event)
camera_watchdog.start()

def receiveSignal(signalNumber, frame):
Expand All @@ -293,7 +300,8 @@ def receiveSignal(signalNumber, frame):
camera_watchdog.join()
for camera_process in camera_processes.values():
camera_process['capture_thread'].join()
tflite_process.stop()
for detector in detectors:
detector.stop()
sys.exit()

signal.signal(signal.SIGTERM, receiveSignal)
Expand Down Expand Up @@ -350,12 +358,14 @@ def stats():
}
}

stats['coral'] = {
'fps': round(total_detection_fps, 2),
'inference_speed': round(tflite_process.avg_inference_speed.value*1000, 2),
'detection_start': tflite_process.detection_start.value,
'pid': tflite_process.detect_process.pid
}
stats['detectors'] = []
for detector in detectors:
stats['detectors'].append({
'inference_speed': round(detector.avg_inference_speed.value*1000, 2),
'detection_start': detector.detection_start.value,
'pid': detector.detect_process.pid
})
stats['detection_fps'] = round(total_detection_fps, 2)

return jsonify(stats)

Expand Down
21 changes: 11 additions & 10 deletions frigate/edgetpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,12 @@ def __init__(self, tf_device=None, labels=None):
device_config = {"device": tf_device}

edge_tpu_delegate = None
try:
print(f"Attempting to load TPU as {device_config['device']}")
edge_tpu_delegate = load_delegate('libedgetpu.so.1.0', device_config)
print("TPU found")
except ValueError:

if tf_device != 'cpu':
try:
print(f"Attempting to load TPU as pci:0")
edge_tpu_delegate = load_delegate('libedgetpu.so.1.0', {"device": "pci:0"})
print("PCIe TPU found")
print(f"Attempting to load TPU as {device_config['device']}")
edge_tpu_delegate = load_delegate('libedgetpu.so.1.0', device_config)
print("TPU found")
except ValueError:
print("No EdgeTPU detected. Falling back to CPU.")

Expand Down Expand Up @@ -135,9 +132,9 @@ def run_detector(detection_queue, out_events: Dict[str, mp.Event], avg_speed, st
avg_speed.value = (avg_speed.value*9 + duration)/10

class EdgeTPUProcess():
def __init__(self, out_events, tf_device=None):
def __init__(self, detection_queue, out_events, tf_device=None):
self.out_events = out_events
self.detection_queue = mp.Queue()
self.detection_queue = detection_queue
self.avg_inference_speed = mp.Value('d', 0.01)
self.detection_start = mp.Value('d', 0.0)
self.detect_process = None
Expand Down Expand Up @@ -192,3 +189,7 @@ def detect(self, tensor_input, threshold=.4):
))
self.fps.update()
return detections

def cleanup(self):
self.shm.unlink()
self.out_shm.unlink()
13 changes: 5 additions & 8 deletions frigate/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,9 @@ def start_or_restart_ffmpeg(ffmpeg_cmd, frame_size, ffmpeg_process=None):

def capture_frames(ffmpeg_process, camera_name, frame_shape, frame_manager: FrameManager,
frame_queue, take_frame: int, fps:EventsPerSecond, skipped_fps: EventsPerSecond,
stop_event: mp.Event, detection_frame: mp.Value, current_frame: mp.Value):
stop_event: mp.Event, current_frame: mp.Value):

frame_num = 0
last_frame = 0
frame_size = frame_shape[0] * frame_shape[1] * frame_shape[2]
skipped_fps.start()
while True:
Expand All @@ -147,8 +146,8 @@ def capture_frames(ffmpeg_process, camera_name, frame_shape, frame_manager: Fram
skipped_fps.update()
continue

# if the detection process is more than 1 second behind, skip this frame
if detection_frame.value > 0.0 and (last_frame - detection_frame.value) > 1:
# if the queue is full, skip this frame
if frame_queue.full():
skipped_fps.update()
continue

Expand All @@ -159,10 +158,9 @@ def capture_frames(ffmpeg_process, camera_name, frame_shape, frame_manager: Fram

# add to the queue
frame_queue.put(current_frame.value)
last_frame = current_frame.value

class CameraCapture(threading.Thread):
def __init__(self, name, ffmpeg_process, frame_shape, frame_queue, take_frame, fps, detection_frame, stop_event):
def __init__(self, name, ffmpeg_process, frame_shape, frame_queue, take_frame, fps, stop_event):
threading.Thread.__init__(self)
self.name = name
self.frame_shape = frame_shape
Expand All @@ -175,13 +173,12 @@ def __init__(self, name, ffmpeg_process, frame_shape, frame_queue, take_frame, f
self.ffmpeg_process = ffmpeg_process
self.current_frame = mp.Value('d', 0.0)
self.last_frame = 0
self.detection_frame = detection_frame
self.stop_event = stop_event

def run(self):
self.skipped_fps.start()
capture_frames(self.ffmpeg_process, self.name, self.frame_shape, self.frame_manager, self.frame_queue, self.take_frame,
self.fps, self.skipped_fps, self.stop_event, self.detection_frame, self.current_frame)
self.fps, self.skipped_fps, self.stop_event, self.current_frame)

def track_camera(name, config, frame_queue, frame_shape, detection_queue, result_connection, detected_objects_queue, fps, detection_fps, read_start, detection_frame, stop_event):
print(f"Starting process for {name}: {os.getpid()}")
Expand Down

0 comments on commit f946813

Please sign in to comment.