feat: add book-region cropping from wide captures
This commit is contained in:
parent
b9ddbee81e
commit
f93d51d500
2 changed files with 72 additions and 0 deletions
28
bookmark/image_processing.py
Normal file
28
bookmark/image_processing.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
def crop_to_book(frame: np.ndarray, reference_frame: np.ndarray, margin: int = 20) -> np.ndarray:
|
||||
"""
|
||||
Crops `frame` down to the bounding box of the largest region that
|
||||
differs from `reference_frame` (the book), padded by `margin` pixels
|
||||
and clamped to the frame bounds. Raises ValueError if no difference
|
||||
is found.
|
||||
"""
|
||||
diff = cv2.absdiff(frame, reference_frame)
|
||||
gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
|
||||
_, mask = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)
|
||||
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
if not contours:
|
||||
raise ValueError("No book detected in frame (no difference from reference)")
|
||||
|
||||
largest = max(contours, key=cv2.contourArea)
|
||||
x, y, w, h = cv2.boundingRect(largest)
|
||||
frame_h, frame_w = frame.shape[:2]
|
||||
|
||||
x0 = max(0, x - margin)
|
||||
y0 = max(0, y - margin)
|
||||
x1 = min(frame_w, x + w + margin)
|
||||
y1 = min(frame_h, y + h + margin)
|
||||
|
||||
return frame[y0:y1, x0:x1]
|
||||
44
tests/test_image_processing.py
Normal file
44
tests/test_image_processing.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from bookmark.image_processing import crop_to_book
|
||||
|
||||
|
||||
def make_reference(size=100, color=200):
|
||||
return np.full((size, size, 3), color, dtype=np.uint8)
|
||||
|
||||
|
||||
def make_book_frame(size=100, ref_color=200, book_color=50, box=(30, 30, 20, 20)):
|
||||
frame = np.full((size, size, 3), ref_color, dtype=np.uint8)
|
||||
x, y, w, h = box
|
||||
frame[y:y + h, x:x + w] = book_color
|
||||
return frame
|
||||
|
||||
|
||||
def test_crop_raises_when_no_difference_from_reference():
|
||||
reference = make_reference()
|
||||
with pytest.raises(ValueError):
|
||||
crop_to_book(reference.copy(), reference)
|
||||
|
||||
|
||||
def test_crop_returns_region_around_book_with_margin():
|
||||
reference = make_reference()
|
||||
frame = make_book_frame(box=(30, 30, 20, 20))
|
||||
|
||||
cropped = crop_to_book(frame, reference, margin=10)
|
||||
|
||||
# book occupies x:[30,50) y:[30,50), with margin=10 expected crop is
|
||||
# roughly x:[20,60) y:[20,60) -> 40x40
|
||||
assert cropped.shape[0] == pytest.approx(40, abs=2)
|
||||
assert cropped.shape[1] == pytest.approx(40, abs=2)
|
||||
|
||||
|
||||
def test_crop_clamps_margin_to_frame_bounds():
|
||||
reference = make_reference()
|
||||
frame = make_book_frame(box=(0, 0, 20, 20))
|
||||
|
||||
cropped = crop_to_book(frame, reference, margin=10)
|
||||
|
||||
# book touches top-left corner, margin should clamp instead of going negative
|
||||
assert cropped.shape[0] <= 30
|
||||
assert cropped.shape[1] <= 30
|
||||
Loading…
Reference in a new issue