Pynote

Python、機械学習、画像処理について

OpenCV - matchShape() で輪郭の類似度を計算する。

概要

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

輪郭の抽出する。

使用する画像


import cv2
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Polygon

# 画像を読み込む。
img = cv2.imread('sample.png')

# 画像を表示する。
fig, axes = plt.subplots(figsize=(6, 6))
axes.imshow(img)
axes.axis('off')
plt.show()

# 輪郭を抽出する。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, contours, hierarchy = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

結果を描画する。

def draw_contours(ax, img, contours):
    ax.imshow(img)
    ax.axis('off')
    for i, cnt in enumerate(contours):
        cnt = np.squeeze(cnt, axis=1)  # (NumPoints, 1, 2) -> (NumPoints, 2)
        # 輪郭の点同士を結ぶ線を描画する。
        ax.add_patch(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', size='20')


fig, ax = plt.subplots(figsize=(6, 6))
draw_contours(ax, img, contours)
plt.show()

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

matchShape()

matchShape()

retval = cv.matchShapes(contour1, contour2, method, parameter)
  • 引数
    • contour1: 輪郭1
    • contour2: 輪郭2
    • method: 比較方法
      • cv.CONTOURS_MATCH_I1
      • cv.CONTOURS_MATCH_I2
      • cv.CONTOURS_MATCH_I3
    • parameter: 比較方法に依存するパラメータ (今のところ、使われてない)
  • 返り値
    • retval: 2つの輪郭の類似度を [0, 1] の範囲の値で返す。0に近い値ほど2つの輪郭は類似していることを意味する。

今回6個の輪郭があるので、M_{ij} が輪郭 i と輪郭 j の類似度である 6 \times 6 の行列を作成する。

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)

ヒートマップで可視化する。

import seaborn as sns
ax = sns.heatmap(matches, annot=True, cmap='Reds')
plt.show()

matchShape() の返り値は、2つの輪郭が近いほど0に近い値になるので、ある閾値を設けて、それ未満であれば同じ輪郭と判定する。

# 対角成分は自分自身との類似度なので、inf を入れておく。
np.fill_diagonal(matches, np.inf)
for i, dists in enumerate(matches):
    score, j = dists.min(), dists.argmin()
    if score < 0.02:
        print('輪郭 {} ~ {}'.format(i, j))
輪郭 0 ~ 1
輪郭 1 ~ 0
輪郭 4 ~ 5
輪郭 5 ~ 4

すると、No0 と No1、No4 と No5 が同じ輪郭であると判断された。