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 | 類似度 |
サンプルコード
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
- cv2.CONTOURS_MATCH_I2
- cv.CONTOURS_MATCH_I3 $$ I_3(A,B) = \max_i \frac{ \left| m^A_i – m^B_i \right| }{ \left| m^A_i \right| } $$
-
前の記事
OpenCV – 微分フィルタ、Prewitt フィルタ、Sobel フィルタについて 2020.06.14
-
次の記事
OpenCV – ndarray 形式の画像を Notebook 上にインライン表示する方法について 2020.06.16