【Python・OpenCV】モルフォロジー演算による画像操作の応用(cv2.morphologyEx)

※当サイトではアフィリエイト広告を利用しています

Python プログラミング 画像処理

【Python・OpenCV】モルフォロジー演算による画像操作の応用(cv2.morphologyEx)

2024-05-09

はじめに

cv2.morphologyEx関数は、OpenCVの重要な機能の1つであり、医療画像処理や製造業での欠陥検出、自動運転技術などの画像処理タスクで活用されています。
この記事で解説したcv2.erode関数やcv2.dilate関数は単純な収縮・膨張を行うだけですが、cv2.morphologyEx関数は特殊なモルフォロジー演算を行うことも可能です。
本記事では、cv2.morphologyEx関数について説明します。

(広告) OpenCV関連書籍をAmazonで探す

cv2.morphologyEx関数

cv2.morphologyEx関数は、cv2.erode関数やcv2.dilate関数と異なり、単一の演算ではなく、開演算や閉演算などの複合演算を行うことができます。
cv2.erode関数は収縮演算、cv2.dilate関数は膨張演算を行いますが、cv2.morphologyEx関数を使えば、これらを組み合わせた演算を1つの関数で行うことができます。

また、cv2.morphologyEx関数には、グラディエント演算、トップハット演算、ブラックハット演算などの特殊な演算も用意されています。これらの演算は、cv2.erode関数やcv2.dilate関数だけでは実現できません。

cv2.morphologyEx関数は、以下のような用途で利用されています。

  • 画像のノイズ除去
  • 輪郭の抽出
  • 穴の除去
  • 細線の除去
  • エッジ検出
  • 特徴量抽出

例えば、開演算は小さなノイズを除去する目的で使用されます。一方、閉演算は小さな穴を埋める目的で使用されます。グラディエント演算は画像のエッジを強調する目的で使用されます。このように、状況に応じて適切な演算を選択することが重要です。

想定される利用シーンは以下の通りです。

  1. 医療画像処理
    • 肺がん検出システムにおける腫瘍の増強
    • MRI画像からの骨や軟部組織の抽出
    • X線画像からの骨の輪郭抽出
  2. 製造業での欠陥検出
    • 半導体ウェハーの欠陥検査
    • 印刷品質検査における印刷ムラの検出
    • 食品の外観検査
  3. 自動運転技術
    • 道路標識や横断歩道の検出
    • 車両や歩行者の検出
  4. 文字認識
    • 番号板の文字認識
    • 手書き文字認識
  5. 監視カメラシステム
    • 動体検出による無人監視
    • 顔認識システム

cv2.morphologyEx関数の引数と戻り値は下の通りです。

cv2.morphologyEx(入力画像, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])

引数

名称説明
入力画像(必須)グレースケールまたはカラーの入力画像データ。numpy.ndarrayオブジェクト。
op(必須)MorphTypesで定義される、演算の種類を指定します。
kernel(必須)モルフォロジー演算に使用するカーネル。cv2.getStructuringElement関数で生成されたものを指定することも可。
dst(オプション)結果がこの変数に格納されます。指定しない場合は、入力画像と同じサイズ・型で新しい配列が作成されて結果が格納され、戻り値として取得できます。
anchor(オプション)モルフォロジー演算の基準点を表すtuple。(デフォルト:(-1, -1))
iterations(オプション)モルフォロジー演算を繰り返す回数を正数(int)で指定します。(デフォルト:1)
borderType(オプション)BorderTypesで指定される、境界ピクセルの指定(デフォルト:cv2.BORDER_CONSTANT)
borderValue(オプション境界値として使用する値を表すscalar値。(デフォルト:cv2.morphologyDefaultBorderValue()
  • 引数opで指定された演算の種類に応じて、入力画像に対してオープニング演算、クロージング演算、グラディエント演算、トップハット演算、ブラックハット演算を行います。
  • kernelで指定された構造化要素によって、各演算の性質が決まります。
  • MorphTypesは下の表に簡単な説明を示します。
MorphTypes説明
cv2.MORPH_ERODEcv2.erode関数と同じ収縮処理を行います。指定したカーネルより小さい領域を削除します。
ノイズ除去や細線化に使われます。
cv2.MORPH_DILATEcv2.dilate関数と同じ膨張処理を行います。指定したカーネルに基づいて物体を膨らませます。
欠損部分の埋め立てや輪郭の太線化に使われます。
cv2.MORPH_OPENオープニング演算(収縮 → 膨張)を行います。
ノイズ除去や細かい突起物の除去に使われます。
cv2.MORPH_CLOSEクロージング演算(膨張 → 収縮)を行います。
小穴の埋め立てや輪郭の平滑化に使われます。
cv2.MORPH_GRADIENTグラディエント演算(膨張演算と収縮演算の差分)を行います。
物体の輪郭を強調(エッジ検出)する際に使われます。
cv2.MORPH_TOPHATトップハット演算(入力画像とオープニング演算の差分)を行います。
明るい領域を検出する際に使われます。
欠陥検出などに使われます。
cv2.MORPH_BLACKHATブラックハット演算(クロージング演算と入力画像の差分)を行います。
暗い領域を検出する際に使われます。
模様や穴の検出などに使われます。
cv2.MORPH_HITMISSHit-or-Miss演算を行います。
特定のパターンの検出に使われます。

戻り値

収縮処理を施した結果の画像データをnumpy.ndarrayオブジェクトで返します。

cv2.erode関数、cv2.dilate関数との比較

cv2.morphologyEx関数は、cv2.erode関数やcv2.dilate関数と異なり、単一の演算ではなく、開演算や閉演算などの複合演算を行うことができます。
cv2.erode関数は収縮演算、cv2.dilate関数は膨張演算を行いますが、cv2.morphologyEx関数を使えば、これらを組み合わせた演算を1つの関数で行うことができます。

また、グラディエント演算、トップハット演算、ブラックハット演算などの特殊な演算は、cv2.erode関数やcv2.dilate関数だけでは実現できません。

cv2.getStructuringElement関数

cv2.getStructuringElement関数は、cv2.morphologyEx関数で使用するカーネルを生成する関数です。カーネルのサイズと形状を指定することで、様々なカーネルを作ることができます。

cv2.getStructuringElement(shape, ksize[, anchor])

ポイント

生成されたカーネルはcv2.erode関数、cv2.dilate関数のカーネルとしても使うことができます。

引数

名称説明
shape(必須)MorphShapesで定義される、カーネルの形状を指定します。
ksize(必須)カーネルのサイズ
anchor(オプション)モルフォロジー演算の基準点を表すtuple。(デフォルト:(-1, -1))

MorphShapesは下の表に簡単な説明を示します。

MorphShapes説明
cv2.MORPH_RECT矩形カーネル
直線的な構造体や角の部分を保持したい場合に適しています。
直線的な輪郭の検出や、角の部分の強調に役立ちます。
建物や道路などの直線的な構造物の画像処理に向いています。
cv2.MORPH_CROSS十字型カーネル
直線的な構造体を強調したい場合に適しています。
細い線やひび割れなどの検出に役立ちます。
印刷物の文字認識や、路面のひび割れ検出などに向いています。
cv2.MORPH_ELLIPSE楕円型カーネル
楕円型の構造体や曲線を保持したい場合に適しています。
楕円型の物体の境界を滑らかにしたり、楕円型のノイズ除去に役立ちます。
生物学的な画像や、楕円型の構造物の画像処理に向いています。

戻り値

カーネルをnumpy.ndarrayオブジェクトで返します。

使い方

以下にサンプルコードを示します。

最初は、グラディエント演算のコードです。
グラディエント演算は、膨張演算と収縮演算の差分を取ることで、画像のエッジ部分を強調します

import cv2
import matplotlib.pyplot as plt

# 入力画像の読み込み
image = cv2.imread("image.jpg", cv2.IMREAD_GRAYSCALE)

# 構造化要素の定義
kernel_rect = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
kernel_cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5)) 
kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) 

# グラディエント演算
gradient_rect = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel_rect)
gradient_cross = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel_cross)
gradient_ellipse = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel_ellipse)

# 結果を表示
plt.rcParams["figure.figsize"] = [16,5]                                 # ウィンドウサイズを設定
title = "cv2.morphologyEx, op=cv2.MORPH_GRADIENT: codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定

plt.subplot(141)                                                        # 1行3列の1番目の領域にプロットを設定
plt.imshow(image, cmap='gray')                                          # 入力画像をグレースケールで表示
plt.title('Original Image')                                             # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(142)                                                        # 1行3列の2番目の領域にプロットを設定
plt.imshow(gradient_rect, cmap='gray')                                  # cv2.MORPH_GRADIENT, cv2.MORPH_RECTの結果
plt.title('cv2.MORPH_RECT')                                             # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(143)                                                        # 1行3列の2番目の領域にプロットを設定
plt.imshow(gradient_cross, cmap='gray')                                 # cv2.MORPH_GRADIENT, cv2.MORPH_CROSSの結果
plt.title('cv2.MORPH_CROSS')                                            # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(144)                                                        # 1行3列の2番目の領域にプロットを設定
plt.imshow(gradient_ellipse, cmap='gray')                               # cv2.MORPH_GRADIENT, cv2.MORPH_ELLIPSE
plt.title('cv2.MORPH_ELLIPSE')                                          # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.show()

カーネルの形状はcv2.MORPH_RECTcv2.MORPH_CROSScv2.MORPH_ELLIPSEの3種類について比較しています。
いずれも大きな差はありませんが、cv2.MORPH_CROSSの場合は細部の形状の再現性が高い様に見えます。

実行すると、以下のような画像が表示されます。

次に、トップハット演算、ブラックハット演算のコードです。
このサンプルコードでは自動車のナンバープレートの検出に挑戦してみます。

下を入力画像としています。

import cv2
import matplotlib.pyplot as plt

# 入力画像の読み込み
image = cv2.imread("image.jpg")

# グレースケールに変換
grayscale_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# グレースケールに変換
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# 構造化要素の定義
kernel_rect = cv2.getStructuringElement(cv2.MORPH_RECT, (21, 5))
kernel_cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (21, 5)) 
kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (21, 5)) 

# トップハット演算
tophat_rect = cv2.morphologyEx(grayscale_image, cv2.MORPH_TOPHAT, kernel_rect)
tophat_cross = cv2.morphologyEx(grayscale_image, cv2.MORPH_TOPHAT, kernel_cross)
tophat_ellipse = cv2.morphologyEx(grayscale_image, cv2.MORPH_TOPHAT, kernel_ellipse)

# ブラックハット演算
blackhat_rect = cv2.morphologyEx(grayscale_image, cv2.MORPH_BLACKHAT, kernel_rect)
blackhat_cross = cv2.morphologyEx(grayscale_image, cv2.MORPH_BLACKHAT, kernel_cross)
blackhat_ellipse = cv2.morphologyEx(grayscale_image, cv2.MORPH_BLACKHAT, kernel_ellipse)

# 結果を表示
plt.rcParams["figure.figsize"] = [16,9]                                 # ウィンドウサイズを設定
plt.rcParams["font.size"] = 9                                           # 文字サイズを設定
title = "cv2.morphologyEx, op=cv2.MORPH_TOPHAT,cv2.MORPH_BLACKHAT: codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定

plt.subplot(241)                                                        # 1行3列の1番目の領域にプロットを設定
plt.imshow(rgb_image)                                                   # 入力画像をグレースケールで表示
plt.title('Original Image')                                             # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(242)                                                        # 1行3列の2番目の領域にプロットを設定
plt.imshow(tophat_rect, cmap='gray')                                    # cv2.MORPH_TOPHAT, cv2.MORPH_RECTの結果
plt.title('cv2.MORPH_TOPHAT, cv2.MORPH_RECT')                           # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(243)                                                        # 1行3列の2番目の領域にプロットを設定
plt.imshow(tophat_cross, cmap='gray')                                   # cv2.MORPH_TOPHAT, cv2.MORPH_CROSSの結果
plt.title('cv2.MORPH_TOPHAT, cv2.MORPH_CROSS')                          # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(244)                                                        # 1行3列の2番目の領域にプロットを設定
plt.imshow(tophat_ellipse, cmap='gray')                                 # cv2.MORPH_TOPHAT, cv2.MORPH_ELLIPSEの結果
plt.title('cv2.MORPH_TOPHAT, cv2.MORPH_ELLIPSE')                        # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(246)                                                        # 1行3列の2番目の領域にプロットを設定
plt.imshow(blackhat_rect, cmap='gray')                                  # cv2.MORPH_BLACKHAT, cv2.MORPH_RECTの結果
plt.title('cv2.MORPH_BLACKHAT, cv2.MORPH_RECT')                         # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(247)                                                        # 1行3列の2番目の領域にプロットを設定
plt.imshow(blackhat_cross, cmap='gray')                                 # cv2.MORPH_BLACKHAT, cv2.MORPH_CROSSの結果
plt.title('cv2.MORPH_TOPHAT, cv2.MORPH_CROSS')                          # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(248)                                                        # 1行3列の2番目の領域にプロットを設定
plt.imshow(blackhat_ellipse, cmap='gray')                               # cv2.MORPH_BLACKHAT, cv2.MORPH_ELLIPSEの結果
plt.title('cv2.MORPH_TOPHAT, cv2.MORPH_ELLIPSE')                        # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.show()

上段の左の画像が入力画像です。上段の右側3つがトップハット演算、下段がブラックハット演算の結果です。
上のサンプルと同様に、カーネルの形状はcv2.MORPH_RECTcv2.MORPH_CROSScv2.MORPH_ELLIPSEの3種類について比較しています。
実行すると、以下のような画像が表示されます。

トップハット演算では白いナンバープレートが強調されていることがわかります。
各カーネルの形状がcv2.MORPH_RECTの場合はナンバープレート全体が強調されていますが、cv2.MORPH_CROSScv2.MORPH_ELLIPSEはナンバープレートの上下に暗い部分ができています。
ブラックハット演算では、いずれのカーネルの形状でもナンバープレートの黒い文字が綺麗に強調されていることがわかります。

Hit-or-Miss変換

Hit-or-Miss変換は、特定のパターンを検出するための画像処理手法です。
cv2.morphologyEx関数の引数opでMorphTypesのcv2.MORPH_HITMISSを設定し、カーネルに検出したい特定のパターンを指定することで適用することができます。

以下にサンプルコードを示します。

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 入力画像を作成
image = np.array((
 [255,   0,   0,   0,   0,   0,   0,   0],
 [  0,   0, 255,   0,   0,   0,   0,   0],
 [  0, 255, 255, 255, 255, 255,   0, 255],
 [  0,   0,   0, 255,   0,   0,   0, 255],
 [  0,   0, 255,   0,   0,   0,   0,   0],
 [  0, 255, 255,   0,   0, 255,   0,   0],
 [255,   0, 255,   0, 255, 255, 255,   0],
 [  0,   0, 255,   0,   0, 255,   0,   0]), dtype=np.uint8)

# カーネルの定義
# 検出したいパターンを0、そうでないパターンを1で指定
kernel = np.array([[0, 1, 0],
                   [1, 1, 1],
                   [0, 1, 0]], dtype=np.uint8)

# Hit-or-Miss変換の実行
output = cv2.morphologyEx(image, cv2.MORPH_HITMISS, kernel)

# 結果を表示
plt.rcParams["figure.figsize"] = [12,6]                                 # ウィンドウサイズを設定
title = "cv2.morphologyEx, op=cv2.MORPH_HITMISS: codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定

plt.subplot(121)                                                        # 1行2列の1番目の領域にプロットを設定
plt.imshow(image, cmap='gray')                                          # 入力画像をグレースケールで表示
plt.title('Input Image')                                                # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.subplot(122)                                                        # 1行2列の2番目の領域にプロットを設定
plt.imshow(output, cmap='gray')                                         # cv2.MORPH_HITMISSの結果
plt.title('Output Image')                                               # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す

plt.show()

このコードでは、まず入力画像をグレースケールで読み込みます。
次に検出したいパターンを”0”、”1”で表すカーネルを定義します。
このサンプルコードの例ではカーネルが以下のパターンを検出します。

つまり、中心の画素値と、その上下左右が"1"、それ以外が"0"という構造を持つパターンを検出します。

最後に、cv2.morphologyEx関数にcv2.MORPH_HITMISSと定義したカーネルを渡すことで、ヒットアンドミス変換が実行されます。
出力画像には、入力画像中で指定したパターンが検出された箇所が白(255)、それ以外が黒(0)で表示されます。

例えば、各画素が下の左側の様な白黒画像を入力した場合、下の右の結果が出力されます。

実行すると、以下のような画像が表示されます。

ヒットアンドミス変換は、検出したいパターンに合わせてカーネルを設計することがポイントになります。

おわりに

cv2.morphologyEx関数について解説しました。
cv2.morphologyEx関数は、カーネルを使った様々な演算を行うことができます。
Hit-or-Miss変換の様な画像処理も容易に実現できる強力な関数です。
状況に応じて適切な演算とカーネルのサイズ、形状の選択は試行錯誤しながら見つけていくことが重要です。
適切に使いこなすことで高度な画像処理が可能になります。

ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。

参考リンク

■(広告)OpenCVの参考書としてどうぞ!■

-Python, プログラミング, 画像処理
-