@@ -28,7 +28,8 @@ class FunscriptGeneratorParameter:
2828 video_path : str
2929 start_frame : int = 0
3030 skip_frames : int = HYPERPARAMETER ['skip_frames' ]
31- min_cycle_time_in_ms : int = 0 # TODO implement cycle time (indented to set playback speed)
31+ max_playback_fps : int = 0
32+ direction : str = 'y'
3233 track_men : bool = True
3334
3435
@@ -58,7 +59,8 @@ def __init__(self,
5859
5960 self .keypress_queue = Queue (maxsize = 32 )
6061 self .stopped = False
61- self .score = []
62+ self .scone_x = []
63+ self .scone_y = []
6264 self .bboxes = {
6365 'Men' : [],
6466 'Woman' : []
@@ -169,6 +171,8 @@ def min_max_selector(self,
169171 image_min :np .ndarray ,
170172 image_max :np .ndarray ,
171173 info :str = "" ,
174+ title_min :str = "" ,
175+ title_max : str = "" ,
172176 lower_limit :int = 0 ,
173177 upper_limit :int = 99 ) -> tuple :
174178 """ Min Max selection Window
@@ -177,6 +181,8 @@ def min_max_selector(self,
177181 image_min (np.ndarray): the frame/image with lowest position
178182 image_max (np.ndarray): the frame/image with highest position
179183 info (str): additional info string th show on the Window
184+ title_min (str): title for the min selection
185+ title_max (str): title for the max selection
180186 lower_limit (int): the lower possible value
181187 upper_limit (int): the highest possible value
182188
@@ -186,8 +192,16 @@ def min_max_selector(self,
186192 cv2 .createTrackbar ("Min" , self .window_name , lower_limit , upper_limit , lambda x : None )
187193 cv2 .createTrackbar ("Max" , self .window_name , lower_limit + 1 , upper_limit , lambda x : None )
188194 image = np .concatenate ((image_min , image_max ), axis = 1 )
195+
189196 if info != "" :
190197 cv2 .putText (image , "Info: " + info , (75 , 75 ), cv2 .FONT_HERSHEY_SIMPLEX , 0.7 , (255 ,0 ,0 ), 2 )
198+
199+ if title_min != "" :
200+ cv2 .putText (image , title_min , (75 , 25 ), cv2 .FONT_HERSHEY_SIMPLEX , 0.7 , (255 ,0 ,0 ), 2 )
201+
202+ if title_max != "" :
203+ cv2 .putText (image , title_max , (image_min .shape [1 ] + 75 , 25 ), cv2 .FONT_HERSHEY_SIMPLEX , 0.7 , (255 ,0 ,0 ), 2 )
204+
191205 cv2 .putText (image , "Use 'space' to quit and set the trackbar values" ,
192206 (75 , 100 ), cv2 .FONT_HERSHEY_SIMPLEX , 0.7 , (255 ,0 ,0 ), 2 )
193207
@@ -214,10 +228,17 @@ def calculate_score(self) -> None:
214228 """ Calculate the score for the predicted tracking boxes
215229
216230 Note:
217- We use y0 from the predicted tracking boxes to create a diff score
231+ We use x0, y0 from the predicted tracking boxes to create a diff score
218232 """
219- if self .params .track_men : self .score = [m [1 ] - w [1 ] for w , m in zip (self .bboxes ['Woman' ], self .bboxes ['Men' ])]
220- else : self .score = [max ([x [1 ] for x in self .bboxes ['Woman' ]]) - w [1 ] for w in self .bboxes ['Woman' ]]
233+ if self .params .track_men :
234+ self .score_x = [m [0 ] - w [0 ] for w , m in zip (self .bboxes ['Woman' ], self .bboxes ['Men' ])]
235+ self .score_y = [m [1 ] - w [1 ] for w , m in zip (self .bboxes ['Woman' ], self .bboxes ['Men' ])]
236+ else :
237+ self .score_x = [max ([x [0 ] for x in self .bboxes ['Woman' ]]) - w [0 ] for w in self .bboxes ['Woman' ]]
238+ self .score_y = [max ([x [1 ] for x in self .bboxes ['Woman' ]]) - w [1 ] for w in self .bboxes ['Woman' ]]
239+
240+ self .score_x = sp .scale_signal (self .score_x , 0 , 100 )
241+ self .score_y = sp .scale_signal (self .score_y , 0 , 100 )
221242
222243
223244 @staticmethod
@@ -235,23 +256,28 @@ def frame_to_millisec(frame: int, fps: float) -> int:
235256 return int (round (float (frame )* float (1000 )/ fps ))
236257
237258
238- def scale_score (self , status : str ) -> None :
259+ def scale_score (self , status : str , direction : str = 'y' ) -> None :
239260 """ Scale the score to desired stroke high
240261
241262 Note:
242263 We determine the lowerst and highes positions in the score and request the real position from user.
243264
244265 Args:
245266 status (str): a status/info message to display in the window
267+ direction (str): scale the 'y' or 'x' score
246268 """
247- if len (self .score ) < 2 : return
269+ if len (self .score_y ) < 2 : return
248270
249271 cap = cv2 .VideoCapture (self .params .video_path )
250272 width = int (cap .get (cv2 .CAP_PROP_FRAME_WIDTH ))
251273 scale = self .get_scaling (width )
252274
253- min_frame = np .argmin (np .array (self .score )) + self .params .start_frame
254- max_frame = np .argmax (np .array (self .score )) + self .params .start_frame
275+ if direction == 'x' :
276+ min_frame = np .argmin (np .array (self .score_x )) + self .params .start_frame
277+ max_frame = np .argmax (np .array (self .score_x )) + self .params .start_frame
278+ else :
279+ min_frame = np .argmin (np .array (self .score_y )) + self .params .start_frame
280+ max_frame = np .argmax (np .array (self .score_y )) + self .params .start_frame
255281
256282 cap .set (cv2 .CAP_PROP_POS_FRAMES , min_frame )
257283 successMin , imgMin = cap .read ()
@@ -268,26 +294,61 @@ def scale_score(self, status: str) -> None:
268294 imgMin = imgMin [:, :int (imgMin .shape [1 ]/ 2 )]
269295 imgMax = imgMax [:, :int (imgMax .shape [1 ]/ 2 )]
270296
271- (desired_min , desired_max ) = self .min_max_selector (imgMin , imgMax , status )
297+ (desired_min , desired_max ) = self .min_max_selector (
298+ image_min = imgMin ,
299+ image_max = imgMax ,
300+ info = status ,
301+ title_min = str ("Bottom" if direction != "x" else "Left" ),
302+ title_max = ("Top" if direction != "x" else "Right" )
303+ )
272304 else :
273305 self .__logger .warning ("Determine min and max failed" )
274306 desired_min = 0
275307 desired_max = 99
276308
277- self .score = sp .scale_signal (self .score , desired_min , desired_max )
309+ if direction == 'x' :
310+ self .score_x = sp .scale_signal (self .score_x , desired_min , desired_max )
311+ else :
312+ self .score_y = sp .scale_signal (self .score_y , desired_min , desired_max )
278313
279314
280- def plot_score (self , name : str ) -> None :
315+ def plot_y_score (self , name : str , idx_list : list , dpi : int = 300 ) -> None :
281316 """ Plot the score to an figure
282317
283318 Args:
284319 name (str): file name for the figure
320+ idx_list (list): list with funscript action points
321+ dpi (int): picture output dpi
285322 """
286- if len (self .score ) < 2 : return
287- figure = Figure ()
288- ax = figure .add_subplot (1 ,1 ,1 )
289- ax .plot (self .score )
290- figure .savefig (fname = name , dpi = 300 , bbox_inches = 'tight' )
323+ if len (self .score_y ) < 2 : return
324+ if len (idx_list ) < 2 : return
325+ rows = 2
326+ figure = Figure (figsize = (max ([6 ,int (len (self .score_y )/ 50 )]), rows * 3 + 1 ), dpi = dpi )
327+ ax = figure .add_subplot (2 ,1 ,1 ) # Rows, Columns, Position
328+ ax .plot (self .score_y [idx_list [0 ]:idx_list [- 1 ]])
329+ ax .plot (idx_list , [self .score_y [idx ] for idx in idx_list ], 'o' )
330+ ax = figure .add_subplot (2 ,1 ,2 )
331+ ax .plot (idx_list , [self .score_y [idx ] for idx in idx_list ])
332+ figure .savefig (fname = name , dpi = dpi , bbox_inches = 'tight' )
333+
334+
335+ def plot_scores (self , name : str , dpi : int = 300 ) -> None :
336+ """ Plot the score to an figure
337+
338+ Args:
339+ name (str): file name for the figure
340+ dpi (int): picture output dpi
341+ """
342+ if len (self .score_y ) < 2 : return
343+ rows = 2
344+ figure = Figure (figsize = (max ([6 ,int (len (self .score_y )/ 50 )]), rows * 3 + 1 ), dpi = dpi )
345+ ax = figure .add_subplot (2 ,1 ,1 ) # Rows, Columns, Position
346+ ax .title .set_text ('Motion in x direction' )
347+ ax .plot (self .score_x )
348+ ax = figure .add_subplot (2 ,1 ,2 )
349+ ax .title .set_text ('Motion in y direction' )
350+ ax .plot (self .score_y )
351+ figure .savefig (fname = name , dpi = dpi , bbox_inches = 'tight' )
291352
292353
293354 def delete_last_tracking_predictions (self , num :int ) -> None :
@@ -459,19 +520,32 @@ def finished(self, status: str, success :bool) -> None:
459520
460521 def run (self ) -> None :
461522 """ The Funscript Generator Thread Function """
523+ # NOTE: score_y and score_x should have the same number of elements so it should be enouth to check one score length
462524 with Listener (on_press = self .on_key_press ) as listener :
463525 status = self .tracking ()
464526 self .calculate_score ()
465- if len (self .score ) >= HYPERPARAMETER ['min_frames' ]: self .scale_score (status )
527+ if len (self .score_y ) >= HYPERPARAMETER ['min_frames' ]:
528+ if self .params .direction != 'x' :
529+ self .scale_score (status , direction = 'y' )
530+ else :
531+ self .scale_score (status , direction = 'x' )
466532
467- if len (self .score ) < HYPERPARAMETER ['min_frames' ]:
533+ if len (self .score_y ) < HYPERPARAMETER ['min_frames' ]:
468534 self .finished (status + ' -> Tracking time insufficient' , False )
469535 return
470536
471- idx_list = sp .get_local_max_and_min_idx (self .score , self .fps )
537+ if self .params .direction != 'x' :
538+ idx_list = sp .get_local_max_and_min_idx (self .score_y , self .fps )
539+ else :
540+ idx_list = sp .get_local_max_and_min_idx (self .score_x , self .fps )
541+
542+ if False :
543+ if self .params .direction != 'x' : self .plot_y_score ('debug_001.png' , idx_list )
544+ self .plot_scores ('debug_002.png' )
545+
472546 for idx in idx_list :
473547 self .funscript .add_action (
474- self .score [idx ],
548+ self .score_y [idx ],
475549 self .frame_to_millisec (idx + self .params .start_frame , self .fps )
476550 )
477551
0 commit comments