mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Add region count to database and use for motion activity (#10480)
* Add region count to database and use for motion activity * Fix test
This commit is contained in:
parent
c93b186eda
commit
93260f6cfd
@ -358,7 +358,6 @@ def motion_activity():
|
|||||||
)
|
)
|
||||||
|
|
||||||
clauses = [(Recordings.start_time > after) & (Recordings.end_time < before)]
|
clauses = [(Recordings.start_time > after) & (Recordings.end_time < before)]
|
||||||
clauses.append((Recordings.motion <= 100))
|
|
||||||
|
|
||||||
if cameras != "all":
|
if cameras != "all":
|
||||||
camera_list = cameras.split(",")
|
camera_list = cameras.split(",")
|
||||||
@ -367,7 +366,7 @@ def motion_activity():
|
|||||||
data: list[Recordings] = (
|
data: list[Recordings] = (
|
||||||
Recordings.select(
|
Recordings.select(
|
||||||
Recordings.start_time,
|
Recordings.start_time,
|
||||||
Recordings.motion,
|
Recordings.regions,
|
||||||
)
|
)
|
||||||
.where(reduce(operator.and_, clauses))
|
.where(reduce(operator.and_, clauses))
|
||||||
.order_by(Recordings.start_time.asc())
|
.order_by(Recordings.start_time.asc())
|
||||||
@ -379,7 +378,8 @@ def motion_activity():
|
|||||||
scale = request.args.get("scale", type=int, default=30)
|
scale = request.args.get("scale", type=int, default=30)
|
||||||
|
|
||||||
# resample data using pandas to get activity on scaled basis
|
# resample data using pandas to get activity on scaled basis
|
||||||
df = pd.DataFrame(data, columns=["start_time", "motion"])
|
df = pd.DataFrame(data, columns=["start_time", "regions"])
|
||||||
|
df = df.rename(columns={"regions": "motion"})
|
||||||
|
|
||||||
# set date as datetime index
|
# set date as datetime index
|
||||||
df["start_time"] = pd.to_datetime(df["start_time"], unit="s")
|
df["start_time"] = pd.to_datetime(df["start_time"], unit="s")
|
||||||
@ -391,6 +391,11 @@ def motion_activity():
|
|||||||
.apply(lambda x: max(x, key=abs, default=0.0))
|
.apply(lambda x: max(x, key=abs, default=0.0))
|
||||||
.fillna(0.0)
|
.fillna(0.0)
|
||||||
)
|
)
|
||||||
|
df["motion"] = (
|
||||||
|
(df["motion"] - df["motion"].min())
|
||||||
|
/ (df["motion"].max() - df["motion"].min())
|
||||||
|
* 100
|
||||||
|
)
|
||||||
|
|
||||||
# change types for output
|
# change types for output
|
||||||
df.index = df.index.astype(int) // (10**9)
|
df.index = df.index.astype(int) // (10**9)
|
||||||
|
@ -74,6 +74,7 @@ class Recordings(Model): # type: ignore[misc]
|
|||||||
objects = IntegerField(null=True)
|
objects = IntegerField(null=True)
|
||||||
dBFS = IntegerField(null=True)
|
dBFS = IntegerField(null=True)
|
||||||
segment_size = FloatField(default=0) # this should be stored as MB
|
segment_size = FloatField(default=0) # this should be stored as MB
|
||||||
|
regions = IntegerField(null=True)
|
||||||
|
|
||||||
|
|
||||||
class ReviewSegment(Model): # type: ignore[misc]
|
class ReviewSegment(Model): # type: ignore[misc]
|
||||||
|
@ -38,10 +38,15 @@ QUEUE_READ_TIMEOUT = 0.00001 # seconds
|
|||||||
|
|
||||||
class SegmentInfo:
|
class SegmentInfo:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, motion_area: int, active_object_count: int, average_dBFS: int
|
self,
|
||||||
|
motion_area: int,
|
||||||
|
active_object_count: int,
|
||||||
|
region_count: int,
|
||||||
|
average_dBFS: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.motion_area = motion_area
|
self.motion_area = motion_area
|
||||||
self.active_object_count = active_object_count
|
self.active_object_count = active_object_count
|
||||||
|
self.region_count = region_count
|
||||||
self.average_dBFS = average_dBFS
|
self.average_dBFS = average_dBFS
|
||||||
|
|
||||||
def should_discard_segment(self, retain_mode: RetainModeEnum) -> bool:
|
def should_discard_segment(self, retain_mode: RetainModeEnum) -> bool:
|
||||||
@ -298,6 +303,7 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
) -> SegmentInfo:
|
) -> SegmentInfo:
|
||||||
video_frame_count = 0
|
video_frame_count = 0
|
||||||
active_count = 0
|
active_count = 0
|
||||||
|
region_count = 0
|
||||||
total_motion_area = 0
|
total_motion_area = 0
|
||||||
for frame in self.object_recordings_info[camera]:
|
for frame in self.object_recordings_info[camera]:
|
||||||
# frame is after end time of segment
|
# frame is after end time of segment
|
||||||
@ -315,8 +321,8 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
if not o["false_positive"] and o["motionless_count"] == 0
|
if not o["false_positive"] and o["motionless_count"] == 0
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
total_motion_area += sum([area(box) for box in frame[2]])
|
total_motion_area += sum([area(box) for box in frame[2]])
|
||||||
|
region_count += len(frame[3])
|
||||||
|
|
||||||
if video_frame_count > 0:
|
if video_frame_count > 0:
|
||||||
normalized_motion_area = min(
|
normalized_motion_area = min(
|
||||||
@ -350,7 +356,9 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
|
|
||||||
average_dBFS = 0 if not audio_values else np.average(audio_values)
|
average_dBFS = 0 if not audio_values else np.average(audio_values)
|
||||||
|
|
||||||
return SegmentInfo(normalized_motion_area, active_count, round(average_dBFS))
|
return SegmentInfo(
|
||||||
|
normalized_motion_area, active_count, region_count, round(average_dBFS)
|
||||||
|
)
|
||||||
|
|
||||||
async def move_segment(
|
async def move_segment(
|
||||||
self,
|
self,
|
||||||
@ -438,6 +446,7 @@ class RecordingMaintainer(threading.Thread):
|
|||||||
Recordings.motion: segment_info.motion_area,
|
Recordings.motion: segment_info.motion_area,
|
||||||
# TODO: update this to store list of active objects at some point
|
# TODO: update this to store list of active objects at some point
|
||||||
Recordings.objects: segment_info.active_object_count,
|
Recordings.objects: segment_info.active_object_count,
|
||||||
|
Recordings.regions: segment_info.region_count,
|
||||||
Recordings.dBFS: segment_info.average_dBFS,
|
Recordings.dBFS: segment_info.average_dBFS,
|
||||||
Recordings.segment_size: segment_size,
|
Recordings.segment_size: segment_size,
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,28 @@ from frigate.record.maintainer import SegmentInfo
|
|||||||
|
|
||||||
class TestRecordRetention(unittest.TestCase):
|
class TestRecordRetention(unittest.TestCase):
|
||||||
def test_motion_should_keep_motion_not_object(self):
|
def test_motion_should_keep_motion_not_object(self):
|
||||||
segment_info = SegmentInfo(motion_area=1, active_object_count=0, average_dBFS=0)
|
segment_info = SegmentInfo(
|
||||||
|
motion_area=1, active_object_count=0, region_count=0, average_dBFS=0
|
||||||
|
)
|
||||||
assert not segment_info.should_discard_segment(RetainModeEnum.motion)
|
assert not segment_info.should_discard_segment(RetainModeEnum.motion)
|
||||||
assert segment_info.should_discard_segment(RetainModeEnum.active_objects)
|
assert segment_info.should_discard_segment(RetainModeEnum.active_objects)
|
||||||
|
|
||||||
def test_object_should_keep_object_not_motion(self):
|
def test_object_should_keep_object_not_motion(self):
|
||||||
segment_info = SegmentInfo(motion_area=0, active_object_count=1, average_dBFS=0)
|
segment_info = SegmentInfo(
|
||||||
|
motion_area=0, active_object_count=1, region_count=0, average_dBFS=0
|
||||||
|
)
|
||||||
assert segment_info.should_discard_segment(RetainModeEnum.motion)
|
assert segment_info.should_discard_segment(RetainModeEnum.motion)
|
||||||
assert not segment_info.should_discard_segment(RetainModeEnum.active_objects)
|
assert not segment_info.should_discard_segment(RetainModeEnum.active_objects)
|
||||||
|
|
||||||
def test_all_should_keep_all(self):
|
def test_all_should_keep_all(self):
|
||||||
segment_info = SegmentInfo(motion_area=0, active_object_count=0, average_dBFS=0)
|
segment_info = SegmentInfo(
|
||||||
|
motion_area=0, active_object_count=0, region_count=0, average_dBFS=0
|
||||||
|
)
|
||||||
assert not segment_info.should_discard_segment(RetainModeEnum.all)
|
assert not segment_info.should_discard_segment(RetainModeEnum.all)
|
||||||
|
|
||||||
def test_should_keep_audio_in_motion_mode(self):
|
def test_should_keep_audio_in_motion_mode(self):
|
||||||
segment_info = SegmentInfo(motion_area=0, active_object_count=0, average_dBFS=1)
|
segment_info = SegmentInfo(
|
||||||
|
motion_area=0, active_object_count=0, region_count=0, average_dBFS=1
|
||||||
|
)
|
||||||
assert not segment_info.should_discard_segment(RetainModeEnum.motion)
|
assert not segment_info.should_discard_segment(RetainModeEnum.motion)
|
||||||
assert segment_info.should_discard_segment(RetainModeEnum.active_objects)
|
assert segment_info.should_discard_segment(RetainModeEnum.active_objects)
|
||||||
|
@ -34,6 +34,9 @@ def migrate(migrator, database, fake=False, **kwargs):
|
|||||||
objects=pw.IntegerField(null=True),
|
objects=pw.IntegerField(null=True),
|
||||||
motion=pw.IntegerField(null=True),
|
motion=pw.IntegerField(null=True),
|
||||||
)
|
)
|
||||||
|
migrator.sql(
|
||||||
|
'CREATE INDEX "recordings_activity" ON "recordings" ("camera", "start_time" DESC, "regions")'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def rollback(migrator, database, fake=False, **kwargs):
|
def rollback(migrator, database, fake=False, **kwargs):
|
||||||
|
39
migrations/023_add_regions.py
Normal file
39
migrations/023_add_regions.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
"""Peewee migrations -- 023_add_regions.py.
|
||||||
|
|
||||||
|
Some examples (model - class or model name)::
|
||||||
|
|
||||||
|
> Model = migrator.orm['model_name'] # Return model in current state by name
|
||||||
|
|
||||||
|
> migrator.sql(sql) # Run custom SQL
|
||||||
|
> migrator.python(func, *args, **kwargs) # Run python code
|
||||||
|
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||||
|
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||||
|
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||||
|
> migrator.change_fields(model, **fields) # Change fields
|
||||||
|
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||||
|
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||||
|
> migrator.rename_table(model, new_table_name)
|
||||||
|
> migrator.add_index(model, *col_names, unique=False)
|
||||||
|
> migrator.drop_index(model, *col_names)
|
||||||
|
> migrator.add_not_null(model, *field_names)
|
||||||
|
> migrator.drop_not_null(model, *field_names)
|
||||||
|
> migrator.add_default(model, field_name, default)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import peewee as pw
|
||||||
|
|
||||||
|
from frigate.models import Recordings
|
||||||
|
|
||||||
|
SQL = pw.SQL
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(migrator, database, fake=False, **kwargs):
|
||||||
|
migrator.add_fields(
|
||||||
|
Recordings,
|
||||||
|
regions=pw.IntegerField(null=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def rollback(migrator, database, fake=False, **kwargs):
|
||||||
|
migrator.remove_fields(Recordings, ["regions"])
|
Loading…
Reference in New Issue
Block a user