""" Debug script to visualize each preprocessing step for red play clock digits. """ from pathlib import Path import cv2 import numpy as np def main(): # Load the red play clock region (frame 472849, which shows "5") region_path = Path("output/debug/red_play_clock/frame_472849_region.png") output_dir = Path("output/debug/red_preprocessing") output_dir.mkdir(parents=True, exist_ok=True) # Load the region region = cv2.imread(str(region_path)) if region is None: print(f"Failed to load {region_path}") return print(f"Loaded region: {region.shape}") # Step 1: Split into color channels b, g, r = cv2.split(region) cv2.imwrite(str(output_dir / "01_red_channel.png"), r) cv2.imwrite(str(output_dir / "01_green_channel.png"), g) cv2.imwrite(str(output_dir / "01_blue_channel.png"), b) print(f"Red channel - min: {r.min()}, max: {r.max()}, mean: {r.mean():.1f}, std: {r.std():.1f}") print(f"Green channel - min: {g.min()}, max: {g.max()}, mean: {g.mean():.1f}") print(f"Blue channel - min: {b.min()}, max: {b.max()}, mean: {b.mean():.1f}") # Step 2: Use red channel as grayscale gray = r.copy() cv2.imwrite(str(output_dir / "02_gray_from_red.png"), gray) print(f"Gray (red channel) - shape: {gray.shape}") # Step 3: Scale up by 4x scale_factor = 4 scaled = cv2.resize(gray, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_LINEAR) cv2.imwrite(str(output_dir / "03_scaled.png"), scaled) print(f"Scaled - shape: {scaled.shape}, min: {scaled.min()}, max: {scaled.max()}, mean: {scaled.mean():.1f}") # Step 4: Otsu's thresholding threshold, binary_otsu = cv2.threshold(scaled, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) cv2.imwrite(str(output_dir / "04_otsu_binary.png"), binary_otsu) print(f"Otsu threshold: {threshold}") print(f"Binary (Otsu) - white pixels: {(binary_otsu == 255).sum()}, black pixels: {(binary_otsu == 0).sum()}") # Step 4b: Try different fixed thresholds for thresh_val in [30, 50, 70, 90]: _, binary_fixed = cv2.threshold(scaled, thresh_val, 255, cv2.THRESH_BINARY) cv2.imwrite(str(output_dir / f"04_fixed_thresh_{thresh_val}.png"), binary_fixed) white_pix = (binary_fixed == 255).sum() black_pix = (binary_fixed == 0).sum() print(f"Fixed threshold {thresh_val}: white={white_pix}, black={black_pix}") # Step 5: Check mean intensity and decide on inversion binary = binary_otsu.copy() mean_intensity = np.mean(binary) print(f"Mean intensity after Otsu: {mean_intensity:.1f}") if mean_intensity < 128: print("Mean < 128, inverting image") binary = cv2.bitwise_not(binary) cv2.imwrite(str(output_dir / "05_after_inversion_check.png"), binary) # Step 6: Morphological operations kernel = np.ones((2, 2), np.uint8) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) cv2.imwrite(str(output_dir / "06_after_morph_close.png"), binary) binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) cv2.imwrite(str(output_dir / "06_after_morph_open.png"), binary) # Step 7: Add padding padding = 10 binary = cv2.copyMakeBorder(binary, padding, padding, padding, padding, cv2.BORDER_CONSTANT, value=255) cv2.imwrite(str(output_dir / "07_final_with_padding.png"), binary) print(f"\nFinal image shape: {binary.shape}") print(f"Output saved to: {output_dir}") # Also compare with standard grayscale approach print("\n--- Comparing with standard grayscale ---") gray_standard = cv2.cvtColor(region, cv2.COLOR_BGR2GRAY) cv2.imwrite(str(output_dir / "compare_standard_gray.png"), gray_standard) print(f"Standard grayscale - min: {gray_standard.min()}, max: {gray_standard.max()}, mean: {gray_standard.mean():.1f}") scaled_standard = cv2.resize(gray_standard, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_LINEAR) threshold_std, binary_standard = cv2.threshold(scaled_standard, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) cv2.imwrite(str(output_dir / "compare_standard_otsu.png"), binary_standard) print(f"Standard grayscale Otsu threshold: {threshold_std}") if __name__ == "__main__": main()