🐥

大きな画像から部分画像の座標を取得する方法

2025/02/23に公開

概要

大きな画像の一部が切り出された複数の画像から、元の画像内での座標を取得する機会がありました。本記事では、そのための方法についての備忘録をまとめます。

OpenCV の SIFT (Scale-Invariant Feature Transform) を用いて、テンプレート画像と元の画像を特徴点マッチングし、アフィン変換を推定して座標を取得する方法を紹介します。

実装

必要なライブラリ

pip install opencv-python numpy tqdm

Pythonコード

以下のコードでは、指定した大きな画像 (image_path) に対して、テンプレート画像 (templates_dir 内の PNG 画像) を SIFT でマッチングし、元の画像内の座標を取得します。

import cv2
import numpy as np
from glob import glob
from tqdm import tqdm
import os

# 画像読み込み
def load_image_gray(path):
    img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
    if img is None:
        print(f"画像が見つかりません: {path}")
    return img

# 特徴点抽出
def extract_features(image, detector):
    return detector.detectAndCompute(image, None)

# マッチング処理
def match_features(des1, des2, matcher, ratio_test=0.7, min_matches=4):
    matches = matcher.knnMatch(des1, des2, k=2)
    good_matches = [m for m, n in matches if m.distance < ratio_test * n.distance]
    return good_matches if len(good_matches) >= min_matches else None

# アフィン変換推定
def estimate_affine_transform(kp1, kp2, good_matches):
    src_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
    dst_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
    M_affine, _ = cv2.estimateAffinePartial2D(src_pts, dst_pts, method=cv2.RANSAC, ransacReprojThreshold=5.0)
    return M_affine

# 画像上にマッチング結果を描画
def draw_matched_rectangle(image, M_affine, templ_shape):
    h, w = templ_shape
    rect_pts = np.float32([[0, 0], [w, 0], [w, h], [0, h]])  # 長方形の四隅
    transformed_pts = cv2.transform(np.array([rect_pts]), M_affine)[0]  # 変換後の座標
    cv2.polylines(image, [np.int32(transformed_pts)], isClosed=True, color=(0, 0, 255), thickness=2)
    return transformed_pts

# メイン処理
def main(image_path, templates_dir, output_path):
    # 画像とテンプレート一覧の読み込み
    img = load_image_gray(image_path)
    templ_paths = glob(templates_dir)
    dst_img = cv2.imread(image_path)

    # SIFT特徴量検出器 & BFMatcher 設定
    sift = cv2.SIFT_create()
    bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)
    kp1, des1 = extract_features(img, sift)

    # 特徴点が見つからなかった場合
    if des1 is None:
        print("対象画像の特徴点が見つかりませんでした。")
        return

    for templ_path in tqdm(templ_paths):
        templ = load_image_gray(templ_path)
        if templ is None:
            continue

        kp2, des2 = extract_features(templ, sift)
        if des2 is None:
            continue

        good_matches = match_features(des1, des2, bf)
        if good_matches is None:
            print(f"特徴点のマッチングが不足: {templ_path}")
            continue

        # アフィン変換推定
        M_affine = estimate_affine_transform(kp1, kp2, good_matches)
        if M_affine is None:
            print(f"アフィン変換推定に失敗: {templ_path}")
            continue

        # 矩形描画
        best_dst = draw_matched_rectangle(dst_img, M_affine, templ.shape)

        # ファイル名を矩形の近くに表示
        x, y, _, _ = cv2.boundingRect(best_dst)
        base_name = os.path.splitext(os.path.basename(templ_path))[0]
        cv2.putText(dst_img, base_name, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)

    # 結果を保存
    cv2.imwrite(output_path, dst_img)
    print(f"結果画像を保存しました: {output_path}")

実行

# 実行
if __name__ == "__main__":
    # パラメータ設定
    IMAGE_PATH = "/xxx/default.jpg"
    TEMPLATES_DIR = "/xxx/*.png"
    OUTPUT_PATH = "/xxx/match_result.jpg"
    main(IMAGE_PATH, TEMPLATES_DIR, OUTPUT_PATH)

まとめ

本記事では、SIFT を用いた特徴点マッチング によって、部分画像が元画像のどこに位置するかを推定し、アフィン変換 で位置を特定する方法を紹介しました。

✅ 特徴点の抽出には SIFT を使用(OpenCV 4.4 以降は自由に使用可能)

✅ BFMatcher で特徴点をマッチングし、RANSAC でノイズを除去

✅ アフィン変換で座標を推定し、矩形で元画像に描画

✅ 結果画像を保存し、どの部分画像がどこにあるかを可視化

この方法を活用すれば、古地図の部分画像の位置特定、OCR の領域検出、画像比較などにも応用できます。

📌 今後の課題

  • 画像が回転している場合の補正

  • SIFT より高速なアルゴリズムの検討(ORB, AKAZE など)

  • 処理速度の最適化(特徴点のフィルタリング)

以上、不完全な点もあるかもしれませんが、参考になりましたら幸いです。 📝

Discussion