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

OpenCV – cv2.watershed で繋がっている輪郭を分離する方法

OpenCV – cv2.watershed で繋がっている輪郭を分離する方法

概要

OpenCV の cv2.watershed で繋がっている輪郭を分離する方法を紹介します。

Advertisement

手順

次の画像の各コインの輪郭を取得することを目指します。

sample.jpg

2値化する

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


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


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

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

# 大津の手法で2値化する。
ret, bin_img = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# ノイズを削除する。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
bin_img = cv2.morphologyEx(bin_img, cv2.MORPH_OPEN, kernel, iterations=2)
imshow(bin_img)

コインの領域が繋がっており、このまま領域抽出を行った場合、以下のように1つの固まりとして抽出されてしまいます。

sure background を抽出する

確実に背景といえる領域を sure background といいます。 膨張演算により、前景領域 (コインの部分) を膨張させ、膨張後もなお背景である部分は sure background といえます。

In [2]:
# sure background 領域を抽出する。
sure_bg = cv2.dilate(bin_img, kernel, iterations=3)
imshow(sure_bg)
Advertisement

sure foreground を抽出する

確実に前景といえる領域を sure foreground といいます。 2値画像に対して距離変換を行ったあと、背景からの距離が一定以上の領域のみ前景とすることで、sure foreground が抽出できます。

In [3]:
# 距離マップを作成する。
dist = cv2.distanceTransform(bin_img, cv2.DIST_L2, 5)
imshow(dist)

# sure foreground 領域を抽出する。
ret, sure_fg = cv2.threshold(dist, 0.5 * dist.max(), 255, cv2.THRESH_BINARY)
sure_fg = sure_fg.astype(np.uint8)  # float32 -> uint8
imshow(sure_fg)

前景か背景か判断できない領域を抽出する

確実に背景といえる領域 (sure background) から確実に前景といえる領域 (sure foreground) の差分をとることで、前景か背景か判断できない領域を抽出します。

In [4]:
# 前景か背景か判断できない領域を抽出する。
unknown = cv2.subtract(sure_bg, sure_fg)
imshow(unknown)

各領域にラベル付けを行う

connectedComponents() で確実に前景といえる部分をラベルを付けます。connectedComponents() では、背景が0で、各物体が 1, 2, … とラベル付けされます。そこで、ラベルを1ずつ増やして、前景か背景か判断できない領域がラベル0となるように調整します。

  • ラベル0: 前景か背景か判断できない領域
  • ラベル1: 背景
  • ラベル2以上: 物体
In [5]:
# sure foreground にたいして、ラベル付を行う。
ret, markers = cv2.connectedComponents(sure_fg)

# 前景か背景か判断できない領域はラベル0
markers += 1
markers[unknown == 255] = 0

fig, ax = plt.subplots(figsize=(6, 6))
ax.imshow(markers, cmap="tab20b")
plt.show()
Advertisement

watershed アルゴリズムを適用する

watershed アルゴリズムを適用すると、各コインがそれぞれ別れた形でラベル付けされます。

In [6]:
# watershed アルゴリズムを適用する。
markers = cv2.watershed(img, markers)

fig, ax = plt.subplots(figsize=(6, 6))
ax.imshow(markers, cmap="tab20b")
plt.show()

watershed アルゴリズムの結果を元に輪郭抽出を行う

watershed アルゴリズムにより、各物体が区別できるようにラベリングが行われたので、これを元に輪郭抽出を行います。

In [7]:
labels = np.unique(markers)

coins = []
for label in labels[2:]:  # 0:背景ラベル 1:境界ラベル は無視する。

    # ラベル label の領域のみ前景、それ以外は背景となる2値画像を作成する。
    target = np.where(markers == label, 255, 0).astype(np.uint8)

    # 作成した2値画像に対して、輪郭抽出を行う。
    contours, hierarchy = cv2.findContours(
        target, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
    )
    coins.append(contours[0])

# 輪郭を描画する。
cv2.drawContours(img, coins, -1, color=(0, 0, 255), thickness=2)
imshow(img)

参考文献