Compare commits
6 Commits
e6616ed1b1
...
10284dad81
Author | SHA1 | Date | |
---|---|---|---|
10284dad81 | |||
a2dc4a2186 | |||
5d76681ded | |||
f8acef2da4 | |||
65b80034cb | |||
5400592afd |
187
croppa/main.py
187
croppa/main.py
@@ -1637,13 +1637,17 @@ class VideoEditor:
|
|||||||
|
|
||||||
def _get_interpolated_tracking_position(self, frame_number):
|
def _get_interpolated_tracking_position(self, frame_number):
|
||||||
"""Linear interpolation in ROTATED frame coords. Returns (rx, ry) or None."""
|
"""Linear interpolation in ROTATED frame coords. Returns (rx, ry) or None."""
|
||||||
# First try template matching if enabled (much better than optical flow)
|
# Get base position from manual tracking points
|
||||||
|
base_pos = self._get_manual_tracking_position(frame_number)
|
||||||
|
|
||||||
|
# Calculate offset from template matching if enabled
|
||||||
|
template_offset = None
|
||||||
if self.template_matching_enabled and self.tracking_template is not None:
|
if self.template_matching_enabled and self.tracking_template is not None:
|
||||||
if self.current_display_frame is not None:
|
if self.current_display_frame is not None:
|
||||||
# Use only the cropped region for much faster template matching
|
# Use only the cropped region for much faster template matching
|
||||||
if self.crop_rect:
|
if self.crop_rect:
|
||||||
crop_x, crop_y, crop_w, crop_h = self.crop_rect
|
crop_x, crop_y, crop_w, crop_h = self.crop_rect
|
||||||
# Extract only the cropped region
|
# Extract only the cropped region from raw frame
|
||||||
cropped_frame = self.current_display_frame[crop_y:crop_y+crop_h, crop_x:crop_x+crop_w]
|
cropped_frame = self.current_display_frame[crop_y:crop_y+crop_h, crop_x:crop_x+crop_w]
|
||||||
if cropped_frame is not None and cropped_frame.size > 0:
|
if cropped_frame is not None and cropped_frame.size > 0:
|
||||||
# Track template in cropped frame (much faster!)
|
# Track template in cropped frame (much faster!)
|
||||||
@@ -1652,12 +1656,12 @@ class VideoEditor:
|
|||||||
center_x, center_y, confidence = result
|
center_x, center_y, confidence = result
|
||||||
print(f"DEBUG: Template match found at ({center_x}, {center_y}) with confidence {confidence:.2f}")
|
print(f"DEBUG: Template match found at ({center_x}, {center_y}) with confidence {confidence:.2f}")
|
||||||
|
|
||||||
# Map from cropped frame coordinates to rotated frame coordinates
|
# Map from cropped frame coordinates to raw frame coordinates
|
||||||
# Add crop offset back
|
# Add crop offset back
|
||||||
rot_x = center_x + crop_x
|
raw_x = center_x + crop_x
|
||||||
rot_y = center_y + crop_y
|
raw_y = center_y + crop_y
|
||||||
|
|
||||||
return (rot_x, rot_y)
|
template_offset = (raw_x, raw_y)
|
||||||
else:
|
else:
|
||||||
# No crop - use full frame
|
# No crop - use full frame
|
||||||
raw_frame = self.current_display_frame.copy()
|
raw_frame = self.current_display_frame.copy()
|
||||||
@@ -1667,22 +1671,11 @@ class VideoEditor:
|
|||||||
center_x, center_y, confidence = result
|
center_x, center_y, confidence = result
|
||||||
print(f"DEBUG: Template match found at ({center_x}, {center_y}) with confidence {confidence:.2f}")
|
print(f"DEBUG: Template match found at ({center_x}, {center_y}) with confidence {confidence:.2f}")
|
||||||
|
|
||||||
# Map from raw frame coordinates to rotated frame coordinates
|
# Template matching returns coordinates in raw frame space
|
||||||
if self.rotation_angle == 90:
|
template_offset = (center_x, center_y)
|
||||||
rot_x = self.frame_height - center_y
|
|
||||||
rot_y = center_x
|
|
||||||
elif self.rotation_angle == 180:
|
|
||||||
rot_x = self.frame_width - center_x
|
|
||||||
rot_y = self.frame_height - center_y
|
|
||||||
elif self.rotation_angle == 270:
|
|
||||||
rot_x = center_y
|
|
||||||
rot_y = self.frame_width - center_x
|
|
||||||
else:
|
|
||||||
rot_x, rot_y = center_x, center_y
|
|
||||||
|
|
||||||
return (rot_x, rot_y)
|
|
||||||
|
|
||||||
# Fall back to feature tracking if enabled - but use smooth interpolation instead of averaging
|
# Calculate offset from feature tracking if enabled
|
||||||
|
feature_offset = None
|
||||||
if self.feature_tracker.tracking_enabled:
|
if self.feature_tracker.tracking_enabled:
|
||||||
# Get the nearest frames with features for smooth interpolation
|
# Get the nearest frames with features for smooth interpolation
|
||||||
feature_frames = sorted(self.feature_tracker.features.keys())
|
feature_frames = sorted(self.feature_tracker.features.keys())
|
||||||
@@ -1690,19 +1683,54 @@ class VideoEditor:
|
|||||||
# Find the two nearest frames for interpolation
|
# Find the two nearest frames for interpolation
|
||||||
if frame_number <= feature_frames[0]:
|
if frame_number <= feature_frames[0]:
|
||||||
# Before first feature frame - use first frame
|
# Before first feature frame - use first frame
|
||||||
return self._get_feature_center(feature_frames[0])
|
feature_offset = self._get_feature_center(feature_frames[0])
|
||||||
elif frame_number >= feature_frames[-1]:
|
elif frame_number >= feature_frames[-1]:
|
||||||
# After last feature frame - use last frame
|
# After last feature frame - use last frame
|
||||||
return self._get_feature_center(feature_frames[-1])
|
feature_offset = self._get_feature_center(feature_frames[-1])
|
||||||
else:
|
else:
|
||||||
# Between two feature frames - interpolate smoothly
|
# Between two feature frames - interpolate smoothly
|
||||||
for i in range(len(feature_frames) - 1):
|
for i in range(len(feature_frames) - 1):
|
||||||
if feature_frames[i] <= frame_number <= feature_frames[i + 1]:
|
if feature_frames[i] <= frame_number <= feature_frames[i + 1]:
|
||||||
return self._interpolate_feature_positions(
|
feature_offset = self._interpolate_feature_positions(
|
||||||
feature_frames[i], feature_frames[i + 1], frame_number
|
feature_frames[i], feature_frames[i + 1], frame_number
|
||||||
)
|
)
|
||||||
|
break
|
||||||
|
|
||||||
# Fall back to manual tracking points
|
# Combine tracking methods: average of all available positions
|
||||||
|
positions = []
|
||||||
|
|
||||||
|
# Add manual tracking position
|
||||||
|
if base_pos:
|
||||||
|
positions.append(base_pos)
|
||||||
|
print(f"DEBUG: Manual tracking: ({base_pos[0]:.1f}, {base_pos[1]:.1f})")
|
||||||
|
|
||||||
|
# Add template matching position
|
||||||
|
if template_offset:
|
||||||
|
positions.append(template_offset)
|
||||||
|
print(f"DEBUG: Template matching: ({template_offset[0]:.1f}, {template_offset[1]:.1f})")
|
||||||
|
|
||||||
|
# Add feature tracking position
|
||||||
|
if feature_offset:
|
||||||
|
positions.append(feature_offset)
|
||||||
|
print(f"DEBUG: Feature tracking: ({feature_offset[0]:.1f}, {feature_offset[1]:.1f})")
|
||||||
|
|
||||||
|
# Calculate average of all available positions
|
||||||
|
if positions:
|
||||||
|
avg_x = sum(pos[0] for pos in positions) / len(positions)
|
||||||
|
avg_y = sum(pos[1] for pos in positions) / len(positions)
|
||||||
|
print(f"DEBUG: Average of {len(positions)} positions: ({avg_x:.1f}, {avg_y:.1f})")
|
||||||
|
return (avg_x, avg_y)
|
||||||
|
|
||||||
|
# Fall back to individual tracking methods if no base position
|
||||||
|
if template_offset:
|
||||||
|
return template_offset
|
||||||
|
elif feature_offset:
|
||||||
|
return feature_offset
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_manual_tracking_position(self, frame_number):
|
||||||
|
"""Get manual tracking position for a frame"""
|
||||||
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())
|
||||||
@@ -1732,6 +1760,38 @@ class VideoEditor:
|
|||||||
return (x1 + t * (x2 - x1), y1 + t * (y2 - y1))
|
return (x1 + t * (x2 - x1), y1 + t * (y2 - y1))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_template_matching_position(self, frame_number):
|
||||||
|
"""Get template matching position and confidence for a frame"""
|
||||||
|
if not self.template_matching_enabled or self.tracking_template is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.current_display_frame is not None:
|
||||||
|
# Use only the cropped region for much faster template matching
|
||||||
|
if self.crop_rect:
|
||||||
|
crop_x, crop_y, crop_w, crop_h = self.crop_rect
|
||||||
|
# Extract only the cropped region from raw frame
|
||||||
|
cropped_frame = self.current_display_frame[crop_y:crop_y+crop_h, crop_x:crop_x+crop_w]
|
||||||
|
if cropped_frame is not None and cropped_frame.size > 0:
|
||||||
|
# Track template in cropped frame (much faster!)
|
||||||
|
result = self.track_template(cropped_frame)
|
||||||
|
if result:
|
||||||
|
center_x, center_y, confidence = result
|
||||||
|
# Map from cropped frame coordinates to raw frame coordinates
|
||||||
|
# Add crop offset back
|
||||||
|
raw_x = center_x + crop_x
|
||||||
|
raw_y = center_y + crop_y
|
||||||
|
return (raw_x, raw_y, confidence)
|
||||||
|
else:
|
||||||
|
# No crop - use full frame
|
||||||
|
raw_frame = self.current_display_frame.copy()
|
||||||
|
if raw_frame is not None:
|
||||||
|
result = self.track_template(raw_frame)
|
||||||
|
if result:
|
||||||
|
center_x, center_y, confidence = result
|
||||||
|
return (center_x, center_y, confidence)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def _get_display_params(self):
|
def _get_display_params(self):
|
||||||
"""Unified display transform parameters for current frame in rotated space."""
|
"""Unified display transform parameters for current frame in rotated space."""
|
||||||
eff_x, eff_y, eff_w, eff_h = self._get_effective_crop_rect_for_frame(getattr(self, 'current_frame', 0))
|
eff_x, eff_y, eff_w, eff_h = self._get_effective_crop_rect_for_frame(getattr(self, 'current_frame', 0))
|
||||||
@@ -2231,37 +2291,35 @@ class VideoEditor:
|
|||||||
print(f"DEBUG: Setting template from region ({x}, {y}, {w}, {h})")
|
print(f"DEBUG: Setting template from region ({x}, {y}, {w}, {h})")
|
||||||
|
|
||||||
if self.current_display_frame is not None:
|
if self.current_display_frame is not None:
|
||||||
# Apply transformations to get the display frame
|
# Map screen coordinates to rotated frame coordinates (raw frame)
|
||||||
display_frame = self.apply_crop_zoom_and_rotation(self.current_display_frame)
|
# This is what we need for template matching during rendering
|
||||||
if display_frame is not None:
|
rot_x, rot_y = self._map_screen_to_rotated(x, y)
|
||||||
# Map screen coordinates to display frame coordinates
|
rot_x2, rot_y2 = self._map_screen_to_rotated(x + w, y + h)
|
||||||
frame_height, frame_width = display_frame.shape[:2]
|
|
||||||
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
|
# Calculate region in rotated frame coordinates
|
||||||
start_y = (available_height - frame_height) // 2
|
raw_x = min(rot_x, rot_x2)
|
||||||
start_x = (self.window_width - frame_width) // 2
|
raw_y = min(rot_y, rot_y2)
|
||||||
|
raw_w = abs(rot_x2 - rot_x)
|
||||||
|
raw_h = abs(rot_y2 - rot_y)
|
||||||
|
|
||||||
|
print(f"DEBUG: Mapped to raw frame coordinates ({raw_x}, {raw_y}, {raw_w}, {raw_h})")
|
||||||
|
|
||||||
|
# Ensure region is within raw frame bounds
|
||||||
|
if (raw_x >= 0 and raw_y >= 0 and
|
||||||
|
raw_x + raw_w <= self.frame_width and
|
||||||
|
raw_y + raw_h <= self.frame_height):
|
||||||
|
|
||||||
# Convert screen coordinates to display frame coordinates
|
# Extract template from raw frame
|
||||||
display_x = x - start_x
|
template = self.current_display_frame[raw_y:raw_y+raw_h, raw_x:raw_x+raw_w]
|
||||||
display_y = y - start_y
|
if template.size > 0:
|
||||||
display_w = w
|
self.tracking_template = template.copy()
|
||||||
display_h = h
|
self.template_region = (raw_x, raw_y, raw_w, raw_h)
|
||||||
|
self.show_feedback_message(f"Template set from region ({raw_w}x{raw_h})")
|
||||||
# Ensure region is within frame bounds
|
print(f"DEBUG: Template set with size {template.shape}")
|
||||||
if (display_x >= 0 and display_y >= 0 and
|
|
||||||
display_x + display_w <= frame_width and
|
|
||||||
display_y + display_h <= frame_height):
|
|
||||||
|
|
||||||
# Extract template from display frame
|
|
||||||
template = display_frame[display_y:display_y+display_h, display_x:display_x+display_w]
|
|
||||||
if template.size > 0:
|
|
||||||
self.tracking_template = template.copy()
|
|
||||||
self.template_region = (display_x, display_y, display_w, display_h)
|
|
||||||
self.show_feedback_message(f"Template set from region ({display_w}x{display_h})")
|
|
||||||
print(f"DEBUG: Template set with size {template.shape}")
|
|
||||||
else:
|
|
||||||
self.show_feedback_message("Template region too small")
|
|
||||||
else:
|
else:
|
||||||
self.show_feedback_message("Template region outside frame bounds")
|
self.show_feedback_message("Template region too small")
|
||||||
|
else:
|
||||||
|
self.show_feedback_message("Template region outside frame bounds")
|
||||||
|
|
||||||
|
|
||||||
def apply_rotation(self, frame):
|
def apply_rotation(self, frame):
|
||||||
@@ -2931,6 +2989,23 @@ class VideoEditor:
|
|||||||
cv2.circle(canvas, (sx, sy), 4, (0, 255, 0), -1) # Green circles for features
|
cv2.circle(canvas, (sx, sy), 4, (0, 255, 0), -1) # Green circles for features
|
||||||
cv2.circle(canvas, (sx, sy), 4, (255, 255, 255), 1)
|
cv2.circle(canvas, (sx, sy), 4, (255, 255, 255), 1)
|
||||||
|
|
||||||
|
# Draw template matching point (blue circle with confidence)
|
||||||
|
if (not self.is_image_mode and
|
||||||
|
self.template_matching_enabled and
|
||||||
|
self.tracking_template is not None):
|
||||||
|
# Get template matching position for current frame
|
||||||
|
template_pos = self._get_template_matching_position(self.current_frame)
|
||||||
|
if template_pos:
|
||||||
|
tx, ty, confidence = template_pos
|
||||||
|
# Map to screen coordinates
|
||||||
|
sx, sy = self._map_rotated_to_screen(tx, ty)
|
||||||
|
# Draw blue circle for template matching
|
||||||
|
cv2.circle(canvas, (sx, sy), 8, (255, 0, 255), -1) # Magenta circle for template
|
||||||
|
cv2.circle(canvas, (sx, sy), 8, (255, 255, 255), 2)
|
||||||
|
# Draw confidence text
|
||||||
|
conf_text = f"{confidence:.2f}"
|
||||||
|
cv2.putText(canvas, conf_text, (sx + 10, sy - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
||||||
|
|
||||||
# Draw selection rectangles for feature extraction/deletion
|
# Draw selection rectangles for feature extraction/deletion
|
||||||
if self.selective_feature_extraction_rect:
|
if self.selective_feature_extraction_rect:
|
||||||
x, y, w, h = self.selective_feature_extraction_rect
|
x, y, w, h = self.selective_feature_extraction_rect
|
||||||
@@ -3850,6 +3925,10 @@ class VideoEditor:
|
|||||||
if not ret:
|
if not ret:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Set current display frame for motion tracking during rendering
|
||||||
|
self.current_display_frame = frame.copy()
|
||||||
|
self.current_frame = start_frame + i
|
||||||
|
|
||||||
processed_frame = self._process_frame_for_render(frame, output_width, output_height, start_frame + i)
|
processed_frame = self._process_frame_for_render(frame, output_width, output_height, start_frame + i)
|
||||||
if processed_frame is not None:
|
if processed_frame is not None:
|
||||||
if i == 0:
|
if i == 0:
|
||||||
|
Reference in New Issue
Block a user