ken_nogi/PythonProj/ScanOCR/スクリプト/box_detector.py
Kenichiro NOGI b8b8f4d93c chore: スクリプト配置をスクリプト/フォルダへ整理しテスト基盤を追加
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 10:07:24 +09:00

104 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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)}")