Enhance tracking point management in VideoEditor and MotionTracker with dual coordinate storage
This commit introduces a new TrackingPoint class to encapsulate both original and display coordinates for tracking points, improving the accuracy and consistency of point transformations. The VideoEditor class has been updated to utilize this new structure, allowing for better handling of tracking points during video editing. Additionally, logging has been enhanced to provide clearer insights into the addition and processing of tracking points, while redundant verification steps have been removed for efficiency. This change streamlines the tracking process and improves the overall user experience.
This commit is contained in:
@@ -1545,27 +1545,34 @@ class VideoEditor:
|
||||
cv2.line(canvas, (bl_x, bl_y), (tl_x, tl_y), (255, 0, 255), 1)
|
||||
|
||||
# Process each tracking point
|
||||
for i, point in enumerate(tracking_points):
|
||||
print(f"draw_tracking_points: processing point {i}: {point}")
|
||||
for i, tracking_point in enumerate(tracking_points):
|
||||
# Get the original coordinates
|
||||
orig_x, orig_y = tracking_point.original
|
||||
print(f"draw_tracking_points: processing point {i}: original={tracking_point.original}")
|
||||
|
||||
# Check if the point is within the frame bounds
|
||||
is_in_frame = (0 <= point[0] < frame_width and 0 <= point[1] < frame_height)
|
||||
is_in_frame = (0 <= orig_x < frame_width and 0 <= orig_y < frame_height)
|
||||
print(f"draw_tracking_points: point {i} is {'inside' if is_in_frame else 'outside'} frame bounds")
|
||||
|
||||
# Check if the point is within the crop area (if cropping is active)
|
||||
is_in_crop = True
|
||||
if self.crop_rect:
|
||||
crop_x, crop_y, crop_w, crop_h = self.crop_rect
|
||||
is_in_crop = (crop_x <= point[0] < crop_x + crop_w and
|
||||
crop_y <= point[1] < crop_y + crop_h)
|
||||
is_in_crop = (crop_x <= orig_x < crop_x + crop_w and
|
||||
crop_y <= orig_y < crop_y + crop_h)
|
||||
print(f"draw_tracking_points: point {i} is {'inside' if is_in_crop else 'outside'} crop area")
|
||||
|
||||
# Get the display coordinates - either from stored value or transform now
|
||||
if tracking_point.display:
|
||||
# Use stored display coordinates
|
||||
display_point = tracking_point.display
|
||||
print(f"draw_tracking_points: using stored display coordinates {display_point}")
|
||||
else:
|
||||
# Transform point from original frame coordinates to display coordinates
|
||||
display_point = self.transform_point(point)
|
||||
display_point = self.transform_point(tracking_point.original)
|
||||
print(f"draw_tracking_points: transformed to display coordinates {display_point}")
|
||||
|
||||
if display_point is not None:
|
||||
print(f"draw_tracking_points: point {i} transformed to {display_point}")
|
||||
|
||||
# Scale and offset the point to match the canvas
|
||||
x = int(offset_x + display_point[0] * scale)
|
||||
y = int(offset_y + display_point[1] * scale)
|
||||
@@ -2213,33 +2220,17 @@ class VideoEditor:
|
||||
if removed:
|
||||
print(f"mouse_callback: removed tracking point at {original_point}")
|
||||
else:
|
||||
# If no point was removed, add a new tracking point
|
||||
# Add a new tracking point with both original and display coordinates
|
||||
# This is the key change - we store both coordinate systems
|
||||
self.motion_tracker.add_tracking_point(
|
||||
self.current_frame,
|
||||
original_point[0],
|
||||
original_point[1]
|
||||
original_point[1],
|
||||
display_coords=(display_x, display_y) # Store the display coordinates directly
|
||||
)
|
||||
print(f"mouse_callback: added tracking point at {original_point}")
|
||||
print(f"mouse_callback: added tracking point at {original_point} (display: {display_x}, {display_y})")
|
||||
|
||||
# Perform a round-trip verification to ensure our coordinate system is consistent
|
||||
verification_point = self.transform_point(original_point)
|
||||
if verification_point:
|
||||
print(f"mouse_callback: verification - point transforms back to {verification_point}")
|
||||
|
||||
# Calculate expected canvas position for verification
|
||||
expected_x = int(start_x + verification_point[0] * scale)
|
||||
expected_y = int(start_y + verification_point[1] * scale)
|
||||
print(f"mouse_callback: verification - expected canvas position: ({expected_x}, {expected_y}), actual: ({x}, {y})")
|
||||
|
||||
# Calculate the error between click and expected position
|
||||
error_x = abs(expected_x - x)
|
||||
error_y = abs(expected_y - y)
|
||||
print(f"mouse_callback: verification - position error: ({error_x}, {error_y}) pixels")
|
||||
|
||||
# If error is significant, print a warning
|
||||
if error_x > 2 or error_y > 2:
|
||||
print(f"WARNING: Significant coordinate transformation error detected!")
|
||||
print(f"This may indicate a problem with the transform/untransform functions.")
|
||||
# No need for verification - we're storing both coordinate systems directly
|
||||
|
||||
# Save state when tracking points change
|
||||
self.save_state()
|
||||
|
@@ -1,31 +1,55 @@
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
from typing import List, Dict, Tuple, Optional, NamedTuple
|
||||
|
||||
|
||||
class TrackingPoint(NamedTuple):
|
||||
"""Represents a tracking point with both original and display coordinates"""
|
||||
original: Tuple[float, float] # Original frame coordinates (x, y)
|
||||
display: Optional[Tuple[float, float]] = None # Display coordinates after transformation (x, y)
|
||||
|
||||
def __str__(self):
|
||||
if self.display:
|
||||
return f"TrackingPoint(orig={self.original}, display={self.display})"
|
||||
return f"TrackingPoint(orig={self.original})"
|
||||
|
||||
|
||||
class MotionTracker:
|
||||
"""Handles motion tracking for crop and pan operations"""
|
||||
|
||||
def __init__(self):
|
||||
self.tracking_points = {} # {frame_number: [(x, y), ...]}
|
||||
self.tracking_points = {} # {frame_number: [TrackingPoint, ...]}
|
||||
self.tracking_enabled = False
|
||||
self.base_crop_rect = None # Original crop rect when tracking started
|
||||
self.base_zoom_center = None # Original zoom center when tracking started
|
||||
|
||||
def add_tracking_point(self, frame_number: int, x: int, y: int):
|
||||
"""Add a tracking point at the specified frame and coordinates"""
|
||||
def add_tracking_point(self, frame_number: int, x: float, y: float, display_coords: Optional[Tuple[float, float]] = None):
|
||||
"""Add a tracking point at the specified frame and coordinates
|
||||
|
||||
Args:
|
||||
frame_number: The frame number to add the point to
|
||||
x: Original x coordinate
|
||||
y: Original y coordinate
|
||||
display_coords: Optional display coordinates after transformation
|
||||
"""
|
||||
if frame_number not in self.tracking_points:
|
||||
self.tracking_points[frame_number] = []
|
||||
self.tracking_points[frame_number].append((x, y))
|
||||
|
||||
def remove_tracking_point(self, frame_number: int, x: int, y: int, radius: int = 50):
|
||||
# Store both original and display coordinates
|
||||
point = TrackingPoint(original=(float(x), float(y)), display=display_coords)
|
||||
print(f"Adding tracking point: {point}")
|
||||
self.tracking_points[frame_number].append(point)
|
||||
|
||||
def remove_tracking_point(self, frame_number: int, x: float, y: float, radius: int = 50):
|
||||
"""Remove a tracking point by frame and proximity to x,y"""
|
||||
if frame_number not in self.tracking_points:
|
||||
return False
|
||||
|
||||
points = self.tracking_points[frame_number]
|
||||
for i, (px, py) in enumerate(points):
|
||||
for i, point in enumerate(points):
|
||||
px, py = point.original
|
||||
# Calculate distance between points
|
||||
distance = ((px - x) ** 2 + (py - y) ** 2) ** 0.5
|
||||
if distance <= radius:
|
||||
print(f"Removing tracking point: {point}")
|
||||
del points[i]
|
||||
if not points:
|
||||
del self.tracking_points[frame_number]
|
||||
@@ -37,7 +61,7 @@ class MotionTracker:
|
||||
"""Clear all tracking points"""
|
||||
self.tracking_points.clear()
|
||||
|
||||
def get_tracking_points_for_frame(self, frame_number: int) -> List[Tuple[int, int]]:
|
||||
def get_tracking_points_for_frame(self, frame_number: int) -> List[TrackingPoint]:
|
||||
"""Get all tracking points for a specific frame"""
|
||||
return self.tracking_points.get(frame_number, [])
|
||||
|
||||
@@ -61,24 +85,24 @@ class MotionTracker:
|
||||
points = self.tracking_points[frame_number]
|
||||
if points:
|
||||
# Return average of all points at this frame
|
||||
avg_x = sum(p[0] for p in points) / len(points)
|
||||
avg_y = sum(p[1] for p in points) / len(points)
|
||||
avg_x = sum(p.original[0] for p in points) / len(points)
|
||||
avg_y = sum(p.original[1] for p in points) / len(points)
|
||||
return (avg_x, avg_y)
|
||||
|
||||
# If frame is before first tracking point
|
||||
if frame_number < frames[0]:
|
||||
points = self.tracking_points[frames[0]]
|
||||
if points:
|
||||
avg_x = sum(p[0] for p in points) / len(points)
|
||||
avg_y = sum(p[1] for p in points) / len(points)
|
||||
avg_x = sum(p.original[0] for p in points) / len(points)
|
||||
avg_y = sum(p.original[1] for p in points) / len(points)
|
||||
return (avg_x, avg_y)
|
||||
|
||||
# If frame is after last tracking point
|
||||
if frame_number > frames[-1]:
|
||||
points = self.tracking_points[frames[-1]]
|
||||
if points:
|
||||
avg_x = sum(p[0] for p in points) / len(points)
|
||||
avg_y = sum(p[1] for p in points) / len(points)
|
||||
avg_x = sum(p.original[0] for p in points) / len(points)
|
||||
avg_y = sum(p.original[1] for p in points) / len(points)
|
||||
return (avg_x, avg_y)
|
||||
|
||||
# Find the two frames to interpolate between
|
||||
@@ -92,10 +116,10 @@ class MotionTracker:
|
||||
continue
|
||||
|
||||
# Get average positions for each frame
|
||||
avg_x1 = sum(p[0] for p in points1) / len(points1)
|
||||
avg_y1 = sum(p[1] for p in points1) / len(points1)
|
||||
avg_x2 = sum(p[0] for p in points2) / len(points2)
|
||||
avg_y2 = sum(p[1] for p in points2) / len(points2)
|
||||
avg_x1 = sum(p.original[0] for p in points1) / len(points1)
|
||||
avg_y1 = sum(p.original[1] for p in points1) / len(points1)
|
||||
avg_x2 = sum(p.original[0] for p in points2) / len(points2)
|
||||
avg_y2 = sum(p.original[1] for p in points2) / len(points2)
|
||||
|
||||
# Linear interpolation
|
||||
t = (frame_number - frame1) / (frame2 - frame1)
|
||||
@@ -154,8 +178,14 @@ class MotionTracker:
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""Convert to dictionary for serialization"""
|
||||
# Convert TrackingPoint objects to tuples for serialization
|
||||
serialized_points = {}
|
||||
for frame_num, points in self.tracking_points.items():
|
||||
# Store only the original coordinates for serialization
|
||||
serialized_points[frame_num] = [p.original for p in points]
|
||||
|
||||
return {
|
||||
'tracking_points': self.tracking_points,
|
||||
'tracking_points': serialized_points,
|
||||
'tracking_enabled': self.tracking_enabled,
|
||||
'base_crop_rect': self.base_crop_rect,
|
||||
'base_zoom_center': self.base_zoom_center
|
||||
@@ -168,7 +198,8 @@ class MotionTracker:
|
||||
self.tracking_points = {}
|
||||
for frame_str, points in tracking_points_data.items():
|
||||
frame_num = int(frame_str) # Convert string key to integer
|
||||
self.tracking_points[frame_num] = points
|
||||
# Convert tuples to TrackingPoint objects
|
||||
self.tracking_points[frame_num] = [TrackingPoint(original=p) for p in points]
|
||||
|
||||
self.tracking_enabled = data.get('tracking_enabled', False)
|
||||
self.base_crop_rect = data.get('base_crop_rect', None)
|
||||
|
Reference in New Issue
Block a user