mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Save motion as motion box count (#10484)
This commit is contained in:
		
							parent
							
								
									380b15b286
								
							
						
					
					
						commit
						657fab2787
					
				@ -366,7 +366,7 @@ def motion_activity():
 | 
				
			|||||||
    data: list[Recordings] = (
 | 
					    data: list[Recordings] = (
 | 
				
			||||||
        Recordings.select(
 | 
					        Recordings.select(
 | 
				
			||||||
            Recordings.start_time,
 | 
					            Recordings.start_time,
 | 
				
			||||||
            Recordings.regions,
 | 
					            Recordings.motion,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .where(reduce(operator.and_, clauses))
 | 
					        .where(reduce(operator.and_, clauses))
 | 
				
			||||||
        .order_by(Recordings.start_time.asc())
 | 
					        .order_by(Recordings.start_time.asc())
 | 
				
			||||||
@ -378,8 +378,7 @@ 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", "regions"])
 | 
					    df = pd.DataFrame(data, columns=["start_time", "motion"])
 | 
				
			||||||
    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")
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,6 @@ from frigate.const import (
 | 
				
			|||||||
    RECORD_DIR,
 | 
					    RECORD_DIR,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from frigate.models import Event, Recordings
 | 
					from frigate.models import Event, Recordings
 | 
				
			||||||
from frigate.util.image import area
 | 
					 | 
				
			||||||
from frigate.util.services import get_video_properties
 | 
					from frigate.util.services import get_video_properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
@ -39,12 +38,12 @@ QUEUE_READ_TIMEOUT = 0.00001  # seconds
 | 
				
			|||||||
class SegmentInfo:
 | 
					class SegmentInfo:
 | 
				
			||||||
    def __init__(
 | 
					    def __init__(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        motion_area: int,
 | 
					        motion_count: int,
 | 
				
			||||||
        active_object_count: int,
 | 
					        active_object_count: int,
 | 
				
			||||||
        region_count: int,
 | 
					        region_count: int,
 | 
				
			||||||
        average_dBFS: int,
 | 
					        average_dBFS: int,
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        self.motion_area = motion_area
 | 
					        self.motion_count = motion_count
 | 
				
			||||||
        self.active_object_count = active_object_count
 | 
					        self.active_object_count = active_object_count
 | 
				
			||||||
        self.region_count = region_count
 | 
					        self.region_count = region_count
 | 
				
			||||||
        self.average_dBFS = average_dBFS
 | 
					        self.average_dBFS = average_dBFS
 | 
				
			||||||
@ -52,7 +51,7 @@ class SegmentInfo:
 | 
				
			|||||||
    def should_discard_segment(self, retain_mode: RetainModeEnum) -> bool:
 | 
					    def should_discard_segment(self, retain_mode: RetainModeEnum) -> bool:
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            retain_mode == RetainModeEnum.motion
 | 
					            retain_mode == RetainModeEnum.motion
 | 
				
			||||||
            and self.motion_area == 0
 | 
					            and self.motion_count == 0
 | 
				
			||||||
            and self.average_dBFS == 0
 | 
					            and self.average_dBFS == 0
 | 
				
			||||||
        ) or (
 | 
					        ) or (
 | 
				
			||||||
            retain_mode == RetainModeEnum.active_objects
 | 
					            retain_mode == RetainModeEnum.active_objects
 | 
				
			||||||
@ -76,13 +75,6 @@ class RecordingMaintainer(threading.Thread):
 | 
				
			|||||||
        self.audio_recordings_info: dict[str, list] = defaultdict(list)
 | 
					        self.audio_recordings_info: dict[str, list] = defaultdict(list)
 | 
				
			||||||
        self.end_time_cache: dict[str, Tuple[datetime.datetime, float]] = {}
 | 
					        self.end_time_cache: dict[str, Tuple[datetime.datetime, float]] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.camera_frame_area: dict[str, int] = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for camera in self.config.cameras.values():
 | 
					 | 
				
			||||||
            self.camera_frame_area[camera.name] = (
 | 
					 | 
				
			||||||
                camera.detect.width * camera.detect.height * 0.1
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def move_files(self) -> None:
 | 
					    async def move_files(self) -> None:
 | 
				
			||||||
        cache_files = [
 | 
					        cache_files = [
 | 
				
			||||||
            d
 | 
					            d
 | 
				
			||||||
@ -304,7 +296,7 @@ class RecordingMaintainer(threading.Thread):
 | 
				
			|||||||
        video_frame_count = 0
 | 
					        video_frame_count = 0
 | 
				
			||||||
        active_count = 0
 | 
					        active_count = 0
 | 
				
			||||||
        region_count = 0
 | 
					        region_count = 0
 | 
				
			||||||
        total_motion_area = 0
 | 
					        motion_count = 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
 | 
				
			||||||
            if frame[0] > end_time.timestamp():
 | 
					            if frame[0] > end_time.timestamp():
 | 
				
			||||||
@ -321,23 +313,9 @@ 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]])
 | 
					            motion_count += len(frame[2])
 | 
				
			||||||
            region_count += len(frame[3])
 | 
					            region_count += len(frame[3])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if video_frame_count > 0:
 | 
					 | 
				
			||||||
            normalized_motion_area = min(
 | 
					 | 
				
			||||||
                int(
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        total_motion_area
 | 
					 | 
				
			||||||
                        / (self.camera_frame_area[camera] * video_frame_count)
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    * 100
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                100,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            normalized_motion_area = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        audio_values = []
 | 
					        audio_values = []
 | 
				
			||||||
        for frame in self.audio_recordings_info[camera]:
 | 
					        for frame in self.audio_recordings_info[camera]:
 | 
				
			||||||
            # frame is after end time of segment
 | 
					            # frame is after end time of segment
 | 
				
			||||||
@ -357,7 +335,7 @@ 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(
 | 
					        return SegmentInfo(
 | 
				
			||||||
            normalized_motion_area, active_count, region_count, round(average_dBFS)
 | 
					            motion_count, active_count, region_count, round(average_dBFS)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def move_segment(
 | 
					    async def move_segment(
 | 
				
			||||||
@ -443,7 +421,7 @@ class RecordingMaintainer(threading.Thread):
 | 
				
			|||||||
                    Recordings.start_time: start_time.timestamp(),
 | 
					                    Recordings.start_time: start_time.timestamp(),
 | 
				
			||||||
                    Recordings.end_time: end_time.timestamp(),
 | 
					                    Recordings.end_time: end_time.timestamp(),
 | 
				
			||||||
                    Recordings.duration: duration,
 | 
					                    Recordings.duration: duration,
 | 
				
			||||||
                    Recordings.motion: segment_info.motion_area,
 | 
					                    Recordings.motion: segment_info.motion_count,
 | 
				
			||||||
                    # 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.regions: segment_info.region_count,
 | 
				
			||||||
 | 
				
			|||||||
@ -7,27 +7,27 @@ 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(
 | 
					        segment_info = SegmentInfo(
 | 
				
			||||||
            motion_area=1, active_object_count=0, region_count=0, average_dBFS=0
 | 
					            motion_count=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(
 | 
					        segment_info = SegmentInfo(
 | 
				
			||||||
            motion_area=0, active_object_count=1, region_count=0, average_dBFS=0
 | 
					            motion_count=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(
 | 
					        segment_info = SegmentInfo(
 | 
				
			||||||
            motion_area=0, active_object_count=0, region_count=0, average_dBFS=0
 | 
					            motion_count=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(
 | 
					        segment_info = SegmentInfo(
 | 
				
			||||||
            motion_area=0, active_object_count=0, region_count=0, average_dBFS=1
 | 
					            motion_count=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)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user