Skip to content

Commit c72aadd

Browse files
author
arch
committed
add x prediction
1 parent 490eeb8 commit c72aadd

File tree

3 files changed

+97
-22
lines changed

3 files changed

+97
-22
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ VERSION.txt
22
*.egg-info
33
build
44
dist
5+
debug*

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pip install pyinstaller
1919
build.bat
2020
```
2121

22-
NOTE: don't use `--onefile` with `pyinstaller`, this is way to slow
22+
This create the Windows Package in `./dist`
2323

2424
### Pip-Package (Recommend for Linux)
2525

funscript_editor/algorithms/funscriptgenerator.py

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)