Add right-edge spine shadow detection for book scans
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 37s
CI / test-go-edu-search (push) Successful in 27s
CI / test-python-klausur (push) Failing after 1m56s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 22s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 37s
CI / test-go-edu-search (push) Successful in 27s
CI / test-python-klausur (push) Failing after 1m56s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 22s
Mirror the left-edge shadow detection for the right side: analyze brightness gradient in the right 25% to find scanner gray strips from book spines. Cuts at the last bright column before the shadow dip. Fixes cropping of book scans where the next page bleeds in. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -201,8 +201,8 @@ def detect_and_crop_page(
|
||||
# --- Left edge: spine-shadow detection ---
|
||||
left_edge = _detect_left_edge_shadow(gray, binary, w, h)
|
||||
|
||||
# --- Right edge: binary vertical projection ---
|
||||
right_edge = _detect_right_edge(binary, w, h)
|
||||
# --- Right edge: spine-shadow detection ---
|
||||
right_edge = _detect_right_edge_shadow(gray, binary, w, h)
|
||||
|
||||
# --- Top / bottom edges: binary horizontal projection ---
|
||||
top_edge, bottom_edge = _detect_top_bottom_edges(binary, w, h)
|
||||
@@ -323,8 +323,50 @@ def _detect_left_edge_shadow(
|
||||
return _detect_edge_projection(binary, axis=0, from_start=True, dim=w)
|
||||
|
||||
|
||||
def _detect_right_edge(binary: np.ndarray, w: int, h: int) -> int:
|
||||
"""Detect right content edge via binary vertical projection."""
|
||||
def _detect_right_edge_shadow(
|
||||
gray: np.ndarray,
|
||||
binary: np.ndarray,
|
||||
w: int,
|
||||
h: int,
|
||||
) -> int:
|
||||
"""Detect right content edge, accounting for book-spine shadow.
|
||||
|
||||
Mirror of _detect_left_edge_shadow: look at the right 25% of the image
|
||||
for a brightness dip (scanner gray strip at book spine).
|
||||
The darkest point in the gradient marks the spine center; crop there.
|
||||
"""
|
||||
search_w = max(1, w // 4)
|
||||
right_start = w - search_w
|
||||
|
||||
# Column-mean brightness in the right quarter
|
||||
col_means = np.mean(gray[:, right_start:], axis=0).astype(np.float64)
|
||||
|
||||
# Smooth with boxcar kernel (width = 1% of image width, min 5)
|
||||
kernel_size = max(5, w // 100)
|
||||
if kernel_size % 2 == 0:
|
||||
kernel_size += 1
|
||||
kernel = np.ones(kernel_size) / kernel_size
|
||||
smoothed = np.convolve(col_means, kernel, mode="same")
|
||||
|
||||
# Determine brightness threshold: midpoint between darkest and brightest
|
||||
val_min = float(np.min(smoothed))
|
||||
val_max = float(np.max(smoothed))
|
||||
shadow_range = val_max - val_min
|
||||
|
||||
# Only use shadow detection if there is a meaningful brightness gradient (> 20 levels)
|
||||
if shadow_range > 20:
|
||||
threshold = val_min + shadow_range * 0.6
|
||||
# Find LAST column (from right) where brightness exceeds threshold
|
||||
# = first column from right that drops below threshold marks the spine
|
||||
above = np.where(smoothed >= threshold)[0]
|
||||
if len(above) > 0:
|
||||
# The last bright column before it drops into shadow
|
||||
shadow_edge = right_start + int(above[-1])
|
||||
logger.debug("Right edge: shadow detected at x=%d (range=%.0f)",
|
||||
shadow_edge, shadow_range)
|
||||
return shadow_edge
|
||||
|
||||
# Fallback: binary vertical projection
|
||||
return _detect_edge_projection(binary, axis=0, from_start=False, dim=w)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user