HOWTO · Python
OpenCV 拡張
この記事では、OpenCV の画像内の特定のオブジェクトの境界からピクセルを追加および削除するために使用する、侵食や膨張などの形態変換の概念について説明します。
このページの内容
cv2.dilate を使うと、画像内の明るい前景領域を広げられます。OpenCV の膨張処理は各画素の近傍を調べ、構造化カーネルに従って前景を広げます。この処理はグレースケール画像と二値画像のどちらにも使えます。効果を見やすくするため、以下の例では明るい画素が1つだけあるグレースケール画像を使います。
OpenCVの膨張処理とは
モルフォロジー処理では、カーネル(構造化要素とも呼ばれます)を使って画像を処理します。二値画像では、前景の画素を白(255)、背景を黒(0)で表すのが一般的です。膨張処理では、カーネルの近傍にある最大値を各出力画素に設定します。そのため、明るい領域が広がり、近くにある明るい領域同士がつながることもあります。一方、収縮処理では近傍の最小値を使うため、明るい領域が縮小します。
近傍は画像上を移動します。この仕組みは2次元畳み込みで使う局所ウィンドウに似ていますが、膨張処理は重み付き和ではなく近傍の最大値を取ります。日本語のリンク先は用意されていないため、このリンクは英語ページを指しています。グレースケール画像でも、この最大値を取る処理によって局所的に明るくなります。値が2種類だけのマスクに限られる処理ではありません。
PythonとOpenCVで膨張処理を適用する
この自己完結型の例では、明るい画素が1つある小さなグレースケール画像と、3 × 3 の矩形近傍を使います。OpenCV と NumPy がインストールされていれば cv2.dilate を使います。どちらかが利用できない場合も、標準ライブラリによる代替処理で同じ近傍最大値の規則を適用するため、例を実行できます。
このセクションのコードと表示されるラベルや出力は英語のままです。
import importlib.util
image = [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 255, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
]
if importlib.util.find_spec("cv2") and importlib.util.find_spec("numpy"):
import cv2
import numpy as np
kernel = np.ones((3, 3), dtype=np.uint8)
dilated = cv2.dilate(np.array(image, dtype=np.uint8), kernel, iterations=1).tolist()
else:
dilated = [
[
max(image[y][x] for y in range(max(0, row - 1), min(5, row + 2))
for x in range(max(0, col - 1), min(5, col + 2)))
for col in range(5)
]
for row in range(5)
]
print(dilated)
[[0, 0, 0, 0, 0], [0, 255, 255, 255, 0], [0, 255, 255, 255, 0], [0, 255, 255, 255, 0], [0, 0, 0, 0, 0]]
ファイルを使う場合は、cv2.imread("input.png", cv2.IMREAD_GRAYSCALE) でグレースケール画像を読み込み、cv2.dilate を呼び出す前に戻り値が None でないことを確認します。パスが誤っている場合や、対応していない形式のファイルを指定した場合、戻り値が None になることがあります。その値にモルフォロジー処理を適用しても画像は得られません。後で比較する必要がある場合は、入力配列と出力配列を別々に保管してください。
カーネルと反復回数が膨張処理に与える影響
カーネルは近傍の範囲と形状を定めます。大きなカーネルはより広い範囲を調べるため、一般に明るい特徴をより遠くまで広げます。矩形カーネルは水平方向と垂直方向に均等に広げたい場合に便利です。楕円形や十字形のカーネルでは近傍の形状が変わります。影響させたい隙間や特徴に合わせて寸法と形状を選び、代表的な画像で結果を確認してください。
iterations 引数は膨張処理を繰り返す回数です。回数を増やすと拡張が重なり、狭い隙間が埋まったり、近くの領域がつながったりします。まずは反復を1回、小さなカーネルから始め、設定を一度に1つずつ調整してください。見た目の変化が大きければよい結果とは限りません。膨張しすぎると細部が失われることがあります。
膨張、収縮、オープニング、クロージングの違い
収縮処理は明るい前景領域を縮め、膨張処理は広げます。オープニングは収縮の後に膨張を行う処理で、カーネルと画像によっては、大きな構造を保ちながら小さな明るい点を取り除けます。クロージングは膨張の後に収縮を行い、明るい構造にある小さな暗い隙間を埋められます。どちらの処理も元の画素をすべて復元するとは限りません。効果は構造化要素と画像の特徴によって変わります。
小さな明るいノイズが問題ならオープニングを、明るい前景にある小さな暗い穴や切れ目が問題ならクロージングを使います。明るい背景に暗い物体がある場合は、まずどちらの極性を前景として扱うかを決めてください。同じ考え方で処理するには、画像を反転する必要があることもあります。
前景の極性と出力を理解する
OpenCV の基本的な膨張処理では、画素値が大きい領域が広がります。明るい前景と暗い背景からなる二値画像では、白い前景が近くの黒い背景へ広がります。白い紙に黒い文字がある写真では、明るい紙面の画素値が高いため、膨張処理によって紙面の領域が広がり、黒い文字が細く見えることがあります。そのため、「物体を大きくする」という直感と反対の結果に見える場合があります。
暗い文字自体を太くしたい場合は、グレースケール画像を反転してから膨張処理を行い、最後に結果をもう一度反転します。カラー画像では各チャンネルが画像データとして処理されます。色の濃淡ではなく形やラベルを前景として扱いたい場合は、単一チャンネルのグレースケール画像または二値マスクを使ってください。
よくある入力と表示の問題を確認する
処理の前に画像を読み込めていることと、カーネルの寸法が画像の細部に合っていることを確認します。幅の広いカーネルや多くの反復を使うと、別々の領域がつながったり、狭い隙間が消えたりすることがあります。出力に変化が見られない場合は、選択した前景が周囲より明るいことと、変化に関係する近傍の画素がカーネルの範囲に含まれていることを確認してください。カーネルの大きさと形状によって、まとめて扱う局所的な値が決まります。カーネルより大きな特徴にも作用することがあります。
OpenCV はカラー画像を BGR のチャンネル順で読み込みますが、Matplotlib は3チャンネル配列を RGB として解釈します。matplotlib.pyplot.imshow で表示する前に、BGR 画像を cv2.cvtColor(image, cv2.COLOR_BGR2RGB) で変換してください。グレースケール配列の場合は cmap="gray" を渡し、グレースケール用の表示にします。表示色の不一致は可視化に影響しますが、膨張処理の計算自体には影響しません。