sundeep-news-scan/scratch_py/_detect_rules.py

75 lines
3.1 KiB
Python

"""PROOF-OF-CONCEPT: detect the printed grey separator rules on a newspaper page
using classical CV (no ML, no API). Horizontal + vertical thin straight lines are
extracted by morphological opening with long thin kernels, then overlaid on the
page so we can see whether they recover article boundaries.
Usage: venv/bin/python3 _detect_rules.py <page_png> [out_png]
"""
import sys
from pathlib import Path
import cv2
import numpy as np
def detect_rules(gray, min_frac=0.10, thickness=9):
"""Return (h_lines, v_lines) as lists of (x1,y1,x2,y2).
min_frac: a rule must be at least this fraction of page width/height long.
"""
H, W = gray.shape
# The rules are mid-grey on white. Invert + threshold so lines become white.
# Use a gentle adaptive-ish binarisation: anything clearly darker than paper.
blur = cv2.GaussianBlur(gray, (3, 3), 0)
bw = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
def extract(kernel_len, horizontal):
if horizontal:
k = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_len, 1))
else:
k = cv2.getStructuringElement(cv2.MORPH_RECT, (1, kernel_len))
opened = cv2.morphologyEx(bw, cv2.MORPH_OPEN, k, iterations=1)
# thicken slightly so near-collinear fragments merge
opened = cv2.dilate(opened, cv2.getStructuringElement(
cv2.MORPH_RECT, (thickness, thickness)))
cnts, _ = cv2.findContours(opened, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
lines = []
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
if horizontal and w >= min_frac * W and h <= 60:
lines.append((x, y + h // 2, x + w, y + h // 2, w))
if (not horizontal) and h >= min_frac * H and w <= 60:
lines.append((x + w // 2, y, x + w // 2, y + h, h))
return lines
h_lines = extract(int(min_frac * W), horizontal=True)
v_lines = extract(int(min_frac * H), horizontal=False)
return h_lines, v_lines
def main(png_path, out_path=None):
png_path = Path(png_path)
out_path = Path(out_path) if out_path else Path(f"/tmp/rules_{png_path.stem}.png")
img = cv2.imread(str(png_path))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
H, W = gray.shape
h_lines, v_lines = detect_rules(gray)
print(f"page {png_path.name} {W}x{H}")
print(f" horizontal rules: {len(h_lines)}")
for x1, y1, x2, y2, ln in sorted(h_lines, key=lambda L: L[1]):
print(f" y={y1:>5} x[{x1:>4}..{x2:>4}] len={ln}")
print(f" vertical rules: {len(v_lines)}")
for x1, y1, x2, y2, ln in sorted(v_lines, key=lambda L: L[0]):
print(f" x={x1:>5} y[{y1:>4}..{y2:>4}] len={ln}")
for x1, y1, x2, y2, ln in h_lines:
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 6) # red = horizontal
for x1, y1, x2, y2, ln in v_lines:
cv2.line(img, (x1, y1), (x2, y2), (255, 0, 0), 6) # blue = vertical
cv2.imwrite(str(out_path), img)
print(f"\noverlay written: {out_path}")
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else None)