mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
upgrade to python3.8 and switch from plasma store to shared_memory
This commit is contained in:
parent
b063099b2a
commit
ec4d048905
25
Dockerfile
25
Dockerfile
@ -1,4 +1,4 @@
|
|||||||
FROM ubuntu:18.04
|
FROM ubuntu:20.04
|
||||||
LABEL maintainer "blakeb@blakeshome.com"
|
LABEL maintainer "blakeb@blakeshome.com"
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
@ -11,27 +11,26 @@ RUN apt -qq update && apt -qq install --no-install-recommends -y \
|
|||||||
# libcap-dev \
|
# libcap-dev \
|
||||||
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
||||||
&& apt -qq install --no-install-recommends -y \
|
&& apt -qq install --no-install-recommends -y \
|
||||||
python3.7 \
|
python3.8 \
|
||||||
python3.7-dev \
|
python3.8-dev \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
# VAAPI drivers for Intel hardware accel
|
# VAAPI drivers for Intel hardware accel
|
||||||
libva-drm2 libva2 i965-va-driver vainfo \
|
libva-drm2 libva2 i965-va-driver vainfo \
|
||||||
&& python3.7 -m pip install -U pip \
|
&& python3.8 -m pip install -U pip \
|
||||||
&& python3.7 -m pip install -U wheel setuptools \
|
&& python3.8 -m pip install -U wheel setuptools \
|
||||||
&& python3.7 -m pip install -U \
|
&& python3.8 -m pip install -U \
|
||||||
opencv-python-headless \
|
opencv-python-headless \
|
||||||
# python-prctl \
|
# python-prctl \
|
||||||
numpy \
|
numpy \
|
||||||
imutils \
|
imutils \
|
||||||
scipy \
|
scipy \
|
||||||
psutil \
|
psutil \
|
||||||
&& python3.7 -m pip install -U \
|
&& python3.8 -m pip install -U \
|
||||||
Flask \
|
Flask \
|
||||||
paho-mqtt \
|
paho-mqtt \
|
||||||
PyYAML \
|
PyYAML \
|
||||||
matplotlib \
|
matplotlib \
|
||||||
pyarrow \
|
|
||||||
click \
|
click \
|
||||||
&& echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" > /etc/apt/sources.list.d/coral-edgetpu.list \
|
&& echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" > /etc/apt/sources.list.d/coral-edgetpu.list \
|
||||||
&& wget -q -O - https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - \
|
&& wget -q -O - https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - \
|
||||||
@ -39,10 +38,10 @@ RUN apt -qq update && apt -qq install --no-install-recommends -y \
|
|||||||
&& echo "libedgetpu1-max libedgetpu/accepted-eula boolean true" | debconf-set-selections \
|
&& echo "libedgetpu1-max libedgetpu/accepted-eula boolean true" | debconf-set-selections \
|
||||||
&& apt -qq install --no-install-recommends -y \
|
&& apt -qq install --no-install-recommends -y \
|
||||||
libedgetpu1-max \
|
libedgetpu1-max \
|
||||||
## Tensorflow lite (python 3.7 only)
|
## Tensorflow lite
|
||||||
&& wget -q https://dl.google.com/coral/python/tflite_runtime-2.1.0.post1-cp37-cp37m-linux_x86_64.whl \
|
&& wget -q https://dl.google.com/coral/python/tflite_runtime-2.1.0.post1-cp38-cp38-linux_x86_64.whl \
|
||||||
&& python3.7 -m pip install tflite_runtime-2.1.0.post1-cp37-cp37m-linux_x86_64.whl \
|
&& python3.8 -m pip install tflite_runtime-2.1.0.post1-cp38-cp38-linux_x86_64.whl \
|
||||||
&& rm tflite_runtime-2.1.0.post1-cp37-cp37m-linux_x86_64.whl \
|
&& rm tflite_runtime-2.1.0.post1-cp38-cp38-linux_x86_64.whl \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& (apt-get autoremove -y; apt-get autoclean -y)
|
&& (apt-get autoremove -y; apt-get autoclean -y)
|
||||||
|
|
||||||
@ -60,4 +59,4 @@ COPY detect_objects.py .
|
|||||||
COPY benchmark.py .
|
COPY benchmark.py .
|
||||||
COPY process_clip.py .
|
COPY process_clip.py .
|
||||||
|
|
||||||
CMD ["python3.7", "-u", "detect_objects.py"]
|
CMD ["python3.8", "-u", "detect_objects.py"]
|
||||||
|
@ -63,23 +63,13 @@ WEB_PORT = CONFIG.get('web_port', 5000)
|
|||||||
DEBUG = (CONFIG.get('debug', '0') == '1')
|
DEBUG = (CONFIG.get('debug', '0') == '1')
|
||||||
TENSORFLOW_DEVICE = CONFIG.get('tensorflow_device')
|
TENSORFLOW_DEVICE = CONFIG.get('tensorflow_device')
|
||||||
|
|
||||||
def start_plasma_store():
|
|
||||||
plasma_cmd = ['plasma_store', '-m', '400000000', '-s', '/tmp/plasma']
|
|
||||||
plasma_process = sp.Popen(plasma_cmd, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
|
|
||||||
time.sleep(1)
|
|
||||||
rc = plasma_process.poll()
|
|
||||||
if rc is not None:
|
|
||||||
return None
|
|
||||||
return plasma_process
|
|
||||||
|
|
||||||
class CameraWatchdog(threading.Thread):
|
class CameraWatchdog(threading.Thread):
|
||||||
def __init__(self, camera_processes, config, tflite_process, tracked_objects_queue, plasma_process, stop_event):
|
def __init__(self, camera_processes, config, tflite_process, tracked_objects_queue, stop_event):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.camera_processes = camera_processes
|
self.camera_processes = camera_processes
|
||||||
self.config = config
|
self.config = config
|
||||||
self.tflite_process = tflite_process
|
self.tflite_process = tflite_process
|
||||||
self.tracked_objects_queue = tracked_objects_queue
|
self.tracked_objects_queue = tracked_objects_queue
|
||||||
self.plasma_process = plasma_process
|
|
||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -94,12 +84,6 @@ class CameraWatchdog(threading.Thread):
|
|||||||
|
|
||||||
now = datetime.datetime.now().timestamp()
|
now = datetime.datetime.now().timestamp()
|
||||||
|
|
||||||
# check the plasma process
|
|
||||||
rc = self.plasma_process.poll()
|
|
||||||
if rc != None:
|
|
||||||
print(f"plasma_process exited unexpectedly with {rc}")
|
|
||||||
self.plasma_process = start_plasma_store()
|
|
||||||
|
|
||||||
# check the detection process
|
# check the detection process
|
||||||
detection_start = self.tflite_process.detection_start.value
|
detection_start = self.tflite_process.detection_start.value
|
||||||
if (detection_start > 0.0 and
|
if (detection_start > 0.0 and
|
||||||
@ -172,8 +156,6 @@ def main():
|
|||||||
client.connect(MQTT_HOST, MQTT_PORT, 60)
|
client.connect(MQTT_HOST, MQTT_PORT, 60)
|
||||||
client.loop_start()
|
client.loop_start()
|
||||||
|
|
||||||
plasma_process = start_plasma_store()
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Setup config defaults for cameras
|
# Setup config defaults for cameras
|
||||||
##
|
##
|
||||||
@ -190,10 +172,15 @@ def main():
|
|||||||
# Queue for clip processing
|
# Queue for clip processing
|
||||||
event_queue = mp.Queue()
|
event_queue = mp.Queue()
|
||||||
|
|
||||||
# Start the shared tflite process
|
# create the detection pipes
|
||||||
tflite_process = EdgeTPUProcess(TENSORFLOW_DEVICE)
|
detection_pipes = {}
|
||||||
|
for name in CONFIG['cameras'].keys():
|
||||||
|
detection_pipes[name] = mp.Pipe(duplex=False)
|
||||||
|
|
||||||
# start the camera processes
|
# Start the shared tflite process
|
||||||
|
tflite_process = EdgeTPUProcess(result_connections={ key:value[1] for (key,value) in detection_pipes.items() }, tf_device=TENSORFLOW_DEVICE)
|
||||||
|
|
||||||
|
# create the camera processes
|
||||||
camera_processes = {}
|
camera_processes = {}
|
||||||
for name, config in CONFIG['cameras'].items():
|
for name, config in CONFIG['cameras'].items():
|
||||||
# Merge the ffmpeg config with the global config
|
# Merge the ffmpeg config with the global config
|
||||||
@ -237,6 +224,8 @@ def main():
|
|||||||
else:
|
else:
|
||||||
frame_shape = get_frame_shape(ffmpeg_input)
|
frame_shape = get_frame_shape(ffmpeg_input)
|
||||||
|
|
||||||
|
config['frame_shape'] = frame_shape
|
||||||
|
|
||||||
frame_size = frame_shape[0] * frame_shape[1] * frame_shape[2]
|
frame_size = frame_shape[0] * frame_shape[1] * frame_shape[2]
|
||||||
take_frame = config.get('take_frame', 1)
|
take_frame = config.get('take_frame', 1)
|
||||||
|
|
||||||
@ -275,12 +264,13 @@ def main():
|
|||||||
}
|
}
|
||||||
|
|
||||||
camera_process = mp.Process(target=track_camera, args=(name, config, frame_queue, frame_shape,
|
camera_process = mp.Process(target=track_camera, args=(name, config, frame_queue, frame_shape,
|
||||||
tflite_process.detection_queue, tracked_objects_queue, camera_processes[name]['process_fps'],
|
tflite_process.detection_queue, detection_pipes[name][0], tracked_objects_queue, camera_processes[name]['process_fps'],
|
||||||
camera_processes[name]['detection_fps'],
|
camera_processes[name]['detection_fps'],
|
||||||
camera_processes[name]['read_start'], camera_processes[name]['detection_frame'], stop_event))
|
camera_processes[name]['read_start'], camera_processes[name]['detection_frame'], stop_event))
|
||||||
camera_process.daemon = True
|
camera_process.daemon = True
|
||||||
camera_processes[name]['process'] = camera_process
|
camera_processes[name]['process'] = camera_process
|
||||||
|
|
||||||
|
# start the camera_processes
|
||||||
for name, camera_process in camera_processes.items():
|
for name, camera_process in camera_processes.items():
|
||||||
camera_process['process'].start()
|
camera_process['process'].start()
|
||||||
print(f"Camera_process started for {name}: {camera_process['process'].pid}")
|
print(f"Camera_process started for {name}: {camera_process['process'].pid}")
|
||||||
@ -291,7 +281,7 @@ def main():
|
|||||||
object_processor = TrackedObjectProcessor(CONFIG['cameras'], client, MQTT_TOPIC_PREFIX, tracked_objects_queue, event_queue, stop_event)
|
object_processor = TrackedObjectProcessor(CONFIG['cameras'], client, MQTT_TOPIC_PREFIX, tracked_objects_queue, event_queue, stop_event)
|
||||||
object_processor.start()
|
object_processor.start()
|
||||||
|
|
||||||
camera_watchdog = CameraWatchdog(camera_processes, CONFIG['cameras'], tflite_process, tracked_objects_queue, plasma_process, stop_event)
|
camera_watchdog = CameraWatchdog(camera_processes, CONFIG['cameras'], tflite_process, tracked_objects_queue, stop_event)
|
||||||
camera_watchdog.start()
|
camera_watchdog.start()
|
||||||
|
|
||||||
def receiveSignal(signalNumber, frame):
|
def receiveSignal(signalNumber, frame):
|
||||||
@ -300,11 +290,9 @@ def main():
|
|||||||
event_processor.join()
|
event_processor.join()
|
||||||
object_processor.join()
|
object_processor.join()
|
||||||
camera_watchdog.join()
|
camera_watchdog.join()
|
||||||
for name, camera_process in camera_processes.items():
|
for camera_process in camera_processes.values():
|
||||||
camera_process['capture_thread'].join()
|
camera_process['capture_thread'].join()
|
||||||
rc = camera_watchdog.plasma_process.poll()
|
tflite_process.stop()
|
||||||
if rc == None:
|
|
||||||
camera_watchdog.plasma_process.terminate()
|
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, receiveSignal)
|
signal.signal(signal.SIGTERM, receiveSignal)
|
||||||
@ -368,9 +356,6 @@ def main():
|
|||||||
'pid': tflite_process.detect_process.pid
|
'pid': tflite_process.detect_process.pid
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = camera_watchdog.plasma_process.poll()
|
|
||||||
stats['plasma_store_rc'] = rc
|
|
||||||
|
|
||||||
return jsonify(stats)
|
return jsonify(stats)
|
||||||
|
|
||||||
@app.route('/<camera_name>/<label>/best.jpg')
|
@app.route('/<camera_name>/<label>/best.jpg')
|
||||||
@ -449,7 +434,5 @@ def main():
|
|||||||
|
|
||||||
object_processor.join()
|
object_processor.join()
|
||||||
|
|
||||||
plasma_process.terminate()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
@ -2,12 +2,14 @@ import os
|
|||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
|
import queue
|
||||||
|
from multiprocessing.connection import Connection
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Dict
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyarrow.plasma as plasma
|
|
||||||
import tflite_runtime.interpreter as tflite
|
import tflite_runtime.interpreter as tflite
|
||||||
from tflite_runtime.interpreter import load_delegate
|
from tflite_runtime.interpreter import load_delegate
|
||||||
from frigate.util import EventsPerSecond, listen
|
from frigate.util import EventsPerSecond, listen, SharedMemoryFrameManager
|
||||||
|
|
||||||
def load_labels(path, encoding='utf-8'):
|
def load_labels(path, encoding='utf-8'):
|
||||||
"""Loads labels from file (with or without index numbers).
|
"""Loads labels from file (with or without index numbers).
|
||||||
@ -100,32 +102,33 @@ class LocalObjectDetector(ObjectDetector):
|
|||||||
|
|
||||||
return detections
|
return detections
|
||||||
|
|
||||||
def run_detector(detection_queue, avg_speed, start, tf_device):
|
def run_detector(detection_queue, result_connections: Dict[str, Connection], avg_speed, start, tf_device):
|
||||||
print(f"Starting detection process: {os.getpid()}")
|
print(f"Starting detection process: {os.getpid()}")
|
||||||
listen()
|
listen()
|
||||||
plasma_client = plasma.connect("/tmp/plasma")
|
frame_manager = SharedMemoryFrameManager()
|
||||||
object_detector = LocalObjectDetector(tf_device=tf_device)
|
object_detector = LocalObjectDetector(tf_device=tf_device)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
object_id_str = detection_queue.get()
|
connection_id = detection_queue.get()
|
||||||
object_id_hash = hashlib.sha1(str.encode(object_id_str))
|
input_frame = frame_manager.get(connection_id, (1,300,300,3))
|
||||||
object_id = plasma.ObjectID(object_id_hash.digest())
|
|
||||||
object_id_out = plasma.ObjectID(hashlib.sha1(str.encode(f"out-{object_id_str}")).digest())
|
|
||||||
input_frame = plasma_client.get(object_id, timeout_ms=0)
|
|
||||||
|
|
||||||
if input_frame is plasma.ObjectNotAvailable:
|
if input_frame is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# detect and put the output in the plasma store
|
# detect and put the output in the plasma store
|
||||||
start.value = datetime.datetime.now().timestamp()
|
start.value = datetime.datetime.now().timestamp()
|
||||||
plasma_client.put(object_detector.detect_raw(input_frame), object_id_out)
|
# TODO: what is the overhead for pickling this result vs writing back to shared memory?
|
||||||
|
# I could try using an Event() and waiting in the other process before looking in memory...
|
||||||
|
detections = object_detector.detect_raw(input_frame)
|
||||||
|
result_connections[connection_id].send(detections)
|
||||||
duration = datetime.datetime.now().timestamp()-start.value
|
duration = datetime.datetime.now().timestamp()-start.value
|
||||||
start.value = 0.0
|
start.value = 0.0
|
||||||
|
|
||||||
avg_speed.value = (avg_speed.value*9 + duration)/10
|
avg_speed.value = (avg_speed.value*9 + duration)/10
|
||||||
|
|
||||||
class EdgeTPUProcess():
|
class EdgeTPUProcess():
|
||||||
def __init__(self, tf_device=None):
|
def __init__(self, result_connections, tf_device=None):
|
||||||
|
self.result_connections = result_connections
|
||||||
self.detection_queue = mp.Queue()
|
self.detection_queue = mp.Queue()
|
||||||
self.avg_inference_speed = mp.Value('d', 0.01)
|
self.avg_inference_speed = mp.Value('d', 0.01)
|
||||||
self.detection_start = mp.Value('d', 0.0)
|
self.detection_start = mp.Value('d', 0.0)
|
||||||
@ -133,40 +136,43 @@ class EdgeTPUProcess():
|
|||||||
self.tf_device = tf_device
|
self.tf_device = tf_device
|
||||||
self.start_or_restart()
|
self.start_or_restart()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.detect_process.terminate()
|
||||||
|
print("Waiting for detection process to exit gracefully...")
|
||||||
|
self.detect_process.join(timeout=30)
|
||||||
|
if self.detect_process.exitcode is None:
|
||||||
|
print("Detection process didnt exit. Force killing...")
|
||||||
|
self.detect_process.kill()
|
||||||
|
self.detect_process.join()
|
||||||
|
|
||||||
def start_or_restart(self):
|
def start_or_restart(self):
|
||||||
self.detection_start.value = 0.0
|
self.detection_start.value = 0.0
|
||||||
if (not self.detect_process is None) and self.detect_process.is_alive():
|
if (not self.detect_process is None) and self.detect_process.is_alive():
|
||||||
self.detect_process.terminate()
|
self.stop()
|
||||||
print("Waiting for detection process to exit gracefully...")
|
self.detect_process = mp.Process(target=run_detector, args=(self.detection_queue, self.result_connections, self.avg_inference_speed, self.detection_start, self.tf_device))
|
||||||
self.detect_process.join(timeout=30)
|
|
||||||
if self.detect_process.exitcode is None:
|
|
||||||
print("Detection process didnt exit. Force killing...")
|
|
||||||
self.detect_process.kill()
|
|
||||||
self.detect_process.join()
|
|
||||||
self.detect_process = mp.Process(target=run_detector, args=(self.detection_queue, self.avg_inference_speed, self.detection_start, self.tf_device))
|
|
||||||
self.detect_process.daemon = True
|
self.detect_process.daemon = True
|
||||||
self.detect_process.start()
|
self.detect_process.start()
|
||||||
|
|
||||||
class RemoteObjectDetector():
|
class RemoteObjectDetector():
|
||||||
def __init__(self, name, labels, detection_queue):
|
def __init__(self, name, labels, detection_queue, result_connection: Connection):
|
||||||
self.labels = load_labels(labels)
|
self.labels = load_labels(labels)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.fps = EventsPerSecond()
|
self.fps = EventsPerSecond()
|
||||||
self.plasma_client = plasma.connect("/tmp/plasma")
|
|
||||||
self.detection_queue = detection_queue
|
self.detection_queue = detection_queue
|
||||||
|
self.result_connection = result_connection
|
||||||
|
self.shm = mp.shared_memory.SharedMemory(name=self.name, create=True, size=300*300*3)
|
||||||
|
self.np_shm = np.ndarray((1,300,300,3), dtype=np.uint8, buffer=self.shm.buf)
|
||||||
|
|
||||||
def detect(self, tensor_input, threshold=.4):
|
def detect(self, tensor_input, threshold=.4):
|
||||||
detections = []
|
detections = []
|
||||||
|
|
||||||
now = f"{self.name}-{str(datetime.datetime.now().timestamp())}"
|
# copy input to shared memory
|
||||||
object_id_frame = plasma.ObjectID(hashlib.sha1(str.encode(now)).digest())
|
# TODO: what if I just write it there in the first place?
|
||||||
object_id_detections = plasma.ObjectID(hashlib.sha1(str.encode(f"out-{now}")).digest())
|
self.np_shm[:] = tensor_input[:]
|
||||||
self.plasma_client.put(tensor_input, object_id_frame)
|
self.detection_queue.put(self.name)
|
||||||
self.detection_queue.put(now)
|
if self.result_connection.poll(10):
|
||||||
raw_detections = self.plasma_client.get(object_id_detections, timeout_ms=10000)
|
raw_detections = self.result_connection.recv()
|
||||||
|
else:
|
||||||
if raw_detections is plasma.ObjectNotAvailable:
|
|
||||||
self.plasma_client.delete([object_id_frame])
|
|
||||||
return detections
|
return detections
|
||||||
|
|
||||||
for d in raw_detections:
|
for d in raw_detections:
|
||||||
@ -177,6 +183,5 @@ class RemoteObjectDetector():
|
|||||||
float(d[1]),
|
float(d[1]),
|
||||||
(d[2], d[3], d[4], d[5])
|
(d[2], d[3], d[4], d[5])
|
||||||
))
|
))
|
||||||
self.plasma_client.delete([object_id_frame, object_id_detections])
|
|
||||||
self.fps.update()
|
self.fps.update()
|
||||||
return detections
|
return detections
|
||||||
|
@ -10,9 +10,8 @@ import copy
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
import itertools
|
import itertools
|
||||||
import pyarrow.plasma as plasma
|
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from frigate.util import draw_box_with_label, PlasmaFrameManager
|
from frigate.util import draw_box_with_label, SharedMemoryFrameManager
|
||||||
from frigate.edgetpu import load_labels
|
from frigate.edgetpu import load_labels
|
||||||
from typing import Callable, Dict
|
from typing import Callable, Dict
|
||||||
from statistics import mean, median
|
from statistics import mean, median
|
||||||
@ -59,7 +58,7 @@ class CameraState():
|
|||||||
self.object_status = defaultdict(lambda: 'OFF')
|
self.object_status = defaultdict(lambda: 'OFF')
|
||||||
self.tracked_objects = {}
|
self.tracked_objects = {}
|
||||||
self.zone_objects = defaultdict(lambda: [])
|
self.zone_objects = defaultdict(lambda: [])
|
||||||
self.current_frame = np.zeros((720,1280,3), np.uint8)
|
self.current_frame = np.zeros(self.config['frame_shape'], np.uint8)
|
||||||
self.current_frame_time = 0.0
|
self.current_frame_time = 0.0
|
||||||
self.previous_frame_id = None
|
self.previous_frame_id = None
|
||||||
self.callbacks = defaultdict(lambda: [])
|
self.callbacks = defaultdict(lambda: [])
|
||||||
@ -88,7 +87,7 @@ class CameraState():
|
|||||||
self.current_frame_time = frame_time
|
self.current_frame_time = frame_time
|
||||||
# get the new frame and delete the old frame
|
# get the new frame and delete the old frame
|
||||||
frame_id = f"{self.name}{frame_time}"
|
frame_id = f"{self.name}{frame_time}"
|
||||||
self.current_frame = self.frame_manager.get(frame_id)
|
self.current_frame = self.frame_manager.get(frame_id, self.config['frame_shape'])
|
||||||
if not self.previous_frame_id is None:
|
if not self.previous_frame_id is None:
|
||||||
self.frame_manager.delete(self.previous_frame_id)
|
self.frame_manager.delete(self.previous_frame_id)
|
||||||
self.previous_frame_id = frame_id
|
self.previous_frame_id = frame_id
|
||||||
@ -238,7 +237,7 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
self.event_queue = event_queue
|
self.event_queue = event_queue
|
||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
self.camera_states: Dict[str, CameraState] = {}
|
self.camera_states: Dict[str, CameraState] = {}
|
||||||
self.plasma_client = PlasmaFrameManager(self.stop_event)
|
self.frame_manager = SharedMemoryFrameManager()
|
||||||
|
|
||||||
def start(camera, obj):
|
def start(camera, obj):
|
||||||
# publish events to mqtt
|
# publish events to mqtt
|
||||||
@ -273,7 +272,7 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
self.client.publish(f"{self.topic_prefix}/{camera}/{object_name}", status, retain=False)
|
self.client.publish(f"{self.topic_prefix}/{camera}/{object_name}", status, retain=False)
|
||||||
|
|
||||||
for camera in self.camera_config.keys():
|
for camera in self.camera_config.keys():
|
||||||
camera_state = CameraState(camera, self.camera_config[camera], self.plasma_client)
|
camera_state = CameraState(camera, self.camera_config[camera], self.frame_manager)
|
||||||
camera_state.on('start', start)
|
camera_state.on('start', start)
|
||||||
camera_state.on('update', update)
|
camera_state.on('update', update)
|
||||||
camera_state.on('end', end)
|
camera_state.on('end', end)
|
||||||
|
@ -9,7 +9,8 @@ import cv2
|
|||||||
import threading
|
import threading
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import hashlib
|
import hashlib
|
||||||
import pyarrow.plasma as plasma
|
from multiprocessing import shared_memory
|
||||||
|
from typing import AnyStr
|
||||||
|
|
||||||
def draw_box_with_label(frame, x_min, y_min, x_max, y_max, label, info, thickness=2, color=None, position='ul'):
|
def draw_box_with_label(frame, x_min, y_min, x_max, y_max, label, info, thickness=2, color=None, position='ul'):
|
||||||
if color is None:
|
if color is None:
|
||||||
@ -148,12 +149,16 @@ def listen():
|
|||||||
signal.signal(signal.SIGUSR1, print_stack)
|
signal.signal(signal.SIGUSR1, print_stack)
|
||||||
|
|
||||||
class FrameManager(ABC):
|
class FrameManager(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def create(self, name, size) -> AnyStr:
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get(self, name, timeout_ms=0):
|
def get(self, name, timeout_ms=0):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def put(self, name, frame):
|
def close(self, name):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -164,66 +169,45 @@ class DictFrameManager(FrameManager):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.frames = {}
|
self.frames = {}
|
||||||
|
|
||||||
def get(self, name, timeout_ms=0):
|
def create(self, name, size) -> AnyStr:
|
||||||
return self.frames.get(name)
|
mem = bytearray(size)
|
||||||
|
self.frames[name] = mem
|
||||||
|
return mem
|
||||||
|
|
||||||
def put(self, name, frame):
|
def get(self, name, shape):
|
||||||
self.frames[name] = frame
|
mem = self.frames[name]
|
||||||
|
return np.ndarray(shape, dtype=np.uint8, buffer=mem)
|
||||||
|
|
||||||
|
def close(self, name):
|
||||||
|
pass
|
||||||
|
|
||||||
def delete(self, name):
|
def delete(self, name):
|
||||||
del self.frames[name]
|
del self.frames[name]
|
||||||
|
|
||||||
class PlasmaFrameManager(FrameManager):
|
class SharedMemoryFrameManager(FrameManager):
|
||||||
def __init__(self, stop_event=None):
|
def __init__(self):
|
||||||
self.stop_event = stop_event
|
self.shm_store = {}
|
||||||
self.connect()
|
|
||||||
|
|
||||||
def connect(self):
|
def create(self, name, size) -> AnyStr:
|
||||||
while True:
|
shm = shared_memory.SharedMemory(name=name, create=True, size=size)
|
||||||
if self.stop_event != None and self.stop_event.is_set():
|
self.shm_store[name] = shm
|
||||||
return
|
return shm.buf
|
||||||
try:
|
|
||||||
self.plasma_client = plasma.connect("/tmp/plasma")
|
|
||||||
return
|
|
||||||
except:
|
|
||||||
print(f"TrackedObjectProcessor: unable to connect plasma client")
|
|
||||||
time.sleep(10)
|
|
||||||
|
|
||||||
def get(self, name, timeout_ms=0):
|
def get(self, name, shape):
|
||||||
object_id = plasma.ObjectID(hashlib.sha1(str.encode(name)).digest())
|
if name in self.shm_store:
|
||||||
while True:
|
shm = self.shm_store[name]
|
||||||
if self.stop_event != None and self.stop_event.is_set():
|
else:
|
||||||
return
|
shm = shared_memory.SharedMemory(name=name)
|
||||||
try:
|
self.shm_store[name] = shm
|
||||||
frame = self.plasma_client.get(object_id, timeout_ms=timeout_ms)
|
return np.ndarray(shape, dtype=np.uint8, buffer=shm.buf)
|
||||||
if frame is plasma.ObjectNotAvailable:
|
|
||||||
return None
|
|
||||||
return frame
|
|
||||||
except:
|
|
||||||
self.connect()
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
def put(self, name, frame):
|
def close(self, name):
|
||||||
object_id = plasma.ObjectID(hashlib.sha1(str.encode(name)).digest())
|
if name in self.shm_store:
|
||||||
while True:
|
self.shm_store[name].close()
|
||||||
if self.stop_event != None and self.stop_event.is_set():
|
del self.shm_store[name]
|
||||||
return
|
|
||||||
try:
|
|
||||||
self.plasma_client.put(frame, object_id)
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to put in plasma: {e}")
|
|
||||||
self.connect()
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
def delete(self, name):
|
def delete(self, name):
|
||||||
object_id = plasma.ObjectID(hashlib.sha1(str.encode(name)).digest())
|
if name in self.shm_store:
|
||||||
while True:
|
self.shm_store[name].close()
|
||||||
if self.stop_event != None and self.stop_event.is_set():
|
self.shm_store[name].unlink()
|
||||||
return
|
del self.shm_store[name]
|
||||||
try:
|
|
||||||
self.plasma_client.delete([object_id])
|
|
||||||
return
|
|
||||||
except:
|
|
||||||
self.connect()
|
|
||||||
time.sleep(1)
|
|
@ -5,7 +5,6 @@ import cv2
|
|||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
import ctypes
|
import ctypes
|
||||||
import pyarrow.plasma as plasma
|
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -15,7 +14,7 @@ import json
|
|||||||
import base64
|
import base64
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from frigate.util import draw_box_with_label, area, calculate_region, clipped, intersection_over_union, intersection, EventsPerSecond, listen, FrameManager, PlasmaFrameManager
|
from frigate.util import draw_box_with_label, area, calculate_region, clipped, intersection_over_union, intersection, EventsPerSecond, listen, FrameManager, SharedMemoryFrameManager
|
||||||
from frigate.objects import ObjectTracker
|
from frigate.objects import ObjectTracker
|
||||||
from frigate.edgetpu import RemoteObjectDetector
|
from frigate.edgetpu import RemoteObjectDetector
|
||||||
from frigate.motion import MotionDetector
|
from frigate.motion import MotionDetector
|
||||||
@ -154,11 +153,10 @@ def capture_frames(ffmpeg_process, camera_name, frame_shape, frame_manager: Fram
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# put the frame in the frame manager
|
# put the frame in the frame manager
|
||||||
frame_manager.put(f"{camera_name}{current_frame.value}",
|
frame_buffer = frame_manager.create(f"{camera_name}{current_frame.value}", frame_size)
|
||||||
np
|
frame_buffer[:] = frame_bytes[:]
|
||||||
.frombuffer(frame_bytes, np.uint8)
|
frame_manager.close(f"{camera_name}{current_frame.value}")
|
||||||
.reshape(frame_shape)
|
|
||||||
)
|
|
||||||
# add to the queue
|
# add to the queue
|
||||||
frame_queue.put(current_frame.value)
|
frame_queue.put(current_frame.value)
|
||||||
last_frame = current_frame.value
|
last_frame = current_frame.value
|
||||||
@ -173,7 +171,7 @@ class CameraCapture(threading.Thread):
|
|||||||
self.take_frame = take_frame
|
self.take_frame = take_frame
|
||||||
self.fps = fps
|
self.fps = fps
|
||||||
self.skipped_fps = EventsPerSecond()
|
self.skipped_fps = EventsPerSecond()
|
||||||
self.plasma_client = PlasmaFrameManager(stop_event)
|
self.frame_manager = SharedMemoryFrameManager()
|
||||||
self.ffmpeg_process = ffmpeg_process
|
self.ffmpeg_process = ffmpeg_process
|
||||||
self.current_frame = mp.Value('d', 0.0)
|
self.current_frame = mp.Value('d', 0.0)
|
||||||
self.last_frame = 0
|
self.last_frame = 0
|
||||||
@ -182,10 +180,10 @@ class CameraCapture(threading.Thread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.skipped_fps.start()
|
self.skipped_fps.start()
|
||||||
capture_frames(self.ffmpeg_process, self.name, self.frame_shape, self.plasma_client, self.frame_queue, self.take_frame,
|
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.detection_frame, self.current_frame)
|
||||||
|
|
||||||
def track_camera(name, config, frame_queue, frame_shape, detection_queue, detected_objects_queue, fps, detection_fps, read_start, detection_frame, stop_event):
|
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()}")
|
print(f"Starting process for {name}: {os.getpid()}")
|
||||||
listen()
|
listen()
|
||||||
|
|
||||||
@ -218,13 +216,13 @@ def track_camera(name, config, frame_queue, frame_shape, detection_queue, detect
|
|||||||
mask[:] = 255
|
mask[:] = 255
|
||||||
|
|
||||||
motion_detector = MotionDetector(frame_shape, mask, resize_factor=6)
|
motion_detector = MotionDetector(frame_shape, mask, resize_factor=6)
|
||||||
object_detector = RemoteObjectDetector(name, '/labelmap.txt', detection_queue)
|
object_detector = RemoteObjectDetector(name, '/labelmap.txt', detection_queue, result_connection)
|
||||||
|
|
||||||
object_tracker = ObjectTracker(10)
|
object_tracker = ObjectTracker(10)
|
||||||
|
|
||||||
plasma_client = PlasmaFrameManager()
|
frame_manager = SharedMemoryFrameManager()
|
||||||
|
|
||||||
process_frames(name, frame_queue, frame_shape, plasma_client, motion_detector, object_detector,
|
process_frames(name, frame_queue, frame_shape, frame_manager, motion_detector, object_detector,
|
||||||
object_tracker, detected_objects_queue, fps, detection_fps, detection_frame, objects_to_track, object_filters, mask, stop_event)
|
object_tracker, detected_objects_queue, fps, detection_fps, detection_frame, objects_to_track, object_filters, mask, stop_event)
|
||||||
|
|
||||||
print(f"{name}: exiting subprocess")
|
print(f"{name}: exiting subprocess")
|
||||||
@ -281,7 +279,7 @@ def process_frames(camera_name: str, frame_queue: mp.Queue, frame_shape,
|
|||||||
|
|
||||||
current_frame_time.value = frame_time
|
current_frame_time.value = frame_time
|
||||||
|
|
||||||
frame = frame_manager.get(f"{camera_name}{frame_time}")
|
frame = frame_manager.get(f"{camera_name}{frame_time}", frame_shape)
|
||||||
|
|
||||||
if frame is None:
|
if frame is None:
|
||||||
print(f"{camera_name}: frame {frame_time} is not in memory store.")
|
print(f"{camera_name}: frame {frame_time} is not in memory store.")
|
||||||
@ -364,3 +362,5 @@ def process_frames(camera_name: str, frame_queue: mp.Queue, frame_shape,
|
|||||||
detected_objects_queue.put((camera_name, frame_time, object_tracker.tracked_objects))
|
detected_objects_queue.put((camera_name, frame_time, object_tracker.tracked_objects))
|
||||||
|
|
||||||
detection_fps.value = object_detector.fps.eps()
|
detection_fps.value = object_detector.fps.eps()
|
||||||
|
|
||||||
|
frame_manager.close(f"{camera_name}{frame_time}")
|
||||||
|
@ -4,7 +4,7 @@ import os
|
|||||||
import datetime
|
import datetime
|
||||||
from unittest import TestCase, main
|
from unittest import TestCase, main
|
||||||
from frigate.video import process_frames, start_or_restart_ffmpeg, capture_frames, get_frame_shape
|
from frigate.video import process_frames, start_or_restart_ffmpeg, capture_frames, get_frame_shape
|
||||||
from frigate.util import DictFrameManager, EventsPerSecond, draw_box_with_label
|
from frigate.util import DictFrameManager, SharedMemoryFrameManager, EventsPerSecond, draw_box_with_label
|
||||||
from frigate.motion import MotionDetector
|
from frigate.motion import MotionDetector
|
||||||
from frigate.edgetpu import LocalObjectDetector
|
from frigate.edgetpu import LocalObjectDetector
|
||||||
from frigate.objects import ObjectTracker
|
from frigate.objects import ObjectTracker
|
||||||
@ -19,6 +19,7 @@ class ProcessClip():
|
|||||||
self.frame_shape = frame_shape
|
self.frame_shape = frame_shape
|
||||||
self.camera_name = 'camera'
|
self.camera_name = 'camera'
|
||||||
self.frame_manager = DictFrameManager()
|
self.frame_manager = DictFrameManager()
|
||||||
|
# self.frame_manager = SharedMemoryFrameManager()
|
||||||
self.frame_queue = mp.Queue()
|
self.frame_queue = mp.Queue()
|
||||||
self.detected_objects_queue = mp.Queue()
|
self.detected_objects_queue = mp.Queue()
|
||||||
self.camera_state = CameraState(self.camera_name, config, self.frame_manager)
|
self.camera_state = CameraState(self.camera_name, config, self.frame_manager)
|
||||||
@ -72,13 +73,15 @@ class ProcessClip():
|
|||||||
for obj in self.camera_state.tracked_objects.values():
|
for obj in self.camera_state.tracked_objects.values():
|
||||||
print(f"{frame_time}: {obj['id']} - {obj['computed_score']} - {obj['score_history']}")
|
print(f"{frame_time}: {obj['id']} - {obj['computed_score']} - {obj['score_history']}")
|
||||||
|
|
||||||
|
self.frame_manager.delete(self.camera_state.previous_frame_id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'object_detected': obj_detected,
|
'object_detected': obj_detected,
|
||||||
'top_score': top_computed_score
|
'top_score': top_computed_score
|
||||||
}
|
}
|
||||||
|
|
||||||
def save_debug_frame(self, debug_path, frame_time, tracked_objects):
|
def save_debug_frame(self, debug_path, frame_time, tracked_objects):
|
||||||
current_frame = self.frame_manager.get(f"{self.camera_name}{frame_time}")
|
current_frame = self.frame_manager.get(f"{self.camera_name}{frame_time}", self.frame_shape)
|
||||||
# draw the bounding boxes on the frame
|
# draw the bounding boxes on the frame
|
||||||
for obj in tracked_objects:
|
for obj in tracked_objects:
|
||||||
thickness = 2
|
thickness = 2
|
||||||
@ -132,6 +135,7 @@ def process(path, label, threshold, debug_path):
|
|||||||
results = []
|
results = []
|
||||||
for c in clips:
|
for c in clips:
|
||||||
frame_shape = get_frame_shape(c)
|
frame_shape = get_frame_shape(c)
|
||||||
|
config['frame_shape'] = frame_shape
|
||||||
process_clip = ProcessClip(c, frame_shape, config)
|
process_clip = ProcessClip(c, frame_shape, config)
|
||||||
process_clip.load_frames()
|
process_clip.load_frames()
|
||||||
process_clip.process_frames(objects_to_track=config['objects']['track'])
|
process_clip.process_frames(objects_to_track=config['objects']['track'])
|
||||||
|
Loading…
Reference in New Issue
Block a user