From 43fd76f4ec90cde5199a1246e21ec565f37073ea Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 11 Mar 2022 21:50:06 -0500 Subject: [PATCH] Add object ratio config parameters Issue: #2948 --- docs/docs/configuration/index.md | 4 ++++ docs/docs/guides/false_positives.md | 6 +++++- docs/docs/integrations/mqtt.md | 2 ++ frigate/config.py | 8 +++++++ frigate/events.py | 2 ++ frigate/models.py | 1 + frigate/object_processing.py | 9 ++++++++ frigate/objects.py | 3 ++- frigate/video.py | 33 ++++++++++++++++++++++------- 9 files changed, 58 insertions(+), 10 deletions(-) diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index 4f1765192..1f6e092df 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -197,6 +197,10 @@ objects: min_area: 5000 # Optional: maximum width*height of the bounding box for the detected object (default: 24000000) max_area: 100000 + # Optional: minimum width/height of the bounding box for the detected object (default: 0) + min_ratio: 0.5 + # Optional: maximum width/height of the bounding box for the detected object (default: 24000000) + max_ratio: 2.0 # Optional: minimum score for the object to initiate tracking (default: shown below) min_score: 0.5 # Optional: minimum decimal percentage for tracked object's computed score to be considered a true positive (default: shown below) diff --git a/docs/docs/guides/false_positives.md b/docs/docs/guides/false_positives.md index 9c135c3d3..7e5d3c29f 100644 --- a/docs/docs/guides/false_positives.md +++ b/docs/docs/guides/false_positives.md @@ -3,7 +3,11 @@ id: false_positives title: Reducing false positives --- -Tune your object filters to adjust false positives: `min_area`, `max_area`, `min_score`, `threshold`. +Tune your object filters to adjust false positives: `min_area`, `max_area`, `min_ratio`, `max_ratio`, `min_score`, `threshold`. + +The `min_area` and `max_area` values are compared against the area (number of pixels) from a given detected object. If the area is outside this range, the object will be ignored as a false positive. This allows objects that must be too small or too large to be ignored. + +Similarly, the `min_ratio` and `max_ratio` values are compared against a given detected object's width/height ratio (in pixels). If the ratio is outside this range, the object will be ignored as a false positive. This allows objects that are proportionally too short-and-wide (higher ratio) or too tall-and-narrow (smaller ratio) to be ignored. For object filters in your configuration, any single detection below `min_score` will be ignored as a false positive. `threshold` is based on the median of the history of scores (padded to 3 values) for a tracked object. Consider the following frames when `min_score` is set to 0.6 and threshold is set to 0.85: diff --git a/docs/docs/integrations/mqtt.md b/docs/docs/integrations/mqtt.md index ef6c07a42..57c673015 100644 --- a/docs/docs/integrations/mqtt.md +++ b/docs/docs/integrations/mqtt.md @@ -50,6 +50,7 @@ Message published for each changed event. The first message is published when th "score": 0.7890625, "box": [424, 500, 536, 712], "area": 23744, + "ratio": 2.113207, "region": [264, 450, 667, 853], "current_zones": ["driveway"], "entered_zones": ["yard", "driveway"], @@ -73,6 +74,7 @@ Message published for each changed event. The first message is published when th "score": 0.87890625, "box": [432, 496, 544, 854], "area": 40096, + "ratio": 1.251397, "region": [218, 440, 693, 915], "current_zones": ["yard", "driveway"], "entered_zones": ["yard", "driveway"], diff --git a/frigate/config.py b/frigate/config.py index e89b43003..772bb88c3 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -209,6 +209,14 @@ class FilterConfig(FrigateBaseModel): max_area: int = Field( default=24000000, title="Maximum area of bounding box for object to be counted." ) + min_ratio: float = Field( + default=0, + title="Minimum ratio of bounding box's width/height for object to be counted.", + ) + max_ratio: float = Field( + default=24000000, # @todo: Is there a way to make this `math.inf` but still load from YAML? + title="Maximum ratio of bounding box's width/height for object to be counted.", + ) threshold: float = Field( default=0.7, title="Average detection confidence threshold for object to be counted.", diff --git a/frigate/events.py b/frigate/events.py index e92a37f56..bedabcb74 100644 --- a/frigate/events.py +++ b/frigate/events.py @@ -77,6 +77,7 @@ class EventProcessor(threading.Thread): region=event_data["region"], box=event_data["box"], area=event_data["area"], + ratio=event_data["ratio"], has_clip=event_data["has_clip"], has_snapshot=event_data["has_snapshot"], ).execute() @@ -96,6 +97,7 @@ class EventProcessor(threading.Thread): region=event_data["region"], box=event_data["box"], area=event_data["area"], + ratio=event_data["ratio"], has_clip=event_data["has_clip"], has_snapshot=event_data["has_snapshot"], ).execute() diff --git a/frigate/models.py b/frigate/models.py index 35a397f5c..54c12a554 100644 --- a/frigate/models.py +++ b/frigate/models.py @@ -18,6 +18,7 @@ class Event(Model): region = JSONField() box = JSONField() area = IntegerField() + ratio = FloatField() class Recordings(Model): diff --git a/frigate/object_processing.py b/frigate/object_processing.py index 37d45e127..b5460978c 100644 --- a/frigate/object_processing.py +++ b/frigate/object_processing.py @@ -192,6 +192,7 @@ class TrackedObject: "score": self.obj_data["score"], "box": self.obj_data["box"], "area": self.obj_data["area"], + "ratio": self.obj_data["ratio"], "region": self.obj_data["region"], "stationary": self.obj_data["motionless_count"] > self.camera_config.detect.stationary.threshold, @@ -341,6 +342,14 @@ def zone_filtered(obj: TrackedObject, object_config): if obj_settings.threshold > obj.computed_score: return True + # if the object is not proportionally wide enough + if obj_settings.min_ratio > obj.obj_data["ratio"]: + return True + + # if the object is not proportionally narrow enough + if obj_settings.max_ratio < obj.obj_data["ratio"]: + return True + return False diff --git a/frigate/objects.py b/frigate/objects.py index fb3ea7daa..387aba1a2 100644 --- a/frigate/objects.py +++ b/frigate/objects.py @@ -149,7 +149,8 @@ class ObjectTracker: "score": obj[1], "box": obj[2], "area": obj[3], - "region": obj[4], + "ratio": obj[4], + "region": obj[5], "frame_time": frame_time, } ) diff --git a/frigate/video.py b/frigate/video.py index 26b99176f..79256f6ca 100755 --- a/frigate/video.py +++ b/frigate/video.py @@ -38,6 +38,9 @@ logger = logging.getLogger(__name__) def filtered(obj, objects_to_track, object_filters): object_name = obj[0] + object_score = obj[1] + object_bounds = obj[2] + object_area = obj[3] if not object_name in objects_to_track: return True @@ -47,24 +50,27 @@ def filtered(obj, objects_to_track, object_filters): # if the min area is larger than the # detected object, don't add it to detected objects - if obj_settings.min_area > obj[3]: + if obj_settings.min_area > object_area: return True # if the detected object is larger than the # max area, don't add it to detected objects - if obj_settings.max_area < obj[3]: + if obj_settings.max_area < object_area: return True # if the score is lower than the min_score, skip - if obj_settings.min_score > obj[1]: + if obj_settings.min_score > object_score: return True if not obj_settings.mask is None: # compute the coordinates of the object and make sure - # the location isnt outside the bounds of the image (can happen from rounding) - y_location = min(int(obj[2][3]), len(obj_settings.mask) - 1) + # the location isn't outside the bounds of the image (can happen from rounding) + object_xmin = object_bounds[0] + object_xmax = object_bounds[2] + object_ymax = object_bounds[3] + y_location = min(int(object_ymax), len(obj_settings.mask) - 1) x_location = min( - int((obj[2][2] - obj[2][0]) / 2.0) + obj[2][0], + int((object_xmax + object_xmin) / 2.0), len(obj_settings.mask[0]) - 1, ) @@ -429,11 +435,16 @@ def detect( y_min = int((box[0] * size) + region[1]) x_max = int((box[3] * size) + region[0]) y_max = int((box[2] * size) + region[1]) + width = x_max - x_min + height = y_max - y_min + area = width * height + ratio = width / height det = ( d[0], d[1], (x_min, y_min, x_max, y_max), - (x_max - x_min) * (y_max - y_min), + area, + ratio, region, ) # apply object filters @@ -615,8 +626,14 @@ def process_frames( for group in detected_object_groups.values(): # apply non-maxima suppression to suppress weak, overlapping bounding boxes + bounds = o[2] # xmin, ymin, xmax, ymax boxes = [ - (o[2][0], o[2][1], o[2][2] - o[2][0], o[2][3] - o[2][1]) + ( + bounds[0], + bounds[1], + bounds[2] - bounds[0], + bounds[3] - bounds[1], + ) for o in group ] confidences = [o[1] for o in group]