@@ -263,8 +263,9 @@ 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+ self ._min_event_dist = None # --min-event-dist # TODO: Implement this to fix #72.
267+ self ._time_before_event = None # -tb/--time-before-event
268+ self ._time_post_event = None # -tp/--time-post-event
268269 self ._use_pts = None # --use_pts
269270
270271 # Region Parameters (set_region)
@@ -471,8 +472,8 @@ def set_event_params(
471472 """Set motion event parameters."""
472473 assert self ._input .framerate is not None
473474 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 )
475+ self ._time_before_event = FrameTimecode (time_pre_event , self ._input .framerate )
476+ self ._time_post_event = FrameTimecode (time_post_event , self ._input .framerate )
476477 self ._use_pts = use_pts
477478
478479 def set_thumbnail_params (self , thumbnails : str = None ):
@@ -693,8 +694,8 @@ def scan(self) -> Optional[DetectionResult]:
693694 )
694695
695696 # 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 )
697+ post_event_len : int = self ._time_post_event .frame_num // (self ._frame_skip + 1 )
698+ pre_event_len : int = self ._time_before_event .frame_num // (self ._frame_skip + 1 )
698699 min_event_len : int = max (self ._min_event_len .frame_num // (self ._frame_skip + 1 ), 1 )
699700
700701 # Calculations below rely on min_event_len always being >= 1 (cannot be zero)
@@ -705,21 +706,19 @@ def scan(self) -> Optional[DetectionResult]:
705706 # need to compensate for rounding errors when we corrected it for frame skip. This is
706707 # important as this affects the number of frames we consider for the actual motion event.
707708 if not self ._use_pts :
708- start_event_shift : int = self ._pre_event_len .frame_num + min_event_len * (
709+ start_event_shift : int = self ._time_before_event .frame_num + min_event_len * (
709710 self ._frame_skip + 1
710711 )
711712 else :
712713 start_event_shift_ms : float = (
713- self ._pre_event_len .get_seconds () + self ._min_event_len .get_seconds ()
714+ self ._time_before_event .get_seconds () + self ._min_event_len .get_seconds ()
714715 ) * 1000
715716
716717 # Length of buffer we require in memory to keep track of all frames required for -l and -tb.
717718 buff_len = pre_event_len + min_event_len
718719 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
720+ last_frame_above_threshold = 0
721+ last_frame_above_threshold_ms = 0
723722
724723 if self ._bounding_box :
725724 self ._bounding_box .set_corrections (
@@ -825,6 +824,11 @@ def scan(self) -> Optional[DetectionResult]:
825824 )
826825 )
827826
827+ #
828+ # TODO: Make a small state diagram and create a new state enum to handle different
829+ # merging modes, etc.
830+ #
831+
828832 # Last frame was part of a motion event, or still within the post-event window.
829833 if in_motion_event :
830834 # If this frame still has motion, reset the post-event window.
@@ -839,67 +843,39 @@ def scan(self) -> Optional[DetectionResult]:
839843 #
840844 # TODO(#72): We should wait until the max of *both* the pre-event and post-
841845 # event windows have passed. Right now we just consider the post-event window.
846+ # We should also allow configuring overlap behavior:
847+ # - normal: If any new motion is found within max(time_pre_event, time_post_event),
848+ # it will be merged with the preceeding event.
849+ # - extended: Events that have a gap of size (time_pre_event + time_post_event)
850+ # between each other will be merged.
842851 else :
843852 num_frames_post_event += 1
844- if num_frames_post_event >= post_event_len :
853+ if num_frames_post_event >= ( pre_event_len + post_event_len ) :
845854 in_motion_event = False
846-
847- logger .debug (
848- "event %d high score %f" % (1 + self ._num_events , self ._highscore )
855+ # TODO: We can't throw these frames away, they might be needed for the
856+ # next event to satisfy it's own pre_event_len.
857+ buffered_frames = []
858+ event = self ._on_event_end (
859+ last_frame_above_threshold ,
860+ last_frame_above_threshold_ms ,
861+ event_start ,
849862 )
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 ))
863+ event_list .append (event )
890864 if self ._output_mode != OutputMode .SCAN_ONLY :
891865 encode_queue .put (MotionEvent (start = event_start , end = event_end ))
892-
893866 # Send frame to encode thread.
894867 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- )
868+ encode_frame = EncodeFrameEvent (
869+ frame_bgr = frame .frame_bgr ,
870+ timecode = frame .timecode ,
871+ bounding_box = bounding_box ,
872+ score = frame_score ,
902873 )
874+ if num_frames_post_event < post_event_len :
875+ encode_queue .put (encode_frame )
876+ else :
877+ buffered_frames .append (encode_frame )
878+
903879 # Not already in a motion event, look for a new one.
904880 else :
905881 # Buffer the required amount of frames and overlay data until we find an event.
@@ -1017,6 +993,48 @@ def scan(self) -> Optional[DetectionResult]:
1017993
1018994 return DetectionResult (event_list , frames_processed )
1019995
996+ def _on_event_end (
997+ self ,
998+ last_frame_above_threshold ,
999+ last_frame_above_threshold_ms ,
1000+ event_start ,
1001+ ) -> MotionEvent :
1002+ logger .debug ("event %d high score %f" % (1 + self ._num_events , self ._highscore ))
1003+ if self ._thumbnails == "highscore" :
1004+ video_name = get_filename (path = self ._input .paths [0 ], include_extension = False )
1005+ output_path = (
1006+ self ._comp_file
1007+ if self ._comp_file
1008+ else OUTPUT_FILE_TEMPLATE .format (
1009+ VIDEO_NAME = video_name ,
1010+ EVENT_NUMBER = "%04d" % (1 + self ._num_events ),
1011+ EXTENSION = "jpg" ,
1012+ )
1013+ )
1014+ if self ._output_dir :
1015+ output_path = os .path .join (self ._output_dir , output_path )
1016+ cv2 .imwrite (output_path , self ._highframe )
1017+ self ._highscore = 0
1018+ self ._highframe = None
1019+
1020+ # Calculate event end based on the last frame we had with motion plus
1021+ # the post event length time. We also need to compensate for the number
1022+ # of frames that we skipped that could have had motion.
1023+ # We also add 1 to include the presentation duration of the last frame.
1024+ if not self ._use_pts :
1025+ event_end = FrameTimecode (
1026+ 1 + last_frame_above_threshold + self ._time_post_event .frame_num + self ._frame_skip ,
1027+ self ._input .framerate ,
1028+ )
1029+ assert event_end .frame_num >= event_start .frame_num
1030+ else :
1031+ event_end = FrameTimecode (
1032+ (last_frame_above_threshold_ms / 1000 ) + self ._time_post_event .get_seconds (),
1033+ self ._input .framerate ,
1034+ )
1035+ assert event_end .get_seconds () >= event_start .get_seconds ()
1036+ return MotionEvent (start = event_start , end = event_end )
1037+
10201038 def _decode_thread (self , decode_queue : queue .Queue ):
10211039 try :
10221040 while not self ._stop .is_set ():
0 commit comments