| | |
| | """ |
| | Debug Enhanced Positioning System for Video Background Replacement |
| | |
| | Key debugging areas: |
| | 1. UI parameter flow validation |
| | 2. Coordinate system debugging |
| | 3. Alpha/video alignment verification |
| | 4. Canvas placement mathematics |
| | 5. Temporal synchronization checks |
| | """ |
| |
|
| | import cv2 |
| | import numpy as np |
| | import os |
| | import time |
| | import random |
| | from pathlib import Path |
| | from moviepy.editor import VideoFileClip |
| | import logging |
| |
|
| | |
| | logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s") |
| | debug_logger = logging.getLogger("positioning_debug") |
| |
|
| | class PositioningDebugger: |
| | """Comprehensive debugging for positioning system""" |
| | |
| | def __init__(self, debug_dir="debug_positioning"): |
| | self.debug_dir = Path(debug_dir) |
| | self.debug_dir.mkdir(exist_ok=True, parents=True) |
| | self.frame_count = 0 |
| | |
| | def log_parameters(self, placement_dict): |
| | """Log all positioning parameters""" |
| | debug_logger.info("=== POSITIONING PARAMETERS ===") |
| | debug_logger.info(f"Raw placement dict: {placement_dict}") |
| | |
| | px = float(placement_dict.get("x", 0.5)) |
| | py = float(placement_dict.get("y", 0.75)) |
| | ps = float(placement_dict.get("scale", 1.0)) |
| | feather_px = int(placement_dict.get("feather", 3)) |
| | |
| | debug_logger.info(f"Parsed - px: {px}, py: {py}, ps: {ps}, feather: {feather_px}") |
| | debug_logger.info(f"Clamped - px: {max(0.0, min(1.0, px))}, py: {max(0.0, min(1.0, py))}") |
| | debug_logger.info(f"Scale range - ps: {max(0.3, min(2.0, ps))}") |
| | |
| | return px, py, ps, feather_px |
| | |
| | def debug_coordinate_calculation(self, frame_dims, alpha_dims, placement): |
| | """Debug coordinate calculations step by step""" |
| | hh, ww = frame_dims |
| | alpha_h, alpha_w = alpha_dims |
| | |
| | px, py, ps, feather_px = self.log_parameters(placement) |
| | |
| | debug_logger.info("=== COORDINATE CALCULATIONS ===") |
| | debug_logger.info(f"Original video dims: {ww}x{hh}") |
| | debug_logger.info(f"Alpha video dims: {alpha_w}x{alpha_h}") |
| | |
| | |
| | sw = max(1, int(ww * ps)) |
| | sh = max(1, int(hh * ps)) |
| | debug_logger.info(f"Scaled subject size: {sw}x{sh} (scale factor: {ps})") |
| | |
| | |
| | cx = int(px * ww) |
| | cy = int(py * hh) |
| | debug_logger.info(f"Target center: ({cx}, {cy})") |
| | |
| | |
| | x0 = int(cx - sw // 2) |
| | y0 = int(cy - sh // 2) |
| | debug_logger.info(f"Top-left corner: ({x0}, {y0})") |
| | |
| | |
| | xs0, ys0 = max(0, x0), max(0, y0) |
| | xs1, ys1 = min(ww, x0 + sw), min(hh, y0 + sh) |
| | debug_logger.info(f"Clipped bounds: ({xs0}, {ys0}) to ({xs1}, {ys1})") |
| | |
| | if xs1 <= xs0 or ys1 <= ys0: |
| | debug_logger.warning("INVALID BOUNDS - Subject will be clipped completely!") |
| | |
| | |
| | src_x0 = xs0 - x0 |
| | src_y0 = ys0 - y0 |
| | src_x1 = src_x0 + (xs1 - xs0) |
| | src_y1 = src_y0 + (ys1 - ys0) |
| | debug_logger.info(f"Source region: ({src_x0}, {src_y0}) to ({src_x1}, {src_y1})") |
| | |
| | return { |
| | 'scaled_size': (sw, sh), |
| | 'center': (cx, cy), |
| | 'top_left': (x0, y0), |
| | 'clipped_dest': (xs0, ys0, xs1, ys1), |
| | 'source_region': (src_x0, src_y0, src_x1, src_y1) |
| | } |
| | |
| | def save_debug_frame(self, frame, alpha, composite, frame_idx, coords): |
| | """Save debug visualization of frame processing""" |
| | debug_path = self.debug_dir / f"frame_{frame_idx:04d}_debug.png" |
| | |
| | |
| | h, w = frame.shape[:2] |
| | debug_viz = np.zeros((h * 2, w * 2, 3), dtype=np.uint8) |
| | |
| | |
| | debug_viz[0:h, 0:w] = cv2.cvtColor((frame * 255).astype(np.uint8), cv2.COLOR_RGB2BGR) |
| | |
| | |
| | alpha_viz = cv2.cvtColor((alpha * 255).astype(np.uint8), cv2.COLOR_GRAY2BGR) |
| | debug_viz[0:h, w:w*2] = alpha_viz |
| | |
| | |
| | debug_viz[h:h*2, 0:w] = cv2.cvtColor((composite * 255).astype(np.uint8), cv2.COLOR_RGB2BGR) |
| | |
| | |
| | coord_viz = np.zeros((h, w, 3), dtype=np.uint8) |
| | |
| | |
| | cx, cy = coords['center'] |
| | sw, sh = coords['scaled_size'] |
| | x0, y0 = coords['top_left'] |
| | |
| | |
| | cv2.line(coord_viz, (cx-10, cy), (cx+10, cy), (0, 255, 0), 2) |
| | cv2.line(coord_viz, (cx, cy-10), (cx, cy+10), (0, 255, 0), 2) |
| | |
| | |
| | cv2.rectangle(coord_viz, (x0, y0), (x0 + sw, y0 + sh), (255, 0, 0), 2) |
| | |
| | |
| | cv2.rectangle(coord_viz, (0, 0), (w-1, h-1), (255, 255, 255), 1) |
| | |
| | debug_viz[h:h*2, w:w*2] = coord_viz |
| | |
| | |
| | font = cv2.FONT_HERSHEY_SIMPLEX |
| | cv2.putText(debug_viz, "Original", (10, 30), font, 0.7, (255, 255, 255), 2) |
| | cv2.putText(debug_viz, "Alpha", (w + 10, 30), font, 0.7, (255, 255, 255), 2) |
| | cv2.putText(debug_viz, "Composite", (10, h + 30), font, 0.7, (255, 255, 255), 2) |
| | cv2.putText(debug_viz, "Coordinates", (w + 10, h + 30), font, 0.7, (255, 255, 255), 2) |
| | |
| | cv2.imwrite(str(debug_path), debug_viz) |
| | debug_logger.info(f"Debug frame saved: {debug_path}") |
| |
|
| | def create_enhanced_composite_function(debugger=None): |
| | """Create composite function with enhanced debugging""" |
| | |
| | def composite_frame_debug(get_frame, t, original_clip, alpha_clip, bg_rgb, placement, feather_px): |
| | """Enhanced composite function with comprehensive debugging""" |
| | |
| | if debugger: |
| | debugger.frame_count += 1 |
| | debug_logger.info(f"=== PROCESSING FRAME {debugger.frame_count} at t={t:.3f}s ===") |
| | |
| | |
| | frame = get_frame(t).astype(np.float32) / 255.0 |
| | hh, ww = frame.shape[:2] |
| | |
| | |
| | alpha_duration = alpha_clip.duration or 0 |
| | if alpha_duration > 0: |
| | alpha_t = min(t, max(0.0, alpha_duration - 0.01)) |
| | else: |
| | alpha_t = 0.0 |
| | |
| | debug_logger.info(f"Alpha lookup: t={t:.3f}, alpha_t={alpha_t:.3f}, alpha_duration={alpha_duration:.3f}") |
| | |
| | try: |
| | a = alpha_clip.get_frame(alpha_t) |
| | if a.ndim == 3: |
| | a = a[:, :, 0] |
| | a = a.astype(np.float32) / 255.0 |
| | debug_logger.info(f"Alpha frame shape: {a.shape}, range: [{a.min():.3f}, {a.max():.3f}]") |
| | except Exception as e: |
| | debug_logger.error(f"Alpha frame error: {e}") |
| | return (bg_rgb * 255).astype(np.uint8) |
| | |
| | |
| | px = max(0.0, min(1.0, float(placement.get("x", 0.5)))) |
| | py = max(0.0, min(1.0, float(placement.get("y", 0.75)))) |
| | ps = max(0.3, min(2.0, float(placement.get("scale", 1.0)))) |
| | |
| | |
| | coords = None |
| | if debugger: |
| | coords = debugger.debug_coordinate_calculation((hh, ww), a.shape, placement) |
| | |
| | |
| | sw = max(1, int(ww * ps)) |
| | sh = max(1, int(hh * ps)) |
| | |
| | debug_logger.info(f"Scaling: {ww}x{hh} -> {sw}x{sh} (factor: {ps})") |
| | |
| | fg_scaled = cv2.resize(frame, (sw, sh), interpolation=cv2.INTER_LINEAR) |
| | a_scaled = cv2.resize(a, (sw, sh), interpolation=cv2.INTER_LINEAR) |
| | |
| | |
| | fg_canvas = np.zeros_like(frame, dtype=np.float32) |
| | a_canvas = np.zeros((hh, ww), dtype=np.float32) |
| | |
| | |
| | cx = int(px * ww) |
| | cy = int(py * hh) |
| | x0 = int(cx - sw // 2) |
| | y0 = int(cy - sh // 2) |
| | |
| | |
| | xs0, ys0 = max(0, x0), max(0, y0) |
| | xs1, ys1 = min(ww, x0 + sw), min(hh, y0 + sh) |
| | |
| | if xs1 <= xs0 or ys1 <= ys0: |
| | debug_logger.warning("Subject completely outside frame bounds!") |
| | if debugger: |
| | debugger.save_debug_frame(frame, a, bg_rgb, debugger.frame_count, coords or {}) |
| | return (bg_rgb * 255).astype(np.uint8) |
| | |
| | |
| | src_x0 = xs0 - x0 |
| | src_y0 = ys0 - y0 |
| | src_x1 = src_x0 + (xs1 - xs0) |
| | src_y1 = src_y0 + (ys1 - ys0) |
| | |
| | try: |
| | fg_canvas[ys0:ys1, xs0:xs1, :] = fg_scaled[src_y0:src_y1, src_x0:src_x1, :] |
| | a_canvas[ys0:ys1, xs0:xs1] = a_scaled[src_y0:src_y1, src_x0:src_x1] |
| | except Exception as e: |
| | debug_logger.error(f"Canvas placement error: {e}") |
| | debug_logger.error(f"Canvas region: [{ys0}:{ys1}, {xs0}:{xs1}]") |
| | debug_logger.error(f"Source region: [{src_y0}:{src_y1}, {src_x0}:{src_x1}]") |
| | if debugger: |
| | debugger.save_debug_frame(frame, a, bg_rgb, debugger.frame_count, coords or {}) |
| | return (bg_rgb * 255).astype(np.uint8) |
| | |
| | |
| | if feather_px > 0: |
| | k = (feather_px * 2 + 1) |
| | a_canvas = cv2.GaussianBlur(a_canvas, (k, k), feather_px) |
| | |
| | |
| | a3 = a_canvas[:, :, None] |
| | comp = a3 * fg_canvas + (1.0 - a3) * bg_rgb |
| | result = np.clip(comp * 255, 0, 255).astype(np.uint8) |
| | |
| | |
| | if debugger and debugger.frame_count % 30 == 1: |
| | debugger.save_debug_frame(frame, a_canvas, comp, debugger.frame_count, coords or {}) |
| | |
| | return result |
| | |
| | return composite_frame_debug |
| |
|
| | def validate_ui_parameter_flow(): |
| | """Test function to validate UI parameters reach processing correctly""" |
| | |
| | |
| | test_placements = [ |
| | {"x": 0.5, "y": 0.5, "scale": 1.0, "feather": 3}, |
| | {"x": 0.2, "y": 0.8, "scale": 0.7, "feather": 5}, |
| | {"x": 0.8, "y": 0.3, "scale": 1.5, "feather": 1}, |
| | ] |
| | |
| | debugger = PositioningDebugger() |
| | |
| | for i, placement in enumerate(test_placements): |
| | print(f"\n=== TEST CASE {i+1} ===") |
| | |
| | |
| | frame_dims = (720, 1280) |
| | alpha_dims = (720, 1280) |
| | |
| | coords = debugger.debug_coordinate_calculation(frame_dims, alpha_dims, placement) |
| | |
| | print(f"Expected center: {coords['center']}") |
| | print(f"Scaled size: {coords['scaled_size']}") |
| | print(f"Placement bounds: {coords['clipped_dest']}") |
| |
|
| | def create_debug_enhanced_process_video_main(): |
| | """Enhanced version of process_video_main with debugging""" |
| | |
| | def process_video_main_debug( |
| | video_path: str, |
| | background_path: str = None, |
| | trim_duration: float = None, |
| | crf: int = 18, |
| | preserve_audio_flag: bool = True, |
| | placement: dict = None, |
| | use_chunked_processing: bool = False, |
| | enable_debug: bool = True, |
| | progress=None, |
| | ): |
| | """ |
| | Enhanced process_video_main with comprehensive positioning debugging |
| | """ |
| | |
| | debugger = PositioningDebugger() if enable_debug else None |
| | messages = [] |
| | |
| | try: |
| | if debugger: |
| | debug_logger.info("=== STARTING DEBUG SESSION ===") |
| | debug_logger.info(f"Video: {video_path}") |
| | debug_logger.info(f"Background: {background_path}") |
| | debug_logger.info(f"Placement: {placement}") |
| | |
| | |
| | placement = placement or {} |
| | if debugger: |
| | debugger.log_parameters(placement) |
| | |
| | |
| | |
| | |
| | def composite_frame(get_frame, t): |
| | return create_enhanced_composite_function(debugger)( |
| | get_frame, t, original_clip, alpha_clip, bg_rgb, placement, |
| | int(placement.get("feather", 3)) |
| | ) |
| | |
| | |
| | |
| | if debugger: |
| | debug_logger.info("=== DEBUG SESSION COMPLETE ===") |
| | debug_logger.info(f"Debug files saved to: {debugger.debug_dir}") |
| | |
| | return result_path, "\n".join(messages) |
| | |
| | except Exception as e: |
| | if debugger: |
| | debug_logger.error(f"Processing failed: {e}") |
| | raise |
| | |
| | return process_video_main_debug |
| |
|
| | |
| | def quick_positioning_fixes(): |
| | """ |
| | Quick fixes to test for common positioning issues |
| | """ |
| | |
| | fixes = { |
| | "coordinate_system_flip": { |
| | "description": "Test if Y coordinate should be flipped", |
| | "change": "cy = int((1.0 - py) * hh) # Flip Y coordinate", |
| | "original": "cy = int(py * hh)" |
| | }, |
| | |
| | "anchor_point_adjustment": { |
| | "description": "Test different anchor points for scaling", |
| | "change": """ |
| | # Try bottom-center anchor instead of center-center |
| | x0 = int(cx - sw // 2) # Keep X centered |
| | y0 = int(cy - sh) # Anchor at bottom |
| | """, |
| | "original": """ |
| | x0 = int(cx - sw // 2) |
| | y0 = int(cy - sh // 2) |
| | """ |
| | }, |
| | |
| | "alpha_scaling_sync": { |
| | "description": "Ensure alpha and frame scaling are synchronized", |
| | "change": """ |
| | # Force exact same dimensions |
| | if a.shape != (hh, ww): |
| | a = cv2.resize(a, (ww, hh), interpolation=cv2.INTER_LINEAR) |
| | """, |
| | "original": "# No explicit resize check" |
| | }, |
| | |
| | "ui_parameter_validation": { |
| | "description": "Add parameter validation and logging", |
| | "change": """ |
| | px = float(placement.get("x", 0.5)) |
| | py = float(placement.get("y", 0.75)) |
| | ps = float(placement.get("scale", 1.0)) |
| | |
| | print(f"DEBUG: px={px}, py={py}, ps={ps}") |
| | print(f"DEBUG: cx={px*ww}, cy={py*hh}") |
| | """, |
| | "original": "# No debug logging" |
| | } |
| | } |
| | |
| | return fixes |
| |
|
| | if __name__ == "__main__": |
| | print("=== Video Background Replacement - Positioning Debug ===") |
| | print("\n1. Running UI parameter validation...") |
| | validate_ui_parameter_flow() |
| | |
| | print("\n2. Available quick fixes:") |
| | fixes = quick_positioning_fixes() |
| | for fix_name, fix_info in fixes.items(): |
| | print(f"\n{fix_name.upper()}:") |
| | print(f" Description: {fix_info['description']}") |
| | print(f" Change: {fix_info['change']}") |
| | |
| | print("\n3. Debug system ready!") |
| | print(" - Use PositioningDebugger class in your main pipeline") |
| | print(" - Enable debug mode: enable_debug=True") |
| | print(" - Check debug_positioning/ folder for output") |