huge changes
This commit is contained in:
160
border.py
Normal file
160
border.py
Normal file
@ -0,0 +1,160 @@
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw
|
||||
from scipy.signal import find_peaks, savgol_filter
|
||||
from scipy.ndimage import sobel
|
||||
|
||||
def analyze_profile(
|
||||
profile: np.ndarray,
|
||||
prominence: float,
|
||||
width: int,
|
||||
direction: str
|
||||
) -> int | None:
|
||||
"""
|
||||
Analyzes a 1D profile to find the most likely edge coordinate.
|
||||
"""
|
||||
if profile.size == 0:
|
||||
return None
|
||||
|
||||
# 1. Smooth the profile to reduce noise while preserving peak shapes.
|
||||
# The window length must be odd.
|
||||
window_length = min(21, len(profile) // 2 * 2 + 1)
|
||||
if window_length < 5: # savgol_filter requires window_length > polyorder
|
||||
smoothed_profile = profile
|
||||
else:
|
||||
smoothed_profile = savgol_filter(profile, window_length=window_length, polyorder=2)
|
||||
|
||||
# 2. Find all significant peaks in the profile.
|
||||
# Prominence is a measure of how much a peak stands out from the baseline.
|
||||
peaks, properties = find_peaks(smoothed_profile, prominence=prominence, width=width)
|
||||
|
||||
if len(peaks) == 0:
|
||||
return None
|
||||
|
||||
# 3. Select the best peak. We choose the one with the highest prominence.
|
||||
most_prominent_peak_index = np.argmax(properties['prominences'])
|
||||
best_peak = peaks[most_prominent_peak_index]
|
||||
|
||||
return best_peak
|
||||
|
||||
def find_film_edges_gradient(
|
||||
image_path: str,
|
||||
border_percent: int = 15,
|
||||
prominence: float = 10.0,
|
||||
min_width: int = 2
|
||||
) -> tuple[int, int, int, int] | None:
|
||||
"""
|
||||
Detects film edges using a directional gradient method, which is robust
|
||||
to complex borders and internal image features.
|
||||
|
||||
Args:
|
||||
image_path (str): Path to the image file.
|
||||
border_percent (int): The percentage of image dimensions to search for a border.
|
||||
prominence (float): Required prominence of a gradient peak to be considered an edge.
|
||||
This is the most critical tuning parameter. Higher values mean
|
||||
the edge must be sharper and more distinct.
|
||||
min_width (int): The minimum width (in pixels) of a peak to be considered.
|
||||
Helps ignore single-pixel noise.
|
||||
|
||||
Returns:
|
||||
A tuple (left, top, right, bottom) or None if detection fails.
|
||||
"""
|
||||
try:
|
||||
with Image.open(image_path) as img:
|
||||
image_gray = np.array(img.convert('L'), dtype=float)
|
||||
height, width = image_gray.shape
|
||||
except Exception as e:
|
||||
print(f"Error opening or processing image: {e}")
|
||||
return None
|
||||
|
||||
# 1. Calculate directional gradients for the entire image once.
|
||||
grad_y = sobel(image_gray, axis=0) # For horizontal lines (top, bottom)
|
||||
grad_x = sobel(image_gray, axis=1) # For vertical lines (left, right)
|
||||
|
||||
coords = {}
|
||||
search_w = int(width * border_percent / 100)
|
||||
search_h = int(height * border_percent / 100)
|
||||
|
||||
# 2. Find Left Edge (Dark -> Light transition, so positive grad_x)
|
||||
left_band_grad = grad_x[:, :search_w]
|
||||
# We only care about positive gradients (dark to light)
|
||||
left_profile = np.sum(np.maximum(0, left_band_grad), axis=0)
|
||||
left_coord = analyze_profile(left_profile, prominence, min_width, "left")
|
||||
coords['left'] = left_coord if left_coord is not None else 0
|
||||
|
||||
# 3. Find Right Edge (Light -> Dark transition, so negative grad_x)
|
||||
right_band_grad = grad_x[:, -search_w:]
|
||||
# We want the strongest negative gradient, so we flip the sign.
|
||||
right_profile = np.sum(np.maximum(0, -right_band_grad), axis=0)
|
||||
# The profile is from right-to-left, so we analyze its reversed version.
|
||||
right_coord = analyze_profile(right_profile[::-1], prominence, min_width, "right")
|
||||
if right_coord is not None:
|
||||
# Convert coordinate back to the original image space
|
||||
coords['right'] = width - 1 - right_coord
|
||||
else:
|
||||
coords['right'] = width - 1
|
||||
|
||||
# 4. Find Top Edge (Dark -> Light transition, so positive grad_y)
|
||||
top_band_grad = grad_y[:search_h, :]
|
||||
top_profile = np.sum(np.maximum(0, top_band_grad), axis=1)
|
||||
top_coord = analyze_profile(top_profile, prominence, min_width, "top")
|
||||
coords['top'] = top_coord if top_coord is not None else 0
|
||||
|
||||
# 5. Find Bottom Edge (Light -> Dark transition, so negative grad_y)
|
||||
bottom_band_grad = grad_y[-search_h:, :]
|
||||
bottom_profile = np.sum(np.maximum(0, -bottom_band_grad), axis=1)
|
||||
bottom_coord = analyze_profile(bottom_profile[::-1], prominence, min_width, "bottom")
|
||||
if bottom_coord is not None:
|
||||
coords['bottom'] = height - 1 - bottom_coord
|
||||
else:
|
||||
coords['bottom'] = height - 1
|
||||
|
||||
# 6. Sanity check and return
|
||||
left, top, right, bottom = map(int, [coords['left'], coords['top'], coords['right'], coords['bottom']])
|
||||
if not (left < right and top < bottom):
|
||||
print("Warning: Detection failed, coordinates are illogical.")
|
||||
return None
|
||||
|
||||
return (left, top, right, bottom)
|
||||
|
||||
|
||||
# --- Example Usage ---
|
||||
if __name__ == "__main__":
|
||||
# Use the image you provided.
|
||||
# NOTE: You must save your image as 'test_negative_v2.png' in the same
|
||||
# directory as the script, or change the path here.
|
||||
image_file = "test_negative_v2.png"
|
||||
|
||||
print(f"Attempting to detect edges on '{image_file}' with gradient method...")
|
||||
|
||||
# Run the detection. The 'prominence' parameter is the one to tune.
|
||||
# Start with a moderate value and increase if it detects noise, decrease
|
||||
# if it misses a subtle edge.
|
||||
crop_box = find_film_edges_gradient(
|
||||
image_file,
|
||||
border_percent=15, # Increase search area slightly due to complex borders
|
||||
prominence=5.0, # A higher value is needed for this high-contrast example
|
||||
min_width=2
|
||||
)
|
||||
|
||||
if crop_box:
|
||||
print(f"\nDetected image box: {crop_box}")
|
||||
|
||||
with Image.open(image_file) as img:
|
||||
# Add a white border for better visualization if the background is black
|
||||
img_with_border = Image.new('RGB', (img.width + 2, img.height + 2), 'white')
|
||||
img_with_border.paste(img, (1, 1))
|
||||
|
||||
draw = ImageDraw.Draw(img_with_border)
|
||||
# Offset the crop box by 1 pixel due to the added border
|
||||
offset_box = [c + 1 for c in crop_box]
|
||||
draw.rectangle(offset_box, outline="red", width=2)
|
||||
|
||||
output_path = "test_negative_detected_final.png"
|
||||
img_with_border.save(output_path)
|
||||
print(f"Saved visualization to '{output_path}'")
|
||||
try:
|
||||
img_with_border.show(title="Detection Result")
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
print("\nCould not robustly detect the film edges.")
|
Reference in New Issue
Block a user