@@ -263,8 +263,12 @@ def __init__(
263263
264264 # Motion Event Parameters (set_event_params)
265265 self ._min_event_len = None # -l/--min-event-length
266- self ._pre_event_len = None # -tb/--time-before-event
267- self ._post_event_len = None # -tp/--time-post-event
266+ # TODO(v1.7): We really need three variables here:
267+ # - how much time after an event ends should we wait for more motion <-- window
268+ # - how much time before/after an event should we include in the video
269+
270+ self ._time_before_event = None # -tb/--time-before-event
271+ self ._time_post_event = None # -tp/--time-post-event
268272 self ._use_pts = None # --use_pts
269273
270274 # Region Parameters (set_region)
@@ -471,8 +475,8 @@ def set_event_params(
471475 """Set motion event parameters."""
472476 assert self ._input .framerate is not None
473477 self ._min_event_len = FrameTimecode (min_event_len , self ._input .framerate )
474- self ._pre_event_len = FrameTimecode (time_pre_event , self ._input .framerate )
475- self ._post_event_len = FrameTimecode (time_post_event , self ._input .framerate )
478+ self ._time_before_event = FrameTimecode (time_pre_event , self ._input .framerate )
479+ self ._time_post_event = FrameTimecode (time_post_event , self ._input .framerate )
476480 self ._use_pts = use_pts
477481
478482 def set_thumbnail_params (self , thumbnails : str = None ):
@@ -693,8 +697,8 @@ def scan(self) -> Optional[DetectionResult]:
693697 )
694698
695699 # Correct event length parameters to account frame skip.
696- post_event_len : int = self ._post_event_len .frame_num // (self ._frame_skip + 1 )
697- pre_event_len : int = self ._pre_event_len .frame_num // (self ._frame_skip + 1 )
700+ post_event_len : int = self ._time_post_event .frame_num // (self ._frame_skip + 1 )
701+ pre_event_len : int = self ._time_before_event .frame_num // (self ._frame_skip + 1 )
698702 min_event_len : int = max (self ._min_event_len .frame_num // (self ._frame_skip + 1 ), 1 )
699703
700704 # Calculations below rely on min_event_len always being >= 1 (cannot be zero)
@@ -705,21 +709,19 @@ def scan(self) -> Optional[DetectionResult]:
705709 # need to compensate for rounding errors when we corrected it for frame skip. This is
706710 # important as this affects the number of frames we consider for the actual motion event.
707711 if not self ._use_pts :
708- start_event_shift : int = self ._pre_event_len .frame_num + min_event_len * (
712+ start_event_shift : int = self ._time_before_event .frame_num + min_event_len * (
709713 self ._frame_skip + 1
710714 )
711715 else :
712716 start_event_shift_ms : float = (
713- self ._pre_event_len .get_seconds () + self ._min_event_len .get_seconds ()
717+ self ._time_before_event .get_seconds () + self ._min_event_len .get_seconds ()
714718 ) * 1000
715719
716720 # Length of buffer we require in memory to keep track of all frames required for -l and -tb.
717721 buff_len = pre_event_len + min_event_len
718722 event_end = self ._input .position
719- if not self ._use_pts :
720- last_frame_above_threshold = 0
721- else :
722- last_frame_above_threshold_ms = 0
723+ last_frame_above_threshold = 0
724+ last_frame_above_threshold_ms = 0
723725
724726 if self ._bounding_box :
725727 self ._bounding_box .set_corrections (
@@ -825,6 +827,11 @@ def scan(self) -> Optional[DetectionResult]:
825827 )
826828 )
827829
830+ #
831+ # TODO: Make a small state diagram and create a new state enum to handle different
832+ # merging modes, etc.
833+ #
834+
828835 # Last frame was part of a motion event, or still within the post-event window.
829836 if in_motion_event :
830837 # If this frame still has motion, reset the post-event window.
@@ -839,67 +846,39 @@ def scan(self) -> Optional[DetectionResult]:
839846 #
840847 # TODO(#72): We should wait until the max of *both* the pre-event and post-
841848 # event windows have passed. Right now we just consider the post-event window.
849+ # We should also allow configuring overlap behavior:
850+ # - normal: If any new motion is found within max(time_pre_event, time_post_event),
851+ # it will be merged with the preceeding event.
852+ # - extended: Events that have a gap of size (time_pre_event + time_post_event)
853+ # between each other will be merged.
842854 else :
843855 num_frames_post_event += 1
844- if num_frames_post_event >= post_event_len :
856+ if num_frames_post_event >= ( pre_event_len + post_event_len ) :
845857 in_motion_event = False
846-
847- logger .debug (
848- "event %d high score %f" % (1 + self ._num_events , self ._highscore )
858+ # TODO: We can't throw these frames away, they might be needed for the
859+ # next event to satisfy it's own pre_event_len.
860+ buffered_frames = []
861+ event = self ._on_event_end (
862+ last_frame_above_threshold ,
863+ last_frame_above_threshold_ms ,
864+ event_start ,
849865 )
850- if self ._thumbnails == "highscore" :
851- video_name = get_filename (
852- path = self ._input .paths [0 ], include_extension = False
853- )
854- output_path = (
855- self ._comp_file
856- if self ._comp_file
857- else OUTPUT_FILE_TEMPLATE .format (
858- VIDEO_NAME = video_name ,
859- EVENT_NUMBER = "%04d" % (1 + self ._num_events ),
860- EXTENSION = "jpg" ,
861- )
862- )
863- if self ._output_dir :
864- output_path = os .path .join (self ._output_dir , output_path )
865- cv2 .imwrite (output_path , self ._highframe )
866- self ._highscore = 0
867- self ._highframe = None
868-
869- # Calculate event end based on the last frame we had with motion plus
870- # the post event length time. We also need to compensate for the number
871- # of frames that we skipped that could have had motion.
872- # We also add 1 to include the presentation duration of the last frame.
873- if not self ._use_pts :
874- event_end = FrameTimecode (
875- 1
876- + last_frame_above_threshold
877- + self ._post_event_len .frame_num
878- + self ._frame_skip ,
879- self ._input .framerate ,
880- )
881- assert event_end .frame_num >= event_start .frame_num
882- else :
883- event_end = FrameTimecode (
884- (last_frame_above_threshold_ms / 1000 )
885- + self ._post_event_len .get_seconds (),
886- self ._input .framerate ,
887- )
888- assert event_end .get_seconds () >= event_start .get_seconds ()
889- event_list .append (MotionEvent (start = event_start , end = event_end ))
866+ event_list .append (event )
890867 if self ._output_mode != OutputMode .SCAN_ONLY :
891868 encode_queue .put (MotionEvent (start = event_start , end = event_end ))
892-
893869 # Send frame to encode thread.
894870 if in_motion_event and self ._output_mode == OutputMode .OPENCV :
895- encode_queue .put (
896- EncodeFrameEvent (
897- frame_bgr = frame .frame_bgr ,
898- timecode = frame .timecode ,
899- bounding_box = bounding_box ,
900- score = frame_score ,
901- )
871+ encode_frame = EncodeFrameEvent (
872+ frame_bgr = frame .frame_bgr ,
873+ timecode = frame .timecode ,
874+ bounding_box = bounding_box ,
875+ score = frame_score ,
902876 )
877+ if num_frames_post_event < post_event_len :
878+ encode_queue .put (encode_frame )
879+ else :
880+ buffered_frames .append (encode_frame )
881+
903882 # Not already in a motion event, look for a new one.
904883 else :
905884 # Buffer the required amount of frames and overlay data until we find an event.
@@ -1017,6 +996,48 @@ def scan(self) -> Optional[DetectionResult]:
1017996
1018997 return DetectionResult (event_list , frames_processed )
1019998
999+ def _on_event_end (
1000+ self ,
1001+ last_frame_above_threshold ,
1002+ last_frame_above_threshold_ms ,
1003+ event_start ,
1004+ ) -> MotionEvent :
1005+ logger .debug ("event %d high score %f" % (1 + self ._num_events , self ._highscore ))
1006+ if self ._thumbnails == "highscore" :
1007+ video_name = get_filename (path = self ._input .paths [0 ], include_extension = False )
1008+ output_path = (
1009+ self ._comp_file
1010+ if self ._comp_file
1011+ else OUTPUT_FILE_TEMPLATE .format (
1012+ VIDEO_NAME = video_name ,
1013+ EVENT_NUMBER = "%04d" % (1 + self ._num_events ),
1014+ EXTENSION = "jpg" ,
1015+ )
1016+ )
1017+ if self ._output_dir :
1018+ output_path = os .path .join (self ._output_dir , output_path )
1019+ cv2 .imwrite (output_path , self ._highframe )
1020+ self ._highscore = 0
1021+ self ._highframe = None
1022+
1023+ # Calculate event end based on the last frame we had with motion plus
1024+ # the post event length time. We also need to compensate for the number
1025+ # of frames that we skipped that could have had motion.
1026+ # We also add 1 to include the presentation duration of the last frame.
1027+ if not self ._use_pts :
1028+ event_end = FrameTimecode (
1029+ 1 + last_frame_above_threshold + self ._time_post_event .frame_num + self ._frame_skip ,
1030+ self ._input .framerate ,
1031+ )
1032+ assert event_end .frame_num >= event_start .frame_num
1033+ else :
1034+ event_end = FrameTimecode (
1035+ (last_frame_above_threshold_ms / 1000 ) + self ._time_post_event .get_seconds (),
1036+ self ._input .framerate ,
1037+ )
1038+ assert event_end .get_seconds () >= event_start .get_seconds ()
1039+ return MotionEvent (start = event_start , end = event_end )
1040+
10201041 def _decode_thread (self , decode_queue : queue .Queue ):
10211042 try :
10221043 while not self ._stop .is_set ():
0 commit comments