Spaces:
Runtime error
Runtime error
import cv2 | |
import numpy as np | |
from pathlib import Path | |
from PIL import Image | |
import pillow_avif | |
import logging | |
from config import NORMALIZE_IMAGES_TO, JPEG_QUALITY | |
logger = logging.getLogger(__name__) | |
def normalize_image(input_path: Path, output_path: Path) -> bool: | |
"""Convert image to normalized format (PNG or JPEG) and optionally remove black bars | |
Args: | |
input_path: Source image path | |
output_path: Target path | |
Returns: | |
bool: True if successful, False otherwise | |
""" | |
try: | |
# Open image with PIL | |
with Image.open(input_path) as img: | |
# Convert to RGB if needed | |
if img.mode in ('RGBA', 'LA'): | |
background = Image.new('RGB', img.size, (255, 255, 255)) | |
if img.mode == 'RGBA': | |
background.paste(img, mask=img.split()[3]) | |
else: | |
background.paste(img, mask=img.split()[1]) | |
img = background | |
elif img.mode != 'RGB': | |
img = img.convert('RGB') | |
# Convert to numpy for black bar detection | |
img_np = np.array(img) | |
# Detect black bars | |
top, bottom, left, right = detect_black_bars(img_np) | |
# Crop if black bars detected | |
if any([top > 0, bottom < img_np.shape[0] - 1, | |
left > 0, right < img_np.shape[1] - 1]): | |
img = img.crop((left, top, right, bottom)) | |
# Save as configured format | |
if NORMALIZE_IMAGES_TO == 'png': | |
img.save(output_path, 'PNG', optimize=True) | |
else: # jpg | |
img.save(output_path, 'JPEG', quality=JPEG_QUALITY, optimize=True) | |
return True | |
except Exception as e: | |
logger.error(f"Error converting image {input_path}: {str(e)}") | |
return False | |
def detect_black_bars(img: np.ndarray) -> tuple[int, int, int, int]: | |
"""Detect black bars in image | |
Args: | |
img: numpy array of image (HxWxC) | |
Returns: | |
Tuple of (top, bottom, left, right) crop coordinates | |
""" | |
# Convert to grayscale if needed | |
if len(img.shape) == 3: | |
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) | |
else: | |
gray = img | |
# Threshold to detect black regions | |
threshold = 20 | |
black_mask = gray < threshold | |
# Find black bars by analyzing row/column means | |
row_means = np.mean(black_mask, axis=1) | |
col_means = np.mean(black_mask, axis=0) | |
# Detect edges where black bars end (95% threshold) | |
black_threshold = 0.95 | |
# Find top and bottom crops | |
top = 0 | |
bottom = img.shape[0] | |
for i, mean in enumerate(row_means): | |
if mean > black_threshold: | |
top = i + 1 | |
else: | |
break | |
for i, mean in enumerate(reversed(row_means)): | |
if mean > black_threshold: | |
bottom = img.shape[0] - i - 1 | |
else: | |
break | |
# Find left and right crops | |
left = 0 | |
right = img.shape[1] | |
for i, mean in enumerate(col_means): | |
if mean > black_threshold: | |
left = i + 1 | |
else: | |
break | |
for i, mean in enumerate(reversed(col_means)): | |
if mean > black_threshold: | |
right = img.shape[1] - i - 1 | |
else: | |
break | |
return top, bottom, left, right | |