Initial scaffold: capture + vision modules with detect CLI
- capture/: frame sampling at configurable fps, ffmpeg audio extraction (WAV, whisper-ready) - vision/: changed-pixel motion scoring, stable/moving segmentation, contour + perspective document detection, Laplacian sharpness scoring, optional CLAHE enhancement - pipeline: two-pass detect (motion timeline, then best-frame crop per stable window) writing crops, debug frames, report.json, and motion_scores.csv - CLI: probe / detect / extract-audio subcommands - config.yaml with tunable thresholds; placeholder packages for events, transcribe, ocr, naming, pdf, review_cli - synthetic sample video generator + 16 unit tests
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from paperpod.vision.document import detect_document, warp_document
|
||||
from paperpod.vision.sharpness import sharpness_score
|
||||
|
||||
|
||||
def synthetic_frame(angle: float = 8.0) -> np.ndarray:
|
||||
"""Dark table with a rotated white 400x600 'document'."""
|
||||
frame = np.full((720, 1280, 3), (60, 90, 120), dtype=np.uint8)
|
||||
doc = np.full((600, 400, 3), 245, dtype=np.uint8)
|
||||
canvas = np.zeros_like(frame)
|
||||
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
|
||||
x0, y0 = 440, 60
|
||||
canvas[y0 : y0 + 600, x0 : x0 + 400] = doc
|
||||
mask[y0 : y0 + 600, x0 : x0 + 400] = 255
|
||||
matrix = cv2.getRotationMatrix2D((x0 + 200, y0 + 300), angle, 1.0)
|
||||
canvas = cv2.warpAffine(canvas, matrix, (1280, 720))
|
||||
mask = cv2.warpAffine(mask, matrix, (1280, 720))
|
||||
frame[mask > 0] = canvas[mask > 0]
|
||||
return frame
|
||||
|
||||
|
||||
def test_detects_rotated_document():
|
||||
det = detect_document(synthetic_frame())
|
||||
assert det is not None
|
||||
assert det.quad.shape == (4, 2)
|
||||
assert det.area_ratio > 0.2 # 400x600 doc in a 1280x720 frame
|
||||
|
||||
|
||||
def test_warp_restores_aspect_ratio():
|
||||
det = detect_document(synthetic_frame(angle=8.0))
|
||||
crop = warp_document(synthetic_frame(angle=8.0), det.quad)
|
||||
h, w = crop.shape[:2]
|
||||
# Source doc is 400x600 (aspect 1.5); allow tolerance for edges/dilation.
|
||||
assert 1.3 < h / w < 1.7
|
||||
# Crop should be mostly white paper.
|
||||
assert crop.mean() > 180
|
||||
|
||||
|
||||
def test_no_document_on_empty_table():
|
||||
frame = np.full((720, 1280, 3), (60, 90, 120), dtype=np.uint8)
|
||||
assert detect_document(frame) is None
|
||||
|
||||
|
||||
def test_sharpness_prefers_sharp_frame():
|
||||
sharp = synthetic_frame()
|
||||
blurred = cv2.GaussianBlur(sharp, (31, 31), 0)
|
||||
assert sharpness_score(sharp) > sharpness_score(blurred)
|
||||
Reference in New Issue
Block a user