Skip to content

Commit d7baf58

Browse files
author
arch
committed
reimplement scaling part 1
1 parent 68d6015 commit d7baf58

File tree

2 files changed

+68
-106
lines changed

2 files changed

+68
-106
lines changed

funscript_editor/algorithms/funscriptgenerator.py

Lines changed: 67 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@
1212
from pynput.keyboard import Key, Listener
1313
from dataclasses import dataclass
1414
from funscript_editor.data.funscript import Funscript
15-
from funscript_editor.data.filevideostream import FileVideoStream
1615
from funscript_editor.algorithms.videotracker import StaticVideoTracker
1716
from PyQt5 import QtGui, QtCore, QtWidgets
1817
from matplotlib.figure import Figure
19-
from funscript_editor.definitions import VIDEO_SCALING_CONFIG_FILE
2018
from funscript_editor.utils.config import HYPERPARAMETER, SETTINGS
2119
from datetime import datetime
22-
from funscript_editor.data.equirectangularvideostream import EquirectangularVideoStream
20+
from funscript_editor.data.ffmpegstream import FFmpegStream, FFmpegStreamParameter, VideoInfo
2321

2422
import funscript_editor.algorithms.signalprocessing as sp
2523
import numpy as np
@@ -41,15 +39,10 @@ class FunscriptGeneratorParameter:
4139
shift_top_points :int = int(HYPERPARAMETER['shift_top_points'])
4240
top_points_offset :float = float(HYPERPARAMETER['top_points_offset'])
4341
bottom_points_offset :float = float(HYPERPARAMETER['bottom_points_offset'])
44-
use_equirectangular :bool = SETTINGS['use_equirectangular']
45-
equirectangular_scaling :float = max((0.2, float(SETTINGS['equirectangular_scaling'])))
4642
zoom_factor :float = max((1.0, float(SETTINGS['zoom_factor'])))
47-
scaling_method :str = SETTINGS['scaling_method']
4843
top_threshold :float = float(HYPERPARAMETER['top_threshold'])
4944
bottom_threshold :float = float(HYPERPARAMETER['bottom_threshold'])
50-
equirectangular_height :int = 720
51-
equirectangular_width :int = 1240
52-
equirectangular_fov :int = 100
45+
preview_scaling :float = 1.0
5346

5447

5548
class FunscriptGenerator(QtCore.QThread):
@@ -71,11 +64,6 @@ def __init__(self,
7164
# XXX destroyWindow(...) sems not to delete the trackbar. Workaround: we give the window each time a unique name
7265
self.window_name = "Funscript Generator ({})".format(datetime.now().strftime("%H:%M:%S"))
7366

74-
# scale config for the video
75-
with open(VIDEO_SCALING_CONFIG_FILE, 'r') as config_file:
76-
self.scale = json.load(config_file)
77-
self.scale = {int(k) : float(v) for k,v in self.scale.items()}
78-
7967
self.keypress_queue = Queue(maxsize=32)
8068
self.stopped = False
8169
self.perspective_params = {}
@@ -98,37 +86,8 @@ def __init__(self,
9886
#: processing event with current processed frame number
9987
processStatus = QtCore.pyqtSignal(int)
10088

101-
__logger = logging.getLogger(__name__)
102-
103-
def get_scaling(self, frame_width: int, frame_height: int) -> float:
104-
""" Get the scaling parameter for current video
105-
106-
Args:
107-
frame_width (int): frame width of current video
108-
frame_height (int): frame height of current video
89+
logger = logging.getLogger(__name__)
10990

110-
Returns:
111-
float: scaling parameter from scaling config
112-
"""
113-
if self.params.scaling_method == 'auto':
114-
scale = []
115-
try:
116-
for monitor in get_monitors():
117-
scale.append( min((monitor.width / (frame_width*1.1), monitor.height / (frame_height*1.1) )) )
118-
except: pass
119-
120-
if len(scale) == 0:
121-
self.__logger.error("Monitor info not found, please switch to scaling_method: 'config'")
122-
scale = 1.0
123-
else:
124-
# asume we use the largest monitor for scipting
125-
scale = max(scale)
126-
else:
127-
# assume scaling_method is 'config' (fallback)
128-
scale = max([0.05, min([8.0]+[self.scale[k] for k in self.scale.keys() if k < frame_width])])
129-
130-
self.__logger.info("Use scaling of %f for preview window and tracking", scale)
131-
return scale
13291

13392
def drawBox(self, img: np.ndarray, bbox: tuple) -> np.ndarray:
13493
""" Draw an tracking box on the image/frame
@@ -256,7 +215,7 @@ def min_max_selector(self,
256215
(self.x_text_start, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2)
257216
cv2.putText(preview, "Set {} to {}".format('Max', trackbarValueMax),
258217
(image_min.shape[1] + self.x_text_start, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2)
259-
cv2.imshow(self.window_name, preview)
218+
cv2.imshow(self.window_name, self.preview_scaling(preview))
260219
if self.was_space_pressed() or cv2.waitKey(25) == ord(' '): break
261220
trackbarValueMin = cv2.getTrackbarPos("Min", self.window_name)
262221
trackbarValueMax = cv2.getTrackbarPos("Max", self.window_name)
@@ -332,17 +291,7 @@ def scale_score(self, status: str, direction : str = 'y') -> None:
332291
cap.release()
333292

334293
if successMin and successMax:
335-
if self.params.use_equirectangular:
336-
dim = (int(self.params.equirectangular_width*self.params.equirectangular_scaling), \
337-
int(self.params.equirectangular_height*self.params.equirectangular_scaling))
338-
imgMin = cv2.resize(imgMin, dim)
339-
imgMax = cv2.resize(imgMax, dim)
340-
else:
341-
scale = self.get_scaling(width, height)
342-
imgMin = cv2.resize(imgMin, None, fx=scale, fy=scale)
343-
imgMax = cv2.resize(imgMax, None, fx=scale, fy=scale)
344-
345-
# Assume we have VR 3D Side by Side
294+
# TODO: Assume we have VR 3D Side by Side
346295
imgMin = imgMin[:, :int(imgMin.shape[1]/2)]
347296
imgMax = imgMax[:, :int(imgMax.shape[1]/2)]
348297

@@ -354,7 +303,7 @@ def scale_score(self, status: str, direction : str = 'y') -> None:
354303
title_max = ("Top" if direction != "x" else "Right")
355304
)
356305
else:
357-
self.__logger.warning("Determine min and max failed")
306+
self.logger.warning("Determine min and max failed")
358307
desired_min = 0
359308
desired_max = 99
360309

@@ -423,19 +372,40 @@ def delete_last_tracking_predictions(self, num :int) -> None:
423372
if self.params.track_men: del self.bboxes['Men'][i]
424373

425374

426-
def get_perspective_roi(self, image :np.ndarray) -> None:
427-
""" Get the perspective ROI parameters form user input
375+
def preview_scaling(self, preview_image :np.ndarray) -> np.ndarray:
376+
""" Scale image for preview
377+
378+
Args:
379+
preview_image (np.ndarray): opencv image
380+
381+
Returns:
382+
np.ndarray: scaled opencv image
383+
"""
384+
"""
385+
frame_height, frame_width = preview_image.shape[:2]
386+
scale = []
387+
try:
388+
for monitor in get_monitors():
389+
scale.append( min((monitor.width / (frame_width*1.1), monitor.height / (frame_height*1.1) )) )
390+
except: pass
391+
392+
if len(scale) == 0:
393+
self.logger.error("Monitor resolution info not found")
394+
scale = 1.0
395+
else:
396+
# asume we use the largest monitor for scipting
397+
scale = max(scale)
398+
"""
399+
return cv2.resize(preview_image, None, fx=self.params.preview_scaling, fy=self.params.preview_scaling)
400+
401+
402+
def get_projection_parameter(self, image :np.ndarray) -> None:
403+
""" Get the projection ROI parameters form user input
428404
429405
Args:
430406
image (np.ndarray): opencv vr 180 or 360 image
431407
"""
432-
perspective = {
433-
'FOV': self.params.equirectangular_fov,
434-
'THETA': -90,
435-
'PHI': -45,
436-
'height': int(self.params.equirectangular_height*self.params.equirectangular_scaling),
437-
'width': int(self.params.equirectangular_width*self.params.equirectangular_scaling)
438-
}
408+
parameter = FFmpegStreamParameter()
439409

440410
# NOTE: improve processing speed to make this menu more responsive
441411
if image.shape[0] > 6000 or image.shape[1] > 6000:
@@ -448,42 +418,37 @@ def get_perspective_roi(self, image :np.ndarray) -> None:
448418
while not selected:
449419
if parameter_changed:
450420
parameter_changed = False
451-
preview = EquirectangularVideoStream.get_perspective(
452-
image,
453-
perspective['FOV'],
454-
perspective['THETA'],
455-
perspective['PHI'],
456-
perspective['height'],
457-
perspective['width']
458-
)
421+
preview = FFmpegStream.get_projection(image, parameter)
459422

460423
preview = self.drawText(preview, "Press 'q' to use current selected region of interest)",
461424
y = 50, color = (255, 0, 0))
462425
preview = self.drawText(preview, "Use 'w', 's' to move up/down to the region of interest",
463426
y = 75, color = (0, 255, 0))
464427

465-
cv2.imshow(self.window_name, preview)
428+
cv2.imshow(self.window_name, self.preview_scaling(preview))
429+
466430
while self.keypress_queue.qsize() > 0:
467431
pressed_key = '{0}'.format(self.keypress_queue.get())
468432
if pressed_key == "'q'":
469433
selected = True
470434
break
471435
elif pressed_key == "'w'":
472-
perspective['PHI'] = min((80, perspective['PHI'] + 5))
436+
parameter.phi = min((80, parameter.phi + 5))
473437
parameter_changed = True
474438
elif pressed_key == "'s'":
475-
perspective['PHI'] = max((-80, perspective['PHI'] - 5))
439+
parameter.phi = max((-80, parameter.phi - 5))
476440
parameter_changed = True
477441

478442
if cv2.waitKey(1) in [ord('q')]: break
479443

480444
try:
481445
background = np.full(preview.shape, 0, dtype=np.uint8)
482446
loading_screen = self.drawText(background, "Please wait ...")
483-
cv2.imshow(self.window_name, loading_screen)
447+
cv2.imshow(self.window_name, self.preview_scaling(loading_screen))
448+
cv2.waitKey(1)
484449
except: pass
485450

486-
self.perspective_params = perspective
451+
return parameter
487452

488453

489454
def get_bbox(self, image: np.ndarray, txt: str) -> tuple:
@@ -498,27 +463,34 @@ def get_bbox(self, image: np.ndarray, txt: str) -> tuple:
498463
"""
499464
image = self.drawText(image, "Press 'space' or 'enter' to continue (sometimes not very responsive)",
500465
y = 75, color = (255, 0, 0))
501-
# cv2.namedWindow(self.window_name)
502-
# cv2.createTrackbar("Scale", self.window_name, 1, 10, lambda x: None)
503466

504467
if self.params.use_zoom:
505468
while True:
506469
zoom_bbox = cv2.selectROI(self.window_name, self.drawText(image, "Zoom selected area"), False)
507470
if zoom_bbox is None or len(zoom_bbox) == 0: continue
508471
if zoom_bbox[2] < 75 or zoom_bbox[3] < 75:
509-
self.__logger.error("The selected zoom area is to small")
472+
self.logger.error("The selected zoom area is to small")
510473
continue
511474
break
512475

513476
image = image[zoom_bbox[1]:zoom_bbox[1]+zoom_bbox[3], zoom_bbox[0]:zoom_bbox[0]+zoom_bbox[2]]
514477
image = cv2.resize(image, None, fx=self.params.zoom_factor, fy=self.params.zoom_factor)
515478

479+
image = self.drawText(image, txt)
480+
image = self.preview_scaling(image)
516481
while True:
517-
bbox = cv2.selectROI(self.window_name, self.drawText(image, txt), False)
482+
bbox = cv2.selectROI(self.window_name, image, False)
518483
if bbox is None or len(bbox) == 0: continue
519484
if bbox[0] == 0 or bbox[1] == 0 or bbox[2] < 9 or bbox[3] < 9: continue
520485
break
521486

487+
# revert the preview scaling
488+
bbox = (round(bbox[0]/self.params.preview_scaling),
489+
round(bbox[1]/self.params.preview_scaling),
490+
round(bbox[2]/self.params.preview_scaling),
491+
round(bbox[3]/self.params.preview_scaling)
492+
)
493+
522494
if self.params.use_zoom:
523495
bbox = (round(bbox[0]/self.params.zoom_factor)+zoom_bbox[0],
524496
round(bbox[1]/self.params.zoom_factor)+zoom_bbox[1],
@@ -548,33 +520,23 @@ def tracking(self) -> str:
548520
Returns:
549521
str: a process status message e.g. 'end of video reached'
550522
"""
551-
if self.params.use_equirectangular:
552-
first_frame = self.get_first_frame()
553-
self.get_perspective_roi(first_frame)
554-
555-
video = EquirectangularVideoStream(
556-
self.params.video_path,
557-
self.perspective_params['FOV'],
558-
self.perspective_params['THETA'],
559-
self.perspective_params['PHI'],
560-
self.perspective_params['height'],
561-
self.perspective_params['width'],
562-
self.params.start_frame
563-
)
564-
else:
565-
video = FileVideoStream(
566-
video_path=self.params.video_path,
567-
scale_determiner=self.get_scaling,
568-
start_frame=self.params.start_frame)
523+
first_frame = FFmpegStream.get_frame(self.params.video_path, self.params.start_frame)
524+
projection_parameter = self.get_projection_parameter(first_frame)
525+
526+
video = FFmpegStream(
527+
video_path = self.params.video_path,
528+
parameter = projection_parameter,
529+
start_frame = self.params.start_frame
530+
)
569531

570532
first_frame = video.read()
571533
bboxWoman = self.get_bbox(first_frame, "Select Woman Feature")
572-
trackerWoman = StaticVideoTracker(first_frame, bboxWoman, limit_searchspace = not self.params.use_equirectangular)
534+
trackerWoman = StaticVideoTracker(first_frame, bboxWoman, limit_searchspace = False) # TODO limit_searchspace for flat videos
573535
self.bboxes['Woman'].append(bboxWoman)
574536

575537
if self.params.track_men:
576538
bboxMen = self.get_bbox(self.drawBox(first_frame, bboxWoman), "Select Men Feature")
577-
trackerMen = StaticVideoTracker(first_frame, bboxMen, limit_searchspace = not self.params.use_equirectangular)
539+
trackerMen = StaticVideoTracker(first_frame, bboxMen, limit_searchspace = False) # TODO limit_searchspace for flat videos
578540
self.bboxes['Men'].append(bboxMen)
579541

580542
if self.params.max_playback_fps > (self.params.skip_frames+1):
@@ -619,7 +581,7 @@ def tracking(self) -> str:
619581
last_frame = self.drawFPS(last_frame)
620582
cv2.putText(last_frame, "Press 'q' if the tracking point shifts or a video cut occured",
621583
(self.x_text_start, 75), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,0,0), 2)
622-
cv2.imshow(self.window_name, last_frame)
584+
cv2.imshow(self.window_name, self.preview_scaling(last_frame))
623585

624586
if self.was_key_pressed('q') or cv2.waitKey(1) == ord('q'):
625587
status = 'Tracking stopped by user'
@@ -647,7 +609,7 @@ def tracking(self) -> str:
647609

648610

649611
video.stop()
650-
self.__logger.info(status)
612+
self.logger.info(status)
651613
self.calculate_score()
652614
return status
653615

funscript_editor/data/ffmpegstream.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def get_projection(
153153
'-vf', video_filter,
154154
'-'
155155
]
156-
print(command)
156+
157157
pipe = sp.Popen(
158158
command,
159159
stdin = sp.PIPE,

0 commit comments

Comments
 (0)