Enhance tracking point management in VideoEditor with rotated frame coordinates
This commit updates the VideoEditor class to store and manage tracking points in rotated frame coordinates, improving accuracy in overlay rendering and user interactions. It introduces a new method for mapping rotated coordinates to screen space and modifies existing methods to ensure consistent handling of coordinates throughout the editing process. These changes enhance the overall functionality and user experience when working with motion tracking in video editing.
This commit is contained in:
@@ -1154,9 +1154,7 @@ class VideoEditor:
|
|||||||
return (x, y, w, h)
|
return (x, y, w, h)
|
||||||
|
|
||||||
def _get_interpolated_tracking_position(self, frame_number):
|
def _get_interpolated_tracking_position(self, frame_number):
|
||||||
"""Linear interpolation between keyed tracking points.
|
"""Linear interpolation in ROTATED frame coords. Returns (rx, ry) or None."""
|
||||||
Returns (x, y) in original frame coords or None.
|
|
||||||
"""
|
|
||||||
if not self.tracking_points:
|
if not self.tracking_points:
|
||||||
return None
|
return None
|
||||||
frames = sorted(self.tracking_points.keys())
|
frames = sorted(self.tracking_points.keys())
|
||||||
@@ -1299,6 +1297,38 @@ class VideoEditor:
|
|||||||
oy = max(0, min(int(round(oy)), self.frame_height - 1))
|
oy = max(0, min(int(round(oy)), self.frame_height - 1))
|
||||||
return ox, oy
|
return ox, oy
|
||||||
|
|
||||||
|
def _map_rotated_to_screen(self, rx, ry):
|
||||||
|
"""Map a point in ROTATED frame coords to canvas screen coords (post-crop)."""
|
||||||
|
# Subtract crop offset in rotated space
|
||||||
|
cx, cy, cw, ch = self._get_effective_crop_rect_for_frame(getattr(self, 'current_frame', 0))
|
||||||
|
rx2 = rx - cx
|
||||||
|
ry2 = ry - cy
|
||||||
|
# Zoomed dimensions of cropped-rotated frame
|
||||||
|
new_w = int(cw * self.zoom_factor)
|
||||||
|
new_h = int(ch * self.zoom_factor)
|
||||||
|
cropped_due_to_zoom = (self.zoom_factor != 1.0) and (new_w > self.window_width or new_h > self.window_height)
|
||||||
|
if cropped_due_to_zoom:
|
||||||
|
offx_max = max(0, new_w - self.window_width)
|
||||||
|
offy_max = max(0, new_h - self.window_height)
|
||||||
|
offx = max(0, min(int(self.display_offset[0]), offx_max))
|
||||||
|
offy = max(0, min(int(self.display_offset[1]), offy_max))
|
||||||
|
else:
|
||||||
|
offx = 0
|
||||||
|
offy = 0
|
||||||
|
zx = rx2 * self.zoom_factor - offx
|
||||||
|
zy = ry2 * self.zoom_factor - offy
|
||||||
|
visible_w = new_w if not cropped_due_to_zoom else min(new_w, self.window_width)
|
||||||
|
visible_h = new_h if not cropped_due_to_zoom else min(new_h, self.window_height)
|
||||||
|
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
|
||||||
|
scale_canvas = min(self.window_width / max(1, visible_w), available_height / max(1, visible_h))
|
||||||
|
final_w = int(visible_w * scale_canvas)
|
||||||
|
final_h = int(visible_h * scale_canvas)
|
||||||
|
start_x_canvas = (self.window_width - final_w) // 2
|
||||||
|
start_y_canvas = (available_height - final_h) // 2
|
||||||
|
sx = int(round(start_x_canvas + zx * scale_canvas))
|
||||||
|
sy = int(round(start_y_canvas + zy * scale_canvas))
|
||||||
|
return sx, sy
|
||||||
|
|
||||||
def _map_screen_to_rotated(self, sx, sy):
|
def _map_screen_to_rotated(self, sx, sy):
|
||||||
"""Map a point on canvas screen coords back to ROTATED frame coords (pre-crop)."""
|
"""Map a point on canvas screen coords back to ROTATED frame coords (pre-crop)."""
|
||||||
frame_number = getattr(self, 'current_frame', 0)
|
frame_number = getattr(self, 'current_frame', 0)
|
||||||
@@ -1969,16 +1999,16 @@ class VideoEditor:
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Draw tracking overlays (points and interpolated cross)
|
# Draw tracking overlays (points and interpolated cross), points stored in ROTATED space
|
||||||
pts = self.tracking_points.get(self.current_frame, []) if not self.is_image_mode else []
|
pts = self.tracking_points.get(self.current_frame, []) if not self.is_image_mode else []
|
||||||
for (ox, oy) in pts:
|
for (rx, ry) in pts:
|
||||||
sx, sy = self._map_original_to_screen(ox, oy)
|
sx, sy = self._map_rotated_to_screen(rx, ry)
|
||||||
cv2.circle(canvas, (sx, sy), 6, (0, 255, 0), -1)
|
cv2.circle(canvas, (sx, sy), 6, (0, 255, 0), -1)
|
||||||
cv2.circle(canvas, (sx, sy), 6, (255, 255, 255), 1)
|
cv2.circle(canvas, (sx, sy), 6, (255, 255, 255), 1)
|
||||||
if self.tracking_enabled and not self.is_image_mode:
|
if self.tracking_enabled and not self.is_image_mode:
|
||||||
interp = self._get_interpolated_tracking_position(self.current_frame)
|
interp = self._get_interpolated_tracking_position(self.current_frame)
|
||||||
if interp:
|
if interp:
|
||||||
sx, sy = self._map_original_to_screen(interp[0], interp[1])
|
sx, sy = self._map_rotated_to_screen(interp[0], interp[1])
|
||||||
cv2.line(canvas, (sx - 10, sy), (sx + 10, sy), (255, 0, 0), 2)
|
cv2.line(canvas, (sx - 10, sy), (sx + 10, sy), (255, 0, 0), 2)
|
||||||
cv2.line(canvas, (sx, sy - 10), (sx, sy + 10), (255, 0, 0), 2)
|
cv2.line(canvas, (sx, sy - 10), (sx, sy + 10), (255, 0, 0), 2)
|
||||||
|
|
||||||
@@ -2045,13 +2075,14 @@ class VideoEditor:
|
|||||||
# Handle right-click for tracking points (no modifiers)
|
# Handle right-click for tracking points (no modifiers)
|
||||||
if event == cv2.EVENT_RBUTTONDOWN and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY)):
|
if event == cv2.EVENT_RBUTTONDOWN and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY)):
|
||||||
if not self.is_image_mode:
|
if not self.is_image_mode:
|
||||||
ox, oy = self._map_screen_to_original(x, y)
|
# Store tracking points in ROTATED frame coordinates (pre-crop)
|
||||||
|
rx, ry = self._map_screen_to_rotated(x, y)
|
||||||
threshold = 50
|
threshold = 50
|
||||||
removed = False
|
removed = False
|
||||||
if self.current_frame in self.tracking_points:
|
if self.current_frame in self.tracking_points:
|
||||||
pts_screen = []
|
pts_screen = []
|
||||||
for idx, (px, py) in enumerate(self.tracking_points[self.current_frame]):
|
for idx, (px, py) in enumerate(self.tracking_points[self.current_frame]):
|
||||||
sxp, syp = self._map_original_to_screen(px, py)
|
sxp, syp = self._map_rotated_to_screen(px, py)
|
||||||
pts_screen.append((idx, sxp, syp))
|
pts_screen.append((idx, sxp, syp))
|
||||||
for idx, sxp, syp in pts_screen:
|
for idx, sxp, syp in pts_screen:
|
||||||
if (sxp - x) ** 2 + (syp - y) ** 2 <= threshold ** 2:
|
if (sxp - x) ** 2 + (syp - y) ** 2 <= threshold ** 2:
|
||||||
@@ -2062,7 +2093,7 @@ class VideoEditor:
|
|||||||
removed = True
|
removed = True
|
||||||
break
|
break
|
||||||
if not removed:
|
if not removed:
|
||||||
self.tracking_points.setdefault(self.current_frame, []).append((int(ox), int(oy)))
|
self.tracking_points.setdefault(self.current_frame, []).append((int(rx), int(ry)))
|
||||||
self.show_feedback_message("Tracking point added")
|
self.show_feedback_message("Tracking point added")
|
||||||
self.clear_transformation_cache()
|
self.clear_transformation_cache()
|
||||||
self.save_state()
|
self.save_state()
|
||||||
|
Reference in New Issue
Block a user