# -*- coding: utf-8 -*- """ テンプレート画像内の赤枠・青枠を検出し、OCR抽出用の矩形領域を返すモジュール。 前提: - テンプレートは元PDFと同じ用紙サイズ・解像度でスキャン/作成されていること - 赤枠 = 氏名欄、青枠 = 日付欄 (必要に応じて色を追加可能) """ import cv2 import numpy as np # HSV色空間での色範囲定義(赤は色相環の両端にまたがるため2レンジ) COLOR_RANGES = { "red": [ # (lower_hsv, upper_hsv) (np.array([0, 100, 100]), np.array([10, 255, 255])), (np.array([160, 100, 100]), np.array([180, 255, 255])), ], "blue": [ (np.array([100, 100, 100]), np.array([130, 255, 255])), ], } MIN_BOX_AREA = 500 # ノイズ除去用の最小面積(px^2)。テンプレート解像度に応じて調整してください def _detect_color_boxes(img_bgr: np.ndarray, color_name: str) -> list[tuple[int, int, int, int]]: """指定色の矩形枠を検出し、[(x, y, w, h), ...] のリストを返す(枠線内側の矩形を返す)""" hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV) mask = np.zeros(hsv.shape[:2], dtype=np.uint8) for lower, upper in COLOR_RANGES[color_name]: mask |= cv2.inRange(hsv, lower, upper) # 枠線の途切れを補正 kernel = np.ones((5, 5), np.uint8) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=2) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) boxes = [] for c in contours: area = cv2.contourArea(c) if area < MIN_BOX_AREA: continue x, y, w, h = cv2.boundingRect(c) boxes.append((x, y, w, h)) return boxes def detect_template_fields(template_path: str) -> dict[str, tuple[int, int, int, int]]: """ テンプレート画像から赤枠(氏名)・青枠(日付)を検出する。 Returns: { "name": (x, y, w, h), # 赤枠が見つかった場合 "date": (x, y, w, h), # 青枠が見つかった場合 } 画像サイズは呼び出し側で正規化(比率)して使うため、あわせて画像サイズも返す。 """ img = cv2.imread(template_path) if img is None: raise FileNotFoundError(f"テンプレート画像を読み込めません: {template_path}") h_img, w_img = img.shape[:2] result = {"_image_size": (w_img, h_img)} red_boxes = _detect_color_boxes(img, "red") blue_boxes = _detect_color_boxes(img, "blue") if red_boxes: # 最大面積のものを採用(複数検出された場合のノイズ対策) result["name"] = max(red_boxes, key=lambda b: b[2] * b[3]) if blue_boxes: result["date"] = max(blue_boxes, key=lambda b: b[2] * b[3]) return result def to_ratio_box(box: tuple[int, int, int, int], image_size: tuple[int, int]) -> tuple[float, float, float, float]: """ピクセル座標を画像サイズに対する比率(0.0-1.0)に変換する。 対象PDFの解像度がテンプレートと異なっていても位置を再現できるようにするため。""" x, y, w, h = box iw, ih = image_size return (x / iw, y / ih, w / iw, h / ih) if __name__ == "__main__": import sys import json if len(sys.argv) < 2: print("使い方: python box_detector.py <テンプレート画像パス>") sys.exit(1) fields = detect_template_fields(sys.argv[1]) image_size = fields.pop("_image_size") print(f"画像サイズ: {image_size}") for label, box in fields.items(): ratio = to_ratio_box(box, image_size) print(f"{label}: pixel={box} ratio={tuple(round(v, 4) for v in ratio)}")