Warning: Undefined variable $position in /home/pystyles/pystyle.info/public_html/wp/wp-content/themes/lionblog/functions.php on line 4897

OpenCV – matchShape で輪郭の類似度を計算し、マッチングする方法について

OpenCV – matchShape で輪郭の類似度を計算し、マッチングする方法について

概要

cv2.matchShape() で2つの輪郭の類似度を算出し、マッチングを行う方法について解説します。

Advertisement

cv2.matchShape

retval = cv2.matchShapes(contour1, contour2, method, parameter)
引数
名前 デフォルト値
contour1 ndarray
比較する輪郭
contour2 ndarray
比較する輪郭
method int (ShapeMatchModes)
比較方法
parameter float
比較方法に関するパラメータ (現在は使用されていない)
返り値
名前 説明
retval 類似度

サンプルコード

sample.jpg

In [1]:
import cv2
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from IPython.display import Image, display


def imshow(img):
    """ndarray 配列をインラインで Notebook 上に表示する。
    """
    ret, encoded = cv2.imencode(".jpg", img)
    display(Image(encoded))


def draw_contours(img, contours):
    """輪郭を可視化する。
    """
    fig, ax = plt.subplots(figsize=(7, 7))
    ax.imshow(img)
    ax.set_axis_off()

    for i, cnt in enumerate(contours):
        cnt = np.squeeze(cnt, axis=1)  # (NumPoints, 1, 2) -> (NumPoints, 2)
        # 輪郭の点同士を結ぶ線を描画する。
        ax.add_patch(plt.Polygon(cnt, color="b", fill=None, lw=2))
        # 輪郭の点を描画する。
        ax.plot(cnt[:, 0], cnt[:, 1], "ro", mew=0, ms=4)
        # 輪郭の番号を描画する。
        ax.text(cnt[0][0], cnt[0][1], i, color="orange", fontsize=20)

    plt.show()
In [2]:
# 画像を読み込む。
img = cv2.imread("sample.jpg")

# グレースケールに変換する。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 2値化する。
binary = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)[1]

# 輪郭を抽出する。
contours, hierarchy = cv2.findContours(
    binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
draw_contours(img, contours)

サンプル画像の図形は以下の特徴があります。

  • No0 と No1 は形状、大きさが同じで角度は異なる
  • No4 と No5 は形状が同じで、大きさ、角度が異なる

cv2.matchShapes() は2つの輪郭を渡すと、その類似度を返します。類似度は小さい値ほど、類似度が高いことを意味します。 それぞれの輪郭同士の類似度を計算し、$M_{ij}$ が輪郭 $i$ と輪郭 $j$ の類似度である行列にまとめてみます。

In [3]:
num_cnts = len(contours)

# 類似度を計算する。
matches = np.empty((num_cnts, num_cnts))
for i, j in np.ndindex(*matches.shape):
    matches[i, j] = cv2.matchShapes(contours[i], contours[j], cv2.CONTOURS_MATCH_I1, 0)

# 行列を可視化する。
fig, ax = plt.subplots(figsize=(7, 7))
ax = sns.heatmap(
    matches, annot=True, cmap="Reds", ax=ax, fmt=".2f", annot_kws={"size": 16}
)
plt.show()

対角成分 $M_{ii}$ は同じ輪郭同士の比較なので、類似度は0となります。 2つの輪郭が近いほど0に近い値になるので、ある閾値を設けて、それ未満であれば同じ輪郭と判定するようにします。

In [4]:
threshold = 0.02
tril_indices = np.triu_indices(len(matches), k=1)

for i, j in zip(*tril_indices):
    if matches[i, j] < threshold:
        print(f"輪郭{i} ~ 輪郭{j}: 類似度: {matches[i, j]:.2f}")
輪郭0 ~ 輪郭1: 類似度: 0.02
輪郭4 ~ 輪郭5: 類似度: 0.00

その結果、No0 と No1、No4 と No5 が同じ輪郭であると判定されました。

比較方法 – method

輪郭の類似度の算出は、スケール、位置及び回転に不変な7つの要素で表される HuMoments をそれぞれの輪郭に対して算出し、その HuMoments の距離を計算することにより行っています。この距離の計算方法が method 引数で指定できます。

2つの輪郭 $A, B$ が与えられたとき、それぞれの HuMoments を $h_i^A, h_i^B, (i = 1, 2, \cdots, 7)$ とします。

$m_i^A = \text{sign}(h_i^A) \log h_i^A, m_i^B = \text{sign}(h_i^B) \log h_i^B$ とおくと、

  • cv2.CONTOURS_MATCH_I1
$$ I_1(A,B) = \sum_{i = 1}^7 \left| \frac{1}{m^A_i} – \frac{1}{m^B_i} \right| $$
  • cv2.CONTOURS_MATCH_I2
$$ I_2(A,B) = \sum_{i = 1}^7 \left| m^A_i – m^B_i \right| $$
  • cv.CONTOURS_MATCH_I3 $$ I_3(A,B) = \max_i \frac{ \left| m^A_i – m^B_i \right| }{ \left| m^A_i \right| } $$