Pynote

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

OpenCV - モルフォロジー演算について

概要

モルフォロジー演算は主に二値画像を対象としたいくつかの非線形な演算を指す。
二値画像からノイズを削除したり、輪郭を抽出するのに役立つ。

試した環境

ipynb は こちら

構成要素

構成要素 (structuring element) またはカーネル (kernel) とはモルフォロジー演算 (morpology operation) を行う際に入力画像の走査を行うための2次元の行列で、各要素は0または1で構成される。
通常は 3x3、5x5 など小さい形状のものが使用される。
画像を走査する際に走査対象のピクセルを識別する構成要素内の成分を原点 (origin) またはアンカー (anchor) という。
構成要素の値が1である成分は走査対象のピクセルの近傍を定義する。

形状が 3x3 で (1, 1) 成分をアンカーとした以下のような構成要素を用意する。

f:id:nekobean:20180614192206p:plain

グレースケール画像を入力とする。
モルフォロジー演算では1ピクセルずつ走査し、処理を実行していく。

f:id:nekobean:20180614192212p:plain

今、画像の位置 (2, 2) のピクセルが走査対象とした場合を考える。
先程の構成要素をそのアンカーが走査対象のピクセルに合うように配置する。
このとき、構成要素で値が1となる成分がその走査対象の近傍となる。

f:id:nekobean:20180614192412p:plain

構成要素の作成

cv::getStructuringElement() で作成できる。

retval = cv2.getStructuringElement(shape, ksize[, anchor])
  • shape: 近傍とする形状
    • cv2.MORPH_RECT: 長方形
    • cv2.MORPH_CROSS: 十字
    • cv2.MORPH_ELLIPSE: 楕円
  • ksize: 構成要素の大きさ
  • anchor: 構成要素の行列のアンカー成分。デフォルトは (-1, -1)。(-1, -1) の場合は行列の中心。
import cv2
import numpy as np
import matplotlib.pyplot as plt

rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5));
cross_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5));
ellipse_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5));

print(rect_kernel)
print(cross_kernel)
print(ellipse_kernel)
[[1 1 1 1 1]
 [1 1 1 1 1]
 [1 1 1 1 1]
 [1 1 1 1 1]
 [1 1 1 1 1]]

[[0 0 1 0 0]
 [0 0 1 0 0]
 [1 1 1 1 1]
 [0 0 1 0 0]
 [0 0 1 0 0]]

[[0 0 1 0 0]
 [1 1 1 1 1]
 [1 1 1 1 1]
 [1 1 1 1 1]
 [0 0 1 0 0]]

画像の端のピクセル

画像の端のピクセルを走査する際に、構成要素のうち、画像のピクセルを参照できない部分が出てくる。
そのため、0で埋めるなどの方法で画像を拡張する方法が一般的に取られる。

f:id:nekobean:20180614192227p:plain

収縮

収縮 (erosion) 演算とは、走査対象のピクセルを近傍で最も小さい画素値に置き換えるモルフォロジー演算である。

\displaystyle dst(x, y) = \min_{element(x', y') \ne 0} src(x + x', y + y')

ただし、element(x', y') は構成要素の (x', y') 成分を表す。

cv2.erode() で収縮演算が行える。

dst = cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
  • src: 入力画像。
  • kernel: 構成要素。
  • anchor: アンカー。デフォルトは (-1, -1)。
  • iterations: 収縮処理を何回繰り返すか。デフォルトは 1。
  • borderType: 画像の端を拡張する方法。デフォルトは cv2.BORDER_CONSTANT。
  • borderValue: 画像の端を固定値で拡張する場合、その値。

f:id:nekobean:20180615013744j:plain

サンプル画像としてs使用する。

# 画像をグレースケール形式で読み込む。
img = cv2.imread("sign.jpg", 0)

# 画像を2値化する
_, img = cv2.threshold(img, 163, 255, cv2.THRESH_BINARY)

plt.imshow(img, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# 3x3 で全要素が1である構造要素を作成する。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3));

# 2値画像を収縮する。
dst = cv2.erode(img, kernel)

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

f:id:nekobean:20180615013832p:plain

普通に2値化した結果

f:id:nekobean:20180615014013p:plain

収縮を行った結果

f:id:nekobean:20180615014651p:plain

わかりづらいが、よく見ると細かいノイズ等が収縮により消えてよりはっきりとした2値化画像が得られた。

膨張

膨張 (dilation) 演算とは、走査対象のピクセルを近傍で最も大きい画素値に置き換えるモルフォロジー演算である。

\displaystyle dst(x, y) = \max_{element(x', y') \ne 0} src(x + x', y + y')

cv2.dilate() で膨張演算が行える。

dst = cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
  • src: 入力画像。
  • kernel: 構成要素。
  • anchor: アンカー。デフォルトは (-1, -1)。
  • iterations: 膨張処理を何回繰り返すか。デフォルトは 1。
  • borderType: 画像の端を拡張する方法。デフォルトは cv2.BORDER_CONSTANT。
  • borderValue: 画像の端を固定値で拡張する場合、その値。
# 画像をグレースケール形式で読み込む。
img = cv2.imread("sign.jpg", 0)

# 画像を2値化する
_, img = cv2.threshold(img, 163, 255, cv2.THRESH_BINARY)

plt.imshow(img, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# 3x3 で全要素が1である構造要素を作成する。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3));

# 2値画像を膨張する。
dst = cv2.dilate(img, kernel)

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

f:id:nekobean:20180615015337p:plain

膨張の結果

高度なモルフォロジー変換

収縮と膨張を組み合わせることで、より高度なモルフォロジー演算が行える。

cv2.morphologyEx() を使用する。

dst = cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
  • src: 入力画像。
  • op: モルフォロジー演算の種類。
  • kernel: 構成要素。
  • anchor: アンカー。デフォルトは (-1, -1)。
  • iterations: 膨張処理を何回繰り返すか。デフォルトは 1。
  • borderType: 画像の端を拡張する方法。デフォルトは cv2.BORDER_CONSTANT。
  • borderValue: 画像の端を固定値で拡張する場合、その値。

引数は op で演算の種類を指定する以外はerode()、dilate() と同じである。

演算の種類
  • cv::MORPH_ERODE: 収縮。erode() と同じ。
  • cv::MORPH_DILATE: 膨張。dilate() と同じ。
  • cv::MORPH_OPEN: オープニング (Opening)。収縮、膨張を順に行う演算。
    •  open(src, element) = dilate(erode(src, element))
  • cv::MORPH_CLOSE: クロージング (Closing)。膨張、収縮を順に行う演算。
    •  close(src, element) = erode(dilate(src, element))
  • cv::MORPH_GRADIENT: モルフォロジー勾配 (morphology gradient)。膨張した結果と収縮した結果の差をとる演算。
    •  morph_grad(src, element) = dilate(src, element) - erode(src, element)
  • cv::MORPH_TOPHAT: トップハット (top hat)。入力画像とオープニングの結果の差をとる演算。
    •  tophat(src, element) = src(src, element) - open(src, element)
  • cv::MORPH_BLACKHAT: ブラックハット (black hat)。入力画像とクロージングの結果の差をとる演算。
    •  blackhat(src, element) = src(src, element) - close(src, element)
  • cv::MORPH_HITMISS: ヒットミス (hit or miss)。
コード
# 画像をグレースケール形式で読み込む。
img = cv2.imread("sign.jpg", 0)

# 画像を2値化する
_, img = cv2.threshold(img, 163, 255, cv2.THRESH_BINARY)

plt.imshow(img, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# 3x3 で全要素が1である構造要素を作成する。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3));

# 収縮
dst = cv2.morphologyEx(img, cv2.MORPH_ERODE, kernel);

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# 膨張
dst = cv2.morphologyEx(img, cv2.MORPH_DILATE, kernel);

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# オープニング
dst = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel);

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# クロージング
dst = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel);

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# モルフォロジー勾配
dst = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel);

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# トップハット
dst = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel);

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# ブラックハット
dst = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel);

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

# ヒットミス
dst = cv2.morphologyEx(img, cv2.MORPH_HITMISS, kernel);

plt.imshow(dst, cmap=plt.cm.gray)
plt.axis('off')
plt.show()

f:id:nekobean:20180615022230p:plain

オープニング

f:id:nekobean:20180615022242p:plain

クロージング

f:id:nekobean:20180615022324p:plain

モルフォロジー勾配

f:id:nekobean:20180615022353p:plain

トップハット

f:id:nekobean:20180615022602p:plain

ブラックハット

f:id:nekobean:20180615022417p:plain

ヒットミス