OpenCVで特徴量マッチング(AKAZE, KNN) サンプルコード付

スポンサーリンク
Python/DeepLearning

こんにちは!みやしんです。

今日はOpenCVの特徴量マッチングという手法を使って2つの画像を比較してみたいと思います。

みやしん
みやしん

特徴量マッチングって何だニャ???

これから分かりやすく説明していくニャ!!!

今回のイメージです!

画像に alt 属性が指定されていません。ファイル名: image-3-1.jpg
OpenCV 特徴量マッチングで出来ること
  1. 同じ画像か判別
  2. 似た画像か判別
  3. 同じものが画像のどこにいるか判別

 こんな感じのことをやってみたい方におススメです!

サンプルコードも付けていますので、自信のない方もコピペして出来ちゃいます!

OpenCV Logo with text svg version.svg

また、OpenCVについてしっかり勉強したい方はこちらもどうぞ! 皆さんも毎日めっちゃ忙しいと思いますが、何とか時間を作って自己投資は続けると良いですよ👍僕も以前は、仕事をしながらプログラミングスクールに通ったこともありますし、今も自己投資はずっと続けるようにしています!

Pyサブスクール:サブスク8,030円/月でPythonを始められるプログラミングスクール
サブスク8,030円/月でPythonを始められるプログラミングスクール。現役エンジニアへの質問も自由に出来ます。話題のPythonを学びたいけどスクールに60万円は高すぎる!でも独学だと挫折が恐い。そんな不満と不安を解決するサブスク型のプ...


スポンサーリンク

特徴量とは

例えば人同士で考えると、「背が高い」とか「おじいさん」など、対象人物の特徴を定性的に表現することができます。コンピュータでは特徴を数値にして定量的に表します。これを特徴量と言います。身長=198cm、年齢=85歳のような感じですね!

特徴量マッチングとは

異なる画像からそれぞれ抽出した特徴量の対応付けをすることです。

今回はopenCVのライブラリにあるAKAZE(Accelerated KAZE)、kNN(k-Nearest Neighbor:k最近傍法)、総当たりマッチング(Brute-Force matcher)という手法を用いてマッチングをしていきたいと思います。

AKAZE(Accelerated KAZE)とは

A-KAZEは拡大縮小、回転、照明変化にも強いロバストな特徴量抽出アルゴリズムです。KAZEというアルゴリズムを高速化してA-KAZE(Acclerated KAZE)となりました。KAZEは日本語の「風」から命名された手法です。SIFTやSURFといったそれ以前の手法と比べて認識精度が高いのが特徴です。また、SIFTやSURFは特許権が設定されており商用で利用するにはライセンスが必要ですが、AKAZEは商用/非商用を問わず利用可能です。

kNN(k-Nearest Neighbor:k最近傍法)とは

KNNは探索空間から最近傍のラベルをK個選択して多数決でクラスラベルを割り当てるアルゴリズムです。Kは自由に決めることができるハイパーパラメータです。

KNNによるクラス分類のイメージはこんな感じです。

赤、青、黄の3クラスに分類されたサンプルがあります。

そして、クラスの分からない白い球が1つあります。この白い球のクラスをKNNで決定します。

ハイパーパラメータをK=4として、白い球から近い順に4つの球を選びます。

すると、赤2つ、青1つ、黄1つとなり、多数決で赤と分類されます。

探索空間から最近傍のラベルをK個探索する方法として、OpenCVは、総当たり法(Brute-Force)と高速近似近傍探索法(FLANN)をサポートしています。今回は総当たり法を採用して進めていきます。

総当たりマッチング(Brute-Force matcher)とは

総当たりマッチング(Brute-Force matcher)は、元画像(1枚目の画像)にある一つの特徴点の特徴量記述子を計算し,比較画像中(2枚目の画像)の全特徴点の特徴量と何らかの距離計算に基づいてマッチングをすることを言います。最も距離が小さい特徴点が対応する特徴点のマッチング結果として返されます。

下記のようなイメージです。

それでは早速画像を準備して特徴量マッチングをやっていきましょう!!

画像の準備

まずは画像の準備です。画像の準備方法は下記の記事をご参照ください。

今回、2枚の犬の画像で特徴量マッチングをします。

元画像 (dog.jpg)

比較画像 (rotate_dog.jpg) ※元画像を拡大+回転させたもの

まずは、この2枚を特徴量マッチングしてみましょう!

特徴量マッチングの実装

まずはコード実装

import cv2
# 画像を読み込む
# 画像1
img1 = cv2.imread("dog.jpg")
# 画像2
img2 = cv2.imread("rotate_dog.jpg")
# A-KAZE検出器の生成
akaze = cv2.AKAZE_create()
# 画像を読込、特徴量を計算
# kp=keypoints(特徴点抽出), des=descriptors(特徴点描画)
# detectAndCompute() => (kp:特徴点の一覧, des:各特徴点の特徴量記述子)  のタプルになります。
kp1, des1 = akaze.detectAndCompute(img1, None)
kp2, des2 = akaze.detectAndCompute(img2, None)

# 特徴量のマッチングを実行
bf = cv2.BFMatcher() # 総当たりマッチング(Brute-Force Matcher)生成
# 特徴量ベクトル同士をBrute-ForceとKNN(kth-nearest neighbor)でマッチング
matches = bf.knnMatch(des1, des2, k=2)
# データをマッチング精度の高いもののみ抽出
ratio = 0.5
good = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good.append([m])
# 対応する特徴点同士を描画
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)

# 画像表示
cv2.imshow('img', img3)

# キー押下で終了
cv2.waitKey(0)
cv2.destroyAllWindows()

実行結果

それぞれの画像で同じ箇所を特徴点として抽出して結んでいますね!

【実験1】ratioの値を増やしてみる (0.5 => 0.8)

ここで実験です!

ratioの値を増やしてみたらどうなるか検証してみました。

# ratio = 0.5 => 0,8に変えてみる
ratio = 0.8
good = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good.append([m])
# 対応する特徴点同士を描画
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)

# 画像表示
cv2.imshow('img', img3)

# キー押下で終了
cv2.waitKey(0)
cv2.destroyAllWindows()

実行結果

おおおおおおお!

めっちゃ増えたwww

ratioの値を大きくすると特徴点が増えるんですね!

ということは逆に減らした場合の結果は予想できますね!

実際に確認してみましょう!

【実験2】ratioの値を増やしてみる (0.5 => 0.2)

# ratio = 0.5 => 0,2に変えてみる
ratio = 0.2
good = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good.append([m])
# 対応する特徴点同士を描画
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)

# 画像表示
cv2.imshow('img', img3)

# キー押下で終了
cv2.waitKey(0)
cv2.destroyAllWindows()

実行結果

予想通り!笑

特徴点が減りましたね!

3つしかないです。

目的に合わせて感度を調整すると良さそうですね!

少しだけコードの中身を見てみましょう。

例えば配列goodはどんな情報を持っているのでしょうか?

【実験3】配列goodの中身を確認

ratio = 0.2
good = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good.append([m])
        
print(good)

実行結果

検出された3つの特徴点の情報を配列で持っているんですね!

【実験4】同一画像の判定方法例

例えば「特徴点が2つ以上だったら同じ画像とみなす!」みたいな判定条件をつくってみるのも面白いですね!

# マッチングした特徴点の数(配列good内の要素数)
len(good)
# 例えば特徴点が2つ以上の時に同一画像と判定する

if len(good) >= 2:
    print("同一画像です")

実行結果

ふむふむ、同一画像の判定に使えそうだ!

【実験5】よく似た子犬(テリア)の画像を使ってマッチングしてみる

# Imageをインポート
from IPython.display import Image
# 似た子犬(テリア)の写真
Image("./teria.jpg")

実行結果

同じ犬?別の犬?どっちかよく分かりませんが、似た写真を準備しました!

いざ、特徴量マッチングをしてみましょう!

img_teria = cv2.imread("teria.jpg")
kp_teria, des_teria = akaze.detectAndCompute(img_teria, None)

matches_teria = bf.knnMatch(des1, des_teria, k=2)

# データをマッチング精度の高いもののみ抽出
ratio = 0.5
good2 = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good2.append([m])
        
# 対応する特徴点同士を描画
img_teria = cv2.drawMatchesKnn(img1, kp1, img_teria, kp_teria, good2, None, flags=2)

# 画像表示
cv2.imshow('img', img_teria)

# キー押下で終了
cv2.waitKey(0)
cv2.destroyAllWindows()

実行結果

おおおお~ちょっと微妙だけどたくさんマッチングしてますねぇ~

更に検証を進めてみましょう!

【実験6】他の犬と一緒

まずは画像を確認してみましょう!

# 次は違う犬種の子犬と一緒に映っている画像(自作)
Image("./with_another.jpg")

実行結果

かわいいポメラニアンですね!

。。。しかし、テリア浮きすぎですね。。。笑

ではマッチングしてみましょう!

img_with_another = cv2.imread("with_another.jpg")
kp_with_another, des_with_another = akaze.detectAndCompute(img_with_another, None)

matches_with_another = bf.knnMatch(des1, des_with_another, k=2)

# データをマッチング精度の高いもののみ抽出
ratio = 0.5
good3 = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good3.append([m])
        
# 対応する特徴点同士を描画
img_with_another = cv2.drawMatchesKnn(img1, kp1, img_with_another, kp_with_another, good3, None, flags=2)

# 画像表示
cv2.imshow('img', img_with_another)

# キー押下で終了
cv2.waitKey(0)
cv2.destroyAllWindows()

実行結果

おおおおおお~!ポメラニアンには殆どマッチングしていないですね!

すごいです!

さらに検証を進めます

【実験7】他の子犬、他の子猫と一緒

# 他の犬や猫と一緒に映っている(自作)
Image("./dogcat.jpg")

実行結果

この画像も特徴量マッチングしてみます!

img_dogcat = cv2.imread("dogcat.jpg")
kp_dogcat, des_dogcat = akaze.detectAndCompute(img_dogcat, None)

matches_dogcat = bf.knnMatch(des1, des_dogcat, k=2)

# データをマッチング精度の高いもののみ抽出
ratio = 0.5
good4 = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good4.append([m])
        
# 対応する特徴点同士を描画
img_dogcat = cv2.drawMatchesKnn(img1, kp1, img_dogcat, kp_dogcat, good4, None, flags=2)

# 画像表示
cv2.imshow('img', img_dogcat)

# キー押下で終了
cv2.waitKey(0)
cv2.destroyAllWindows()

実行結果

ちょっと誤検出が増えましたね!

なるほど、面白いですね!

テリアとのマッチングが一番多いのは変わらないですが、似ているものがあると誤検出してしまいますね!

【実験8】たくさんの犬と一緒

次の画像はこちらです!

# たくさん犬が映っている(自作)
Image("./some_dogs.jpg")

実行結果

端の方に不自然にいますね!笑

では、特徴量マッチングをしてみます。

img_sdogs = cv2.imread("some_dogs.jpg")
kp_sdogs, des_sdogs = akaze.detectAndCompute(img_sdogs, None)

matches_dog_dogs = bf.knnMatch(des1, des_sdogs, k=2)

# データをマッチング精度の高いもののみ抽出
ratio = 0.5
good5 = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good5.append([m])
        
# 対応する特徴点同士を描画
img_dog_dogs = cv2.drawMatchesKnn(img1, kp1, img_sdogs, kp_sdogs, good5, None, flags=2)

# 画像表示
cv2.imshow('img', img_dog_dogs)

# キー押下で終了
cv2.waitKey(0)
cv2.destroyAllWindows()

実行結果

テリアのところに多くの特徴点がマッチングしていますね!

今回も少し誤検出が発生していますが目視で確認するならギリギリOKな感じでしょうか。

奥側の柵に多くの誤検出が見られますね。

【実験9】芝生に寝そべらせてみた

こんな感じの画像を作ってみました!

# 芝生の中に横たわっている感じ(自作)
Image("./green_dog.jpg")

実行結果

不自然過ぎてウケますね。。。笑

では、特徴量マッチングをしてみましょう!

img_greendog = cv2.imread("green_dog.jpg")
kp_greendog, des_greendog = akaze.detectAndCompute(img_greendog, None)

matches_greendog = bf.knnMatch(des1, des_greendog, k=2)

# データをマッチング精度の高いもののみ抽出
ratio = 0.5
good6 = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good6.append([m])
        
# 対応する特徴点同士を描画
img_greendog = cv2.drawMatchesKnn(img1, kp1, img_greendog, kp_greendog, good6, None, flags=2)

# 画像表示
cv2.imshow('img', img_greendog)

# キー押下で終了
cv2.waitKey(0)
cv2.destroyAllWindows()

実行結果

これは悲惨ですね!!!

全く特徴量がマッチしていない!ちょっとウケます。

これは予想外でした。

おそらく芝生模様で輪郭のエッジがたくさん発生していて誤検出してしまうのでしょうか。

背景の選択大切ということが分かりました!

【実験10】違う子犬画像

違う子犬と特徴量マッチングするとどうなるのでしょう?

# 他の子犬と比較
Image("./other_dog.jpg")

実験6で使った画像です。

img_other_dog = cv2.imread("other_dog.jpg")
kp_other_dog, des_other_dog = akaze.detectAndCompute(img_other_dog, None)

matches_other_dog = bf.knnMatch(des1, des_other_dog, k=2)

# データをマッチング精度の高いもののみ抽出
ratio = 0.5
good7 = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good7.append([m])
        
# 対応する特徴点同士を描画
img_other_dog = cv2.drawMatchesKnn(img1, kp1, img_other_dog, kp_other_dog, good7, None, flags=2)

# 画像表示
cv2.imshow('img', img_other_dog)

# キー押下で終了
cv2.waitKey(0)
cv2.destroyAllWindows()

実行結果

悲劇です!笑 

かなりマッチングしています!

似ている子犬単品だとマッチングしてしまうんですね!

実験6では、テリアの画像を合成していたから、そことのマッチング率の方が大きかったから上手くいっていたのでしょう!

裏を返せば、似たものを探すこともできそう!

まとめ

今回はopenCVを使った特徴量マッチングにチャレンジしました!

今回の実験結果
  1. 画像の背景の寄与が大きい。エッジが立ちやすい背景だと誤検出の原因となりそう。
  2. マッチした特徴点の数で同一画像かどうか判別できそう。
  3. 同じものがどこにいるか探すことができる。
  4. 似たものかどうか判別できそう。特徴点の数で条件つくれそう。

こんな感じです!

プログラミングや画像処理ってやっぱり面白いですね!

Pyサブスクール:サブスク8,030円/月でPythonを始められるプログラミングスクール
サブスク8,030円/月でPythonを始められるプログラミングスクール。現役エンジニアへの質問も自由に出来ます。話題のPythonを学びたいけどスクールに60万円は高すぎる!でも独学だと挫折が恐い。そんな不満と不安を解決するサブスク型のプ...


Python、機械学習、AIについてもっと学習したい方はこちらの記事がおススメです!

自己投資は惜しまずやる!をオススメしています。

合わせて読みたい

画像処理関係に興味のある方はこちらの記事もオススメです!

ありがとうございました!

Python/DeepLearning
スポンサーリンク
記事が良かったらSNSでシェアを宜しくお願いします!
みやしんをフォローすると役立つ情報がいっぱいにゃ!
スポンサーリンク

コメント

タイトルとURLをコピーしました