Add object ratio config parameters

Issue: #2948
This commit is contained in:
Nick 2022-03-11 21:50:06 -05:00 committed by mathmaniac43
parent 0abd0627df
commit 1aa95d1e93
9 changed files with 58 additions and 10 deletions

View File

@ -194,6 +194,10 @@ objects:
min_area: 5000 min_area: 5000
# Optional: maximum width*height of the bounding box for the detected object (default: 24000000) # Optional: maximum width*height of the bounding box for the detected object (default: 24000000)
max_area: 100000 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) # Optional: minimum score for the object to initiate tracking (default: shown below)
min_score: 0.5 min_score: 0.5
# Optional: minimum decimal percentage for tracked object's computed score to be considered a true positive (default: shown below) # Optional: minimum decimal percentage for tracked object's computed score to be considered a true positive (default: shown below)

View File

@ -3,7 +3,11 @@ id: false_positives
title: Reducing 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: 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:

View File

@ -52,6 +52,7 @@ Message published for each changed event. The first message is published when th
"score": 0.7890625, "score": 0.7890625,
"box": [424, 500, 536, 712], "box": [424, 500, 536, 712],
"area": 23744, "area": 23744,
"ratio": 2.113207,
"region": [264, 450, 667, 853], "region": [264, 450, 667, 853],
"current_zones": ["driveway"], "current_zones": ["driveway"],
"entered_zones": ["yard", "driveway"], "entered_zones": ["yard", "driveway"],
@ -75,6 +76,7 @@ Message published for each changed event. The first message is published when th
"score": 0.87890625, "score": 0.87890625,
"box": [432, 496, 544, 854], "box": [432, 496, 544, 854],
"area": 40096, "area": 40096,
"ratio": 1.251397,
"region": [218, 440, 693, 915], "region": [218, 440, 693, 915],
"current_zones": ["yard", "driveway"], "current_zones": ["yard", "driveway"],
"entered_zones": ["yard", "driveway"], "entered_zones": ["yard", "driveway"],

View File

@ -210,6 +210,14 @@ class FilterConfig(FrigateBaseModel):
max_area: int = Field( max_area: int = Field(
default=24000000, title="Maximum area of bounding box for object to be counted." 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( threshold: float = Field(
default=0.7, default=0.7,
title="Average detection confidence threshold for object to be counted.", title="Average detection confidence threshold for object to be counted.",

View File

@ -77,6 +77,7 @@ class EventProcessor(threading.Thread):
region=event_data["region"], region=event_data["region"],
box=event_data["box"], box=event_data["box"],
area=event_data["area"], area=event_data["area"],
ratio=event_data["ratio"],
has_clip=event_data["has_clip"], has_clip=event_data["has_clip"],
has_snapshot=event_data["has_snapshot"], has_snapshot=event_data["has_snapshot"],
).execute() ).execute()
@ -96,6 +97,7 @@ class EventProcessor(threading.Thread):
region=event_data["region"], region=event_data["region"],
box=event_data["box"], box=event_data["box"],
area=event_data["area"], area=event_data["area"],
ratio=event_data["ratio"],
has_clip=event_data["has_clip"], has_clip=event_data["has_clip"],
has_snapshot=event_data["has_snapshot"], has_snapshot=event_data["has_snapshot"],
).execute() ).execute()

View File

@ -19,6 +19,7 @@ class Event(Model):
box = JSONField() box = JSONField()
area = IntegerField() area = IntegerField()
retain_indefinitely = BooleanField(default=False) retain_indefinitely = BooleanField(default=False)
ratio = FloatField()
class Recordings(Model): class Recordings(Model):

View File

@ -192,6 +192,7 @@ class TrackedObject:
"score": self.obj_data["score"], "score": self.obj_data["score"],
"box": self.obj_data["box"], "box": self.obj_data["box"],
"area": self.obj_data["area"], "area": self.obj_data["area"],
"ratio": self.obj_data["ratio"],
"region": self.obj_data["region"], "region": self.obj_data["region"],
"stationary": self.obj_data["motionless_count"] "stationary": self.obj_data["motionless_count"]
> self.camera_config.detect.stationary.threshold, > self.camera_config.detect.stationary.threshold,
@ -341,6 +342,14 @@ def zone_filtered(obj: TrackedObject, object_config):
if obj_settings.threshold > obj.computed_score: if obj_settings.threshold > obj.computed_score:
return True 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 return False

View File

@ -150,7 +150,8 @@ class ObjectTracker:
"score": obj[1], "score": obj[1],
"box": obj[2], "box": obj[2],
"area": obj[3], "area": obj[3],
"region": obj[4], "ratio": obj[4],
"region": obj[5],
"frame_time": frame_time, "frame_time": frame_time,
} }
) )

View File

@ -38,6 +38,9 @@ logger = logging.getLogger(__name__)
def filtered(obj, objects_to_track, object_filters): def filtered(obj, objects_to_track, object_filters):
object_name = obj[0] object_name = obj[0]
object_score = obj[1]
object_bounds = obj[2]
object_area = obj[3]
if not object_name in objects_to_track: if not object_name in objects_to_track:
return True return True
@ -47,24 +50,27 @@ def filtered(obj, objects_to_track, object_filters):
# if the min area is larger than the # if the min area is larger than the
# detected object, don't add it to detected objects # 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 return True
# if the detected object is larger than the # if the detected object is larger than the
# max area, don't add it to detected objects # 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 return True
# if the score is lower than the min_score, skip # 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 return True
if not obj_settings.mask is None: if not obj_settings.mask is None:
# compute the coordinates of the object and make sure # compute the coordinates of the object and make sure
# the location isnt outside the bounds of the image (can happen from rounding) # the location isn't outside the bounds of the image (can happen from rounding)
y_location = min(int(obj[2][3]), len(obj_settings.mask) - 1) 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( 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, len(obj_settings.mask[0]) - 1,
) )
@ -429,11 +435,16 @@ def detect(
y_min = int((box[0] * size) + region[1]) y_min = int((box[0] * size) + region[1])
x_max = int((box[3] * size) + region[0]) x_max = int((box[3] * size) + region[0])
y_max = int((box[2] * size) + region[1]) 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 = ( det = (
d[0], d[0],
d[1], d[1],
(x_min, y_min, x_max, y_max), (x_min, y_min, x_max, y_max),
(x_max - x_min) * (y_max - y_min), area,
ratio,
region, region,
) )
# apply object filters # apply object filters
@ -615,8 +626,14 @@ def process_frames(
for group in detected_object_groups.values(): for group in detected_object_groups.values():
# apply non-maxima suppression to suppress weak, overlapping bounding boxes # apply non-maxima suppression to suppress weak, overlapping bounding boxes
bounds = o[2] # xmin, ymin, xmax, ymax
boxes = [ 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 for o in group
] ]
confidences = [o[1] for o in group] confidences = [o[1] for o in group]