cfb40 / scripts /archive /v3 /debug_red_preprocessing.py
andytaylor-smg's picture
establishing proper package, no need to sys.path.insert
1cd70c6
"""
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()