88from dataclasses import dataclass
99from threading import Thread
1010from queue import Queue
11- from funscript_editor .utils .config import SETTINGS
11+ from funscript_editor .utils .config import SETTINGS , HYPERPARAMETER
1212
1313import numpy as np
1414
@@ -21,6 +21,7 @@ class StaticVideoTracker:
2121 Args:
2222 first_frame (np.ndarray): open cv image representing the start frame
2323 tracking_bbox (tuple): tuple with (x,y,w,h) of the init tracking box
24+ fps (float): video fps
2425 limit_searchspace (dict) : only insert the specified region around the init box
2526 supervised_tracking_area (tuple, optional): tuple with (x,y,w,h) of the supervised tracking area
2627 queue_size (int): in (work) and out (result) queue size
@@ -29,20 +30,27 @@ class StaticVideoTracker:
2930 def __init__ (self ,
3031 first_frame : np .ndarray ,
3132 tracking_bbox : tuple ,
33+ fps : float ,
3234 limit_searchspace : dict = {'h' : 0.45 , 'w' :0.4 },
3335 supervised_tracking_area : tuple = None ,
3436 queue_size : int = 2 ):
3537 self .first_frame = first_frame
3638 self .limit_searchspace = limit_searchspace
3739 self .first_tracking_bbox = tracking_bbox
3840 self .supervised_tracking_area = supervised_tracking_area
41+ self .fps = max ((1 ,fps ))
3942 self .stopped = False
4043 self .sleep_time = 0.001
4144 self .queue_in = Queue (maxsize = queue_size )
4245 self .queue_out = Queue (maxsize = queue_size )
4346 self .thread = Thread (target = self .run , args = ())
4447 self .thread .daemon = True
4548 self .thread .start ()
49+ self .tracking_points = []
50+ self .tracking_counter = 0
51+ self .cluster_center = [0 , 0 ]
52+ self .plausibility_thresholds = [0 , 0 ]
53+
4654
4755
4856 __logger = logging .getLogger (__name__ )
@@ -53,6 +61,7 @@ class Status:
5361 OK :str = "OK"
5462 TRACKING_LOST :str = "Tracking Lost"
5563 FEATURE_OUTSIDE :str = "Feature outside the specified area"
64+ IMPLAUSIBLE :str = "Tracking point is not plausible"
5665
5766
5867 @staticmethod
@@ -126,6 +135,47 @@ def __setup_tracker(self) -> None:
126135 self .tracker = cv2 .TrackerCSRT_create ()
127136
128137
138+ def __is_plausible (self , box ) -> bool :
139+ tracking_init_phase_in_sec = HYPERPARAMETER ['tracking_init_phase_in_sec' ]
140+ tracking_plausibility_factor_x = HYPERPARAMETER ['tracking_plausibility_factor_x' ]
141+ tracking_plausibility_factor_y = HYPERPARAMETER ['tracking_plausibility_factor_y' ]
142+ if self .tracking_counter <= round (self .fps * tracking_init_phase_in_sec ):
143+ self .tracking_points .append ([box [0 ] + box [2 ]/ 2 , box [1 ] + box [3 ]/ 2 ])
144+ if self .tracking_counter == round (self .fps * tracking_init_phase_in_sec ):
145+ self .__logger .info ("Determine Plausibility Threshold for Tracker" )
146+ self .cluster_center = np .mean (np .array (self .tracking_points ), axis = 0 )
147+ distances_x = [abs (self .cluster_center [0 ] - point [0 ]) for point in self .tracking_points ]
148+ distances_y = [abs (self .cluster_center [1 ] - point [1 ]) for point in self .tracking_points ]
149+ self .plausibility_thresholds = [ \
150+ max (distances_x ) * tracking_plausibility_factor_x , \
151+ max (distances_y ) * tracking_plausibility_factor_y \
152+ ]
153+ else :
154+ point = [box [0 ] + box [2 ]/ 2 , box [1 ] + box [3 ]/ 2 ]
155+ if abs (point [0 ] - self .cluster_center [0 ]) > self .plausibility_thresholds [0 ]:
156+ self .__logger .warning (
157+ "Tracking point x is not plausible (%d > %d +- %d)" ,
158+ round (point [0 ]),
159+ round (self .cluster_center [0 ]),
160+ round (self .plausibility_thresholds [0 ])
161+ )
162+ return False
163+
164+ if abs (point [1 ] - self .cluster_center [1 ]) > self .plausibility_thresholds [1 ]:
165+ self .__logger .warning (
166+ "Tracking point y is not plausible (%d > %d +- %d)" ,
167+ round (point [1 ]),
168+ round (self .cluster_center [1 ]),
169+ round (self .plausibility_thresholds [1 ])
170+ )
171+ return False
172+
173+ self .cluster_center [0 ] = (self .cluster_center [0 ] * self .tracking_counter + point [0 ]) / (self .tracking_counter + 1 )
174+ self .cluster_center [1 ] = (self .cluster_center [1 ] * self .tracking_counter + point [1 ]) / (self .tracking_counter + 1 )
175+
176+ return True
177+
178+
129179 def run (self ) -> None :
130180 """ The Video Tracker Thread Function """
131181 self .__setup_tracker ()
@@ -155,15 +205,18 @@ def run(self) -> None:
155205 frame = self .queue_in .get ()
156206 frame_roi = frame [y0 :y1 , x0 :x1 ]
157207 success , bbox = self .tracker .update (frame_roi )
208+ self .tracking_counter += 1
158209 status = StaticVideoTracker .Status .TRACKING_LOST
159- if success :
210+ if not success or bbox is None :
211+ bbox = None
212+ else :
160213 status = StaticVideoTracker .Status .OK
161214 bbox = (int (bbox [0 ] + x0 ), int (bbox [1 ] + y0 ), int (bbox [2 ]), int (bbox [3 ]))
162215 if not StaticVideoTracker .is_bbox_in_tracking_area (bbox , self .supervised_tracking_area ):
163216 status = StaticVideoTracker .Status .FEATURE_OUTSIDE
164- bbox = None
165- else :
166- bbox = None
217+ elif SETTINGS [ 'tracking_plausibility_check' ]:
218+ if not self . __is_plausible ( bbox ) :
219+ status = StaticVideoTracker . Status . IMPLAUSIBLE
167220
168221 self .queue_out .put ((status , bbox ))
169222
0 commit comments