Переглянути джерело

update docs and add back benchmark

Blake Blackshear 5 роки тому
батько
коміт
e6892d66b8
6 змінених файлів з 62 додано та 104 видалено
  1. 1 0
      Dockerfile
  2. 43 33
      README.md
  3. 9 11
      benchmark.py
  4. 9 25
      config/config.example.yml
  5. BIN
      diagram.png
  6. 0 35
      frigate/video.py

+ 1 - 0
Dockerfile

@@ -55,5 +55,6 @@ RUN wget -q https://storage.googleapis.com/download.tensorflow.org/models/tflite
 WORKDIR /opt/frigate/
 WORKDIR /opt/frigate/
 ADD frigate frigate/
 ADD frigate frigate/
 COPY detect_objects.py .
 COPY detect_objects.py .
+COPY benchmark.py .
 
 
 CMD ["python3.7", "-u", "detect_objects.py"]
 CMD ["python3.7", "-u", "detect_objects.py"]

+ 43 - 33
README.md

@@ -1,14 +1,13 @@
 # Frigate - Realtime Object Detection for IP Cameras
 # Frigate - Realtime Object Detection for IP Cameras
-**Note:** This version requires the use of a [Google Coral USB Accelerator](https://coral.withgoogle.com/products/accelerator/)
-
 Uses OpenCV and Tensorflow to perform realtime object detection locally for IP cameras. Designed for integration with HomeAssistant or others via MQTT.
 Uses OpenCV and Tensorflow to perform realtime object detection locally for IP cameras. Designed for integration with HomeAssistant or others via MQTT.
 
 
-- Leverages multiprocessing and threads heavily with an emphasis on realtime over processing every frame
-- Allows you to define specific regions (squares) in the image to look for objects
-- No motion detection (for now)
-- Object detection with Tensorflow runs in a separate thread
+Use of a [Google Coral USB Accelerator](https://coral.withgoogle.com/products/accelerator/) is optional, but highly recommended. On my Intel i7 processor, I can process 2-3 FPS with the CPU. The Coral can process 100+ FPS with very low CPU load.
+
+- Leverages multiprocessing heavily with an emphasis on realtime over processing every frame
+- Uses a very low overhead motion detection to determine where to run object detection
+- Object detection with Tensorflow runs in a separate process
 - Object info is published over MQTT for integration into HomeAssistant as a binary sensor
 - Object info is published over MQTT for integration into HomeAssistant as a binary sensor
-- An endpoint is available to view an MJPEG stream for debugging
+- An endpoint is available to view an MJPEG stream for debugging, but should not be used continuously
 
 
 ![Diagram](diagram.png)
 ![Diagram](diagram.png)
 
 
@@ -22,12 +21,16 @@ Build the container with
 docker build -t frigate .
 docker build -t frigate .
 ```
 ```
 
 
-The `mobilenet_ssd_v2_coco_quant_postprocess_edgetpu.tflite` model is included and used by default. You can use your own model and labels by mounting files in the container at `/frozen_inference_graph.pb` and `/label_map.pbtext`. Models must be compatible with the Coral according to [this](https://coral.withgoogle.com/models/).
+Models for both CPU and EdgeTPU (Coral) are bundled in the image. You can use your own models with volume mounts:
+- CPU Model: `/cpu_model.tflite`
+- EdgeTPU Model: `/edgetpu_model.tflite`
+- Labels: `/labelmap.txt`
 
 
 Run the container with
 Run the container with
-```
+```bash
 docker run --rm \
 docker run --rm \
 --privileged \
 --privileged \
+--shm-size=512m \ # should work for a 2-3 cameras
 -v /dev/bus/usb:/dev/bus/usb \
 -v /dev/bus/usb:/dev/bus/usb \
 -v <path_to_config_dir>:/config:ro \
 -v <path_to_config_dir>:/config:ro \
 -v /etc/localtime:/etc/localtime:ro \
 -v /etc/localtime:/etc/localtime:ro \
@@ -37,11 +40,12 @@ frigate:latest
 ```
 ```
 
 
 Example docker-compose:
 Example docker-compose:
-```
+```yaml
   frigate:
   frigate:
     container_name: frigate
     container_name: frigate
     restart: unless-stopped
     restart: unless-stopped
     privileged: true
     privileged: true
+    shm_size: '1g' # should work for 5-7 cameras
     image: frigate:latest
     image: frigate:latest
     volumes:
     volumes:
       - /dev/bus/usb:/dev/bus/usb
       - /dev/bus/usb:/dev/bus/usb
@@ -57,6 +61,8 @@ A `config.yml` file must exist in the `config` directory. See example [here](con
 
 
 Access the mjpeg stream at `http://localhost:5000/<camera_name>` and the best snapshot for any object type with at `http://localhost:5000/<camera_name>/<object_name>/best.jpg`
 Access the mjpeg stream at `http://localhost:5000/<camera_name>` and the best snapshot for any object type with at `http://localhost:5000/<camera_name>/<object_name>/best.jpg`
 
 
+Debug info is available at `http://localhost:5000/debug/stats`
+
 ## Integration with HomeAssistant
 ## Integration with HomeAssistant
 ```
 ```
 camera:
 camera:
@@ -93,30 +99,34 @@ automation:
             photo:
             photo:
               - url: http://<ip>:5000/<camera_name>/person/best.jpg
               - url: http://<ip>:5000/<camera_name>/person/best.jpg
                 caption: A person was detected.
                 caption: A person was detected.
+
+sensor:
+  - platform: rest
+    name: Frigate Debug
+    resource: http://localhost:5000/debug/stats
+    scan_interval: 5
+    json_attributes:
+      - back
+      - coral
+    value_template: 'OK'  
+  - platform: template
+    sensors:
+      back_fps: 
+        value_template: '{{ states.sensor.frigate_debug.attributes["back"]["fps"] }}'
+        unit_of_measurement: 'FPS'
+      back_skipped_fps: 
+        value_template: '{{ states.sensor.frigate_debug.attributes["back"]["skipped_fps"] }}'
+        unit_of_measurement: 'FPS'
+      back_detection_fps: 
+        value_template: '{{ states.sensor.frigate_debug.attributes["back"]["detection_fps"] }}'
+        unit_of_measurement: 'FPS'
+      frigate_coral_fps: 
+        value_template: '{{ states.sensor.frigate_debug.attributes["coral"]["fps"] }}'
+        unit_of_measurement: 'FPS'
+      frigate_coral_inference:
+        value_template: '{{ states.sensor.frigate_debug.attributes["coral"]["inference_speed"] }}' 
+        unit_of_measurement: 'ms'
 ```
 ```
 
 
 ## Tips
 ## Tips
 - Lower the framerate of the video feed on the camera to reduce the CPU usage for capturing the feed
 - Lower the framerate of the video feed on the camera to reduce the CPU usage for capturing the feed
-
-## Future improvements
-- [x] Remove motion detection for now
-- [x] Try running object detection in a thread rather than a process
-- [x] Implement min person size again
-- [x] Switch to a config file
-- [x] Handle multiple cameras in the same container
-- [ ] Attempt to figure out coral symlinking
-- [ ] Add object list to config with min scores for mqtt
-- [ ] Move mjpeg encoding to a separate process
-- [ ] Simplify motion detection (check entire image against mask, resize instead of gaussian blur)
-- [ ] See if motion detection is even worth running
-- [ ] Scan for people across entire image rather than specfic regions
-- [ ] Dynamically resize detection area and follow people
-- [ ] Add ability to turn detection on and off via MQTT
-- [ ] Output movie clips of people for notifications, etc.
-- [ ] Integrate with homeassistant push camera
-- [ ] Merge bounding boxes that span multiple regions
-- [ ] Implement mode to save labeled objects for training
-- [ ] Try and reduce CPU usage by simplifying the tensorflow model to just include the objects we care about
-- [ ] Look into GPU accelerated decoding of RTSP stream
-- [ ] Send video over a socket and use JSMPEG
-- [x] Look into neural compute stick

+ 9 - 11
benchmark.py

@@ -1,20 +1,18 @@
 import statistics
 import statistics
 import numpy as np
 import numpy as np
-from edgetpu.detection.engine import DetectionEngine
+import time
+from frigate.edgetpu import ObjectDetector
 
 
-# Path to frozen detection graph. This is the actual model that is used for the object detection.
-PATH_TO_CKPT = '/frozen_inference_graph.pb'
-
-# Load the edgetpu engine and labels
-engine = DetectionEngine(PATH_TO_CKPT)
+object_detector = ObjectDetector()
 
 
 frame = np.zeros((300,300,3), np.uint8)
 frame = np.zeros((300,300,3), np.uint8)
-flattened_frame = np.expand_dims(frame, axis=0).flatten()
+input_frame = np.expand_dims(frame, axis=0)
 
 
 detection_times = []
 detection_times = []
 
 
-for x in range(0, 1000):
-    objects = engine.detect_with_input_tensor(flattened_frame, threshold=0.1, top_k=3)
-    detection_times.append(engine.get_inference_time())
+for x in range(0, 100):
+    start = time.monotonic()
+    object_detector.detect_raw(input_frame)
+    detection_times.append(time.monotonic()-start)
 
 
-print("Average inference time: " + str(statistics.mean(detection_times)))
+print(f"Average inference time: {statistics.mean(detection_times)*1000:.2f}ms")

+ 9 - 25
config/config.example.yml

@@ -39,8 +39,6 @@ mqtt:
 #     - -use_wallclock_as_timestamps
 #     - -use_wallclock_as_timestamps
 #     - '1'
 #     - '1'
 #   output_args:
 #   output_args:
-#     - -vf
-#     - mpdecimate
 #     - -f
 #     - -f
 #     - rawvideo
 #     - rawvideo
 #     - -pix_fmt
 #     - -pix_fmt
@@ -89,12 +87,15 @@ cameras:
     # width: 720
     # width: 720
 
 
     ################
     ################
-    ## Optional mask. Must be the same dimensions as your video feed.
+    ## Optional mask. Must be the same aspect ratio as your video feed.
+    ## 
     ## The mask works by looking at the bottom center of the bounding box for the detected
     ## The mask works by looking at the bottom center of the bounding box for the detected
     ## person in the image. If that pixel in the mask is a black pixel, it ignores it as a
     ## person in the image. If that pixel in the mask is a black pixel, it ignores it as a
     ## false positive. In my mask, the grass and driveway visible from my backdoor camera 
     ## false positive. In my mask, the grass and driveway visible from my backdoor camera 
     ## are white. The garage doors, sky, and trees (anywhere it would be impossible for a 
     ## are white. The garage doors, sky, and trees (anywhere it would be impossible for a 
     ## person to stand) are black.
     ## person to stand) are black.
+    ## 
+    ## Masked areas are also ignored for motion detection.
     ################
     ################
     # mask: back-mask.bmp
     # mask: back-mask.bmp
 
 
@@ -106,13 +107,14 @@ cameras:
     take_frame: 1
     take_frame: 1
 
 
     ################
     ################
-    # The number of seconds frigate will allow a camera to go without sending a frame before
-    # assuming the ffmpeg process has a problem and restarting.
+    # The expected framerate for the camera. Frigate will try and ensure it maintains this framerate
+    # by dropping frames as necessary. Setting this lower than the actual framerate will allow frigate
+    # to process every frame at the expense of realtime processing.
     ################
     ################
-    # watchdog_timeout: 300
+    fps: 5
 
 
     ################
     ################
-    # Configuration for the snapshot sent over mqtt
+    # Configuration for the snapshots in the debug view and mqtt
     ################
     ################
     snapshots:
     snapshots:
       show_timestamp: True
       show_timestamp: True
@@ -128,21 +130,3 @@ cameras:
           min_area: 5000
           min_area: 5000
           max_area: 100000
           max_area: 100000
           threshold: 0.5
           threshold: 0.5
-    
-    ################
-    # size: size of the region in pixels
-    # x_offset/y_offset: position of the upper left corner of your region (top left of image is 0,0)
-    # Tips: All regions are resized to 300x300 before detection because the model is trained on that size.
-    #       Resizing regions takes CPU power. Ideally, all regions should be as close to 300x300 as possible.
-    #       Defining a region that goes outside the bounds of the image will result in errors.
-    ################
-    regions:
-      - size: 350
-        x_offset: 0
-        y_offset: 300
-      - size: 400
-        x_offset: 350
-        y_offset: 250
-      - size: 400
-        x_offset: 750
-        y_offset: 250


+ 0 - 35
frigate/video.py

@@ -350,38 +350,3 @@ def track_camera(name, config, ffmpeg_global_config, global_objects_config, dete
         plasma_client.put(frame, plasma.ObjectID(object_id))
         plasma_client.put(frame, plasma.ObjectID(object_id))
         # add to the queue
         # add to the queue
         detected_objects_queue.put((name, frame_time, object_tracker.tracked_objects))
         detected_objects_queue.put((name, frame_time, object_tracker.tracked_objects))
-
-        # if (frames >= 700 and frames <= 1635) or (frames >= 2500):
-        # if (frames >= 300 and frames <= 600):
-        # if (frames >= 0):
-            # row1 = cv2.hconcat([gray, cv2.convertScaleAbs(avg_frame)])
-            # row2 = cv2.hconcat([frameDelta, thresh])
-            # cv2.imwrite(f"/lab/debug/output/{frames}.jpg", cv2.vconcat([row1, row2]))
-            # # cv2.imwrite(f"/lab/debug/output/resized-frame-{frames}.jpg", resized_frame)
-            # for region in motion_regions:
-            #     cv2.rectangle(frame, (region[0], region[1]), (region[2], region[3]), (255,128,0), 2)
-            # for region in object_regions:
-            #     cv2.rectangle(frame, (region[0], region[1]), (region[2], region[3]), (0,128,255), 2)
-            # for region in merged_regions:
-            #     cv2.rectangle(frame, (region[0], region[1]), (region[2], region[3]), (0,255,0), 2)
-            # for box in motion_boxes:
-            #     cv2.rectangle(frame, (box[0], box[1]), (box[2], box[3]), (255,0,0), 2)
-            # for detection in detections:
-            #     box = detection[2]
-            #     draw_box_with_label(frame, box[0], box[1], box[2], box[3], detection[0], f"{detection[1]*100}%")
-            # for obj in object_tracker.tracked_objects.values():
-            #     box = obj['box']
-            #     draw_box_with_label(frame, box[0], box[1], box[2], box[3], obj['label'], obj['id'], thickness=1, color=(0,0,255), position='bl')
-            # cv2.putText(frame, str(total_detections), (10, 10), cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(0, 0, 0), thickness=2)
-            # cv2.putText(frame, str(frame_detections), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(0, 0, 0), thickness=2)
-            # cv2.imwrite(f"/lab/debug/output/frame-{frames}.jpg", frame)
-            # break
-
-    # start a thread to publish object scores
-    # mqtt_publisher = MqttObjectPublisher(self.mqtt_client, self.mqtt_topic_prefix, self)
-    # mqtt_publisher.start()
-
-    # create a watchdog thread for capture process
-    # self.watchdog = CameraWatchdog(self)
-
-