Help us understand the problem. What is going on with this article?

画像の特徴点情報をファイルに保存してマッチングに利用する方法

はじめに

OpenCVの特徴点抽出及びマッチングを用いる機会が多いのですが、
複数画像を用いる場合、特徴点抽出に時間がかかることがよく起こりました。
そこで今回は特徴点情報をファイルに出力し、マッチングに利用するプログラムを作りました。

今回は例として、女の子たち(12枚のソース画像)から赤めがねの女の子(ターゲット画像)を見つけてみましょう!

ソース画像⬇︎
youngwoman_37.jpgyoungwoman_38.jpgyoungwoman_39.jpgyoungwoman_40.jpg

youngwoman_41.jpgyoungwoman_42.jpgyoungwoman_43.jpgyoungwoman_44.jpg

youngwoman_45.jpgyoungwoman_46.jpgyoungwoman_47.jpgyoungwoman_48.jpg

ターゲット画像⬇︎
target_girl.jpg

開発環境

今回は以下の環境で開発しました。
OpenCV 4.1.2
Python 3.8.1

構成

以下プログラムの流れです。次節から順を追って解説します。
1. ソース画像の特徴点情報を取得しファイルに保存
2. ターゲット画像の特徴点情報を取得
3. ファイルからソース画像の特徴点情報を取得
4. マッチング

1. ソース画像の特徴点情報を取得しファイルに保存

まずはsave_features.pyにて、ソース画像(女の子たち)の特徴点情報を取得しファイルに保存します。
今回はOpenCVに実装されている特徴点記述子、AKAZEを用いました。
keypointをファイル保存するにあたり、cv::KeyPointにアクセスしてリスト化する必要があります。

save_features.py
# keypointをlist化
keypoint = []
for p in features[0]:
    temp = (p.pt, p.size, p.angle, p.response, p.octave, p.class_id)
    keypoint.append(temp)

また、今回はメモリの消費量を抑えるために特徴点情報をbyte型に変換しました。

save_features.py
# keypointをbytesに変換
map(bytes, keypoints)

以下、ソースコード全体です。

save_features.py
import cv2 as cv
import pickle

SOURCE_FILES = [
    "youngwoman_37.jpg",
    "youngwoman_38.jpg",
    "youngwoman_39.jpg",
    "youngwoman_40.jpg",
    "youngwoman_41.jpg",
    "youngwoman_42.jpg",
    "youngwoman_43.jpg",
    "youngwoman_44.jpg",
    "youngwoman_45.jpg",
    "youngwoman_46.jpg",
    "youngwoman_47.jpg",
    "youngwoman_48.jpg",
]


def get_features(img_file_name):
    """Get features of master images

        Args:
            img_file_name(list): Master image

        Returns:
            keypoints, descriptors, img

    """
    img = cv.imread("images/" + img_file_name)

    # 特徴点情報抽出
    akaze = cv.AKAZE_create()
    kp, des = akaze.detectAndCompute(img, None)

    features = [kp, des]

    return features


sources = {}
for item in SOURCE_FILES:
    features = get_features(item)
    # keypointをlist化
    keypoints = []
    for p in features[0]:
        temp = (p.pt, p.size, p.angle, p.response, p.octave, p.class_id)
        keypoints.append(temp)

    # keypointsをbytesに変換
    map(bytes, keypoints)
    # 特徴点情報を辞書化
    sources[item] = {
        "src": item,
        "keypoint": keypoints,
        "descriptor": features[1],
    }

# 特徴点情報をファイルに書き込み
with open("sources_data.pickle", mode="wb") as f:
    pickle.dump(sources, f)

2.ターゲット画像の特徴点情報を取得

手順2からget_features_from_file.pyにて処理を行なっています。
ソース画像同様、特徴点記述子であるAKAZEを用いて特徴点情報を取得します。

get_features_from_file.py
# ターゲット画像読み込み
target_img = cv.imread("images/target_girl.jpg")
# 特徴量取得
akaze = cv.AKAZE_create()
target_keypoint, target_descriptor = akaze.detectAndCompute(target_img, None)

3.ファイルからソース画像の特徴点情報を取得

get_sources()でファイルから特徴点情報を取得します。
pickle化するためにkeypointsをbyte化したので、listに変換し元の構造に戻します。

get_features_from_file.py
def get_sources():
    """Get source's features from file

        Returns:
            sources(list): source's keypoints, descriptors,and img
        """
    # 特徴点情報をファイルから取得
    with open("sources_data.pickle", mode="rb") as f:
        sources = pickle.load(f)

    for n in sources:
        items = sources[n]
        # keypointsをbytesからlistに直す
        list(map(list, items["keypoint"]))
        # keypointsを元の構造に復元
        keypoints = []
        for p in items["keypoint"]:
            temp = cv.KeyPoint(
                x=p[0][0],
                y=p[0][1],
                _size=p[1],
                _angle=p[2],
                _response=p[3],
                _octave=p[4],
                _class_id=p[5],
            )
            keypoints.append(temp)
        items["keypoint"] = keypoints

    return sources

4.マッチング

ソース画像ごとにターゲット画像の特徴点情報とマッチングを行います。
データを間引きし、マッチングした特徴点の数が設定した閾値(今回は20に設定)以上であればマッチング成功とします。

get_features_from_file.py
for n in sources:
    source = sources[n]
    source_img = cv.imread("images/" + source["src"])
    matches = matcher.knnMatch(source["descriptor"], target_des, k=2)
    # データを間引きする
    ratio = 0.5
    matched_keypoints = []
    for m, n in matches:
        if m.distance < ratio * n.distance:
            matched_keypoints.append([m])

    # 任意の閾値よりgoodが多い場合結果画像を出力
    if len(matched_keypoints) > 20:
        out = cv.drawMatchesKnn(
            source_img,
            source["keypoint"],
            target_img,
            target_kp,
            matched_keypoints,
            None,
            flags=2,
        )

以下、ソースコード全体です。

get_features_from_file.py
import cv2 as cv
import pickle


def get_sources():
    """Get source's features from file

        Returns:
            sources(list): source's keypoints, descriptors,and img
        """
    # 特徴点情報をファイルから取得
    with open("sources_data.pickle", mode="rb") as f:
        sources = pickle.load(f)

    for n in sources:
        items = sources[n]
        # keypointsをbytesからlistに直す
        list(map(list, items["keypoint"]))
        # keypointsを元の構造に復元
        keypoints = []
        for p in items["keypoint"]:
            temp = cv.KeyPoint(
                x=p[0][0],
                y=p[0][1],
                _size=p[1],
                _angle=p[2],
                _response=p[3],
                _octave=p[4],
                _class_id=p[5],
            )
            keypoints.append(temp)
        items["keypoint"] = keypoints

    return sources


matcher = cv.BFMatcher()

# ターゲット画像読み込み
target_img = cv.imread("images/target_girl.jpg")
# 特徴量取得
akaze = cv.AKAZE_create()
target_kp, target_des = akaze.detectAndCompute(target_img, None)
# ソース画像の特徴点情報をファイルから読み込み
sources = get_sources()

for n in sources:
    source = sources[n]
    source_img = cv.imread("images/" + source["src"])
    matches = matcher.knnMatch(source["descriptor"], target_des, k=2)
    # データを間引きする
    ratio = 0.5
    matched_keypoints = []
    for m, n in matches:
        if m.distance < ratio * n.distance:
            matched_keypoints.append([m])

    # 任意の閾値よりgoodが多い場合結果画像を出力
    if len(matched_keypoints) > 20:
        out = cv.drawMatchesKnn(
            source_img,
            source["keypoint"],
            target_img,
            target_kp,
            matched_keypoints,
            None,
            flags=2,
        )

cv.imwrite("images/result.jpg", out)
cv.waitKey()

結果

以下の結果画像では、ソース画像とターゲット画像とでマッチングした特徴点同士を描画しています。
無事ターゲットの女の子を見つけることができました!
image.png

終わりに

特徴点をファイルに保存する肝は、
cv::KeyPointにアクセスすること
アクセスした情報をもとにリスト化すること
です。
OpenCVで画像処理する機会があればぜひ試してみてください。

参考ページ

Python3版OpenCVのKeyPointをファイルに書き出す

python3,opencv3で特徴点マッチング(AKAZE, KNN)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした