@@ -32,21 +32,35 @@ class FunscriptGeneratorParameter:
3232 start_frame : int = 0 # default is video start (input: set current video position)
3333 end_frame : int = - 1 # default is video end (-1)
3434 track_men : bool = True # set by userinput at start (message box)
35- skip_frames : int = max ((0 , int (HYPERPARAMETER ['skip_frames' ])))
35+
36+ # Settings
3637 max_playback_fps : int = max ((0 , int (SETTINGS ['max_playback_fps' ])))
3738 direction : str = SETTINGS ['tracking_direction' ]
3839 use_zoom : bool = SETTINGS ['use_zoom' ]
39- shift_bottom_points : int = int (HYPERPARAMETER ['shift_bottom_points' ])
40- shift_top_points : int = int (HYPERPARAMETER ['shift_top_points' ])
41- top_points_offset : float = float (HYPERPARAMETER ['top_points_offset' ])
42- bottom_points_offset : float = float (HYPERPARAMETER ['bottom_points_offset' ])
4340 zoom_factor : float = max ((1.0 , float (SETTINGS ['zoom_factor' ])))
44- top_threshold : float = float (HYPERPARAMETER ['top_threshold' ])
45- bottom_threshold : float = float (HYPERPARAMETER ['bottom_threshold' ])
4641 preview_scaling : float = float (SETTINGS ['preview_scaling' ])
4742 projection : str = str (SETTINGS ['projection' ]).lower ()
4843 use_kalman_filter : bool = SETTINGS ['use_kalman_filter' ]
4944
45+ # General
46+ skip_frames : int = max ((0 , int (HYPERPARAMETER ['skip_frames' ])))
47+
48+ # y-Movement
49+ shift_bottom_points : int = int (HYPERPARAMETER ['shift_bottom_points' ])
50+ shift_top_points : int = int (HYPERPARAMETER ['shift_top_points' ])
51+ bottom_points_offset : float = float (HYPERPARAMETER ['bottom_points_offset' ])
52+ top_points_offset : float = float (HYPERPARAMETER ['top_points_offset' ])
53+ bottom_threshold : float = float (HYPERPARAMETER ['bottom_threshold' ])
54+ top_threshold : float = float (HYPERPARAMETER ['top_threshold' ])
55+
56+ # x-Movement
57+ shift_left_points : int = int (HYPERPARAMETER ['shift_left_points' ])
58+ shift_right_points : int = int (HYPERPARAMETER ['shift_right_points' ])
59+ left_points_offset : float = float (HYPERPARAMETER ['left_points_offset' ])
60+ right_points_offset : float = float (HYPERPARAMETER ['right_points_offset' ])
61+ left_threshold : float = float (HYPERPARAMETER ['left_threshold' ])
62+ right_threshold : float = float (HYPERPARAMETER ['right_threshold' ])
63+
5064
5165class FunscriptGenerator (QtCore .QThread ):
5266 """ Funscript Generator Thread
@@ -74,7 +88,8 @@ def __init__(self,
7488 self .tracking_fps = []
7589 self .score = {
7690 'x' : [],
77- 'y' : []
91+ 'y' : [],
92+ 'd' : []
7893 }
7994 self .bboxes = {
8095 'Men' : [],
@@ -283,12 +298,15 @@ def calculate_score(self) -> None:
283298 if self .params .track_men :
284299 self .score ['x' ] = [m [0 ] - w [0 ] for w , m in zip (self .bboxes ['Woman' ], self .bboxes ['Men' ])]
285300 self .score ['y' ] = [m [1 ] - w [1 ] for w , m in zip (self .bboxes ['Woman' ], self .bboxes ['Men' ])]
301+ self .score ['d' ] = [np .sqrt (np .sum ((np .array (m [:2 ]) - np .array (w [:2 ])) ** 2 , axis = 0 )) for w , m in zip (bboxes ['Woman' ], bboxes ['Men' ])]
286302 else :
287303 self .score ['x' ] = [max ([x [0 ] for x in self .bboxes ['Woman' ]]) - w [0 ] for w in self .bboxes ['Woman' ]]
288304 self .score ['y' ] = [max ([x [1 ] for x in self .bboxes ['Woman' ]]) - w [1 ] for w in self .bboxes ['Woman' ]]
305+ # TODO: how to calc d?
289306
290307 self .score ['x' ] = sp .scale_signal (self .score ['x' ], 0 , 100 )
291308 self .score ['y' ] = sp .scale_signal (self .score ['y' ], 0 , 100 )
309+ self .score ['d' ] = sp .scale_signal (self .score ['d' ], 0 , 100 )
292310
293311
294312 def scale_score (self , status : str , direction : str = 'y' ) -> None :
@@ -307,7 +325,10 @@ def scale_score(self, status: str, direction : str = 'y') -> None:
307325 width = int (cap .get (cv2 .CAP_PROP_FRAME_WIDTH ))
308326 height = int (cap .get (cv2 .CAP_PROP_FRAME_HEIGHT ))
309327
310- if direction == 'x' :
328+ if direction == 'd' :
329+ min_frame = np .argmin (np .array (self .score ['d' ])) + self .params .start_frame
330+ max_frame = np .argmax (np .array (self .score ['d' ])) + self .params .start_frame
331+ elif direction == 'x' :
311332 min_frame = np .argmin (np .array (self .score ['x' ])) + self .params .start_frame
312333 max_frame = np .argmax (np .array (self .score ['x' ])) + self .params .start_frame
313334 else :
@@ -337,19 +358,35 @@ def scale_score(self, status: str, direction : str = 'y') -> None:
337358 imgMin = cv2 .resize (imgMin , None , fx = scale , fy = scale )
338359 imgMax = cv2 .resize (imgMax , None , fx = scale , fy = scale )
339360
361+ if direction == 'y' :
362+ title_min = "Bottom"
363+ elif direction == 'x' :
364+ title_min = "Left"
365+ else :
366+ title_min = "Minimum"
367+
368+ if direction == 'y' :
369+ title_max = "Top"
370+ elif direction == 'x' :
371+ title_max = "Right"
372+ else :
373+ title_max = "Maxmimum"
374+
340375 (desired_min , desired_max ) = self .min_max_selector (
341376 image_min = imgMin ,
342377 image_max = imgMax ,
343378 info = status ,
344- title_min = str ( "Bottom" if direction != "x" else "Left" ) ,
345- title_max = ( "Top" if direction != "x" else "Right" )
379+ title_min = title_min ,
380+ title_max = title_max
346381 )
347382 else :
348383 self .logger .warning ("Determine min and max failed" )
349384 desired_min = 0
350385 desired_max = 99
351386
352- if direction == 'x' :
387+ if direction == 'd' :
388+ self .score ['d' ] = sp .scale_signal (self .score ['d' ], desired_min , desired_max )
389+ elif direction == 'x' :
353390 self .score ['x' ] = sp .scale_signal (self .score ['x' ], desired_min , desired_max )
354391 else :
355392 self .score ['y' ] = sp .scale_signal (self .score ['y' ], desired_min , desired_max )
@@ -729,15 +766,29 @@ def apply_shift(self, frame_number, position: str) -> int:
729766 Args:
730767 position (str): is max or min
731768 """
732- if position in ['max' , 'top' ] and self .params .direction != 'x' :
733- if frame_number >= - 1 * self .params .shift_top_points \
734- and frame_number + self .params .shift_top_points < len (self .score ['y' ]): \
735- return self .params .start_frame + frame_number + self .params .shift_top_points
769+ if self .params .direction == 'd' :
770+ shift_a = 0
771+ elif self .params .direction == 'x' :
772+ shift_a = self .params .shift_right_points
773+ else :
774+ shift_a = self .params .shift_top_points
775+
776+ if self .params .direction == 'd' :
777+ shift_b = 0
778+ elif self .params .direction == 'x' :
779+ shift_b = self .params .shift_left_points
780+ else :
781+ shift_b = self .params .shift_bottom_points
736782
737- if position in ['min' , 'bottom' ] and self .params .direction != 'x' :
738- if frame_number >= - 1 * self .params .shift_bottom_points \
739- and frame_number + self .params .shift_bottom_points < len (self .score ['y' ]): \
740- return self .params .start_frame + frame_number + self .params .shift_bottom_points
783+ if position in ['max' , 'top' , 'right' ] :
784+ if frame_number >= - 1 * shift_a \
785+ and frame_number + shift_a < len (self .score ['y' ]): \
786+ return self .params .start_frame + frame_number + shift_a
787+
788+ if position in ['min' , 'bottom' , 'left' ]:
789+ if frame_number >= - 1 * shift_b \
790+ and frame_number + shift_b < len (self .score ['y' ]): \
791+ return self .params .start_frame + frame_number + shift_b
741792
742793 return self .params .start_frame + frame_number
743794
@@ -751,16 +802,28 @@ def get_score_with_offset(self, idx_dict) -> list:
751802 Returns:
752803 list: score with offset
753804 """
754- if self .params .direction == 'x' :
755- return self .score ['x' ]
805+ if self .params .direction == 'd' :
806+ offset_a = 0
807+ elif self .params .direction == 'x' :
808+ offset_a = self .params .right_points_offset
809+ else :
810+ offset_a = self .params .top_points_offset
811+
812+ if self .params .direction == 'd' :
813+ offset_b = 0
814+ elif self .params .direction == 'x' :
815+ offset_b = self .params .left_points_offset
816+ else :
817+ offset_b = self .params .bottom_points_offset
756818
757819 score = copy .deepcopy (self .score ['y' ])
758820 score_min , score_max = min (score ), max (score )
759- for idx in idx_dict ['min' ]:
760- score [idx ] = max (( score_min , min ((score_max , score [idx ] + self .params .bottom_points_offset )) ))
761821
762822 for idx in idx_dict ['max' ]:
763- score [idx ] = max (( score_min , min ((score_max , score [idx ] + self .params .top_points_offset )) ))
823+ score [idx ] = max (( score_min , min ((score_max , score [idx ] + offset_a )) ))
824+
825+ for idx in idx_dict ['min' ]:
826+ score [idx ] = max (( score_min , min ((score_max , score [idx ] + offset_b )) ))
764827
765828 return score
766829
@@ -786,55 +849,79 @@ def apply_kalman_filter(self) -> None:
786849 self .bboxes ['Men' ][idx ] = (prediction [0 ], prediction [1 ], item [2 ], item [3 ])
787850
788851
789- def run (self ) -> None :
790- """ The Funscript Generator Thread Function """
791- # NOTE: score['y'] and score['x'] should have the same number size so it should be enouth to check one score length
792- with Listener (on_press = self .on_key_press ) as listener :
793- status = self .tracking ()
794-
795- if self .params .use_kalman_filter :
796- self .apply_kalman_filter ()
797-
798- if len (self .score ['y' ]) >= HYPERPARAMETER ['min_frames' ]:
799- self .logger .info ("Scale score" )
800- if self .params .direction != 'x' :
801- self .scale_score (status , direction = 'y' )
802- else :
803- self .scale_score (status , direction = 'x' )
804-
805- if len (self .score ['y' ]) < HYPERPARAMETER ['min_frames' ]:
806- self .finished (status + ' -> Tracking time insufficient' , False )
807- return
852+ def determin_change_points (self ) -> dict :
853+ """ Determine all change points
808854
855+ Returns:
856+ dict: all local max and min points in score {'min':[idx1, idx2, ...], 'max':[idx1, idx2, ...]}
857+ """
809858 self .logger .info ("Determine local max and min" )
810- if self .params .direction != 'x ' :
811- idx_dict = sp .get_local_max_and_min_idx (self .score ['y ' ], self .video_info .fps )
812- else :
859+ if self .params .direction == 'd ' :
860+ idx_dict = sp .get_local_max_and_min_idx (self .score ['d ' ], self .video_info .fps )
861+ elif self . params . direction == 'x' :
813862 idx_dict = sp .get_local_max_and_min_idx (self .score ['x' ], self .video_info .fps )
863+ else :
864+ idx_dict = sp .get_local_max_and_min_idx (self .score ['y' ], self .video_info .fps )
865+ return idx_dict
814866
815- idx_list = [x for k in ['min' , 'max' ] for x in idx_dict [k ]]
816- idx_list .sort ()
817867
818- if False :
819- self .plot_scores ('debug_001.png' )
820- if self .params .direction != 'x' :
821- self .plot_y_score ('debug_002.png' , idx_list )
868+ def create_funscript (self , idx_dict : dict ) -> None :
869+ """ Generate the Funscript
822870
871+ Args:
872+ idx_dict (dict): dictionary with all local max and min points in score
873+ {'min':[idx1, idx2, ...], 'max':[idx1, idx2, ...]}
874+ """
823875 output_score = self .get_score_with_offset (idx_dict )
876+
877+ if self .params .direction == 'd' :
878+ threshold_a = 0
879+ elif self .params .direction == 'x' :
880+ threshold_a = self .params .left_threshold
881+ else :
882+ threshold_a = self .params .bottom_threshold
883+
884+ if self .params .direction == 'd' :
885+ threshold_b = 0
886+ elif self .params .direction == 'x' :
887+ threshold_b = self .params .right_threshold
888+ else :
889+ threshold_b = self .params .top_threshold
890+
824891 for idx in idx_dict ['min' ]:
825892 self .funscript .add_action (
826893 min (output_score ) \
827- if output_score [idx ] < min (output_score ) + self . params . bottom_threshold \
894+ if output_score [idx ] < min (output_score ) + threshold_a \
828895 else round (output_score [idx ]),
829896 FFmpegStream .frame_to_millisec (self .apply_shift (idx , 'min' ), self .video_info .fps )
830897 )
831898
832899 for idx in idx_dict ['max' ]:
833900 self .funscript .add_action (
834901 max (output_score ) \
835- if output_score [idx ] > max (output_score ) - self . params . top_threshold \
902+ if output_score [idx ] > max (output_score ) - threshold_b \
836903 else round (output_score [idx ]),
837904 FFmpegStream .frame_to_millisec (self .apply_shift (idx , 'max' ), self .video_info .fps )
838905 )
839906
907+
908+ def run (self ) -> None :
909+ """ The Funscript Generator Thread Function """
910+ # NOTE: score['y'] and score['x'] should have the same number size so it should be enouth to check one score length
911+ with Listener (on_press = self .on_key_press ) as listener :
912+ status = self .tracking ()
913+
914+ if self .params .use_kalman_filter :
915+ self .apply_kalman_filter ()
916+
917+ if len (self .score ['y' ]) >= HYPERPARAMETER ['min_frames' ]:
918+ self .logger .info ("Scale score" )
919+ self .scale_score (status , direction = self .params .direction )
920+
921+ if len (self .score ['y' ]) < HYPERPARAMETER ['min_frames' ]:
922+ self .finished (status + ' -> Tracking time insufficient' , False )
923+ return
924+
925+ idx_dict = self .determin_change_points ()
926+ self .create_funscript (idx_dict )
840927 self .finished (status , True )
0 commit comments