OpenCV – floodFill で領域を抽出し、塗りつぶす方法

目次

概要

OpenCV の cv2.floodFill() は指定した点に似た画素値を持つ領域を抽出し、塗りつぶすことができる関数です。この関数の本質は領域の抽出であるため、塗りつぶす代わりに、セグメンテーションにも活用できます。

cv2.floodFill

retval, image, mask, rect = cv2.floodFill(
    image, mask, seedPoint, newVal[, loDiff[, upDiff[, flags]]])

公式リファレンス: cv2.floodFill

引数
名前 デフォルト値
image ndarray
1または3チャンネルで型が 8bit または浮動小数点数の画像
mask ndarray
image に1画素分パディングした型が 8bit の画像
seedPoint tuple of 2 ints
塗りつぶしを開始する画素
newVal int / tuple of ints
塗りつぶし後の値
loDiff int / tuple of ints 0 / (0, 0, 0)
連結成分を探す際の下限
upDiff int / tuple of ints 0 / (0, 0, 0)
連結成分を探す際の上限
flags int 4
塗りつぶし方法などを指定するフラグ
返り値
名前 説明
retval 塗りつぶした画素数
image 塗りつぶし後の画像
mask マスク
rect 連結成分に外接する長方形

塗りつぶす基準

seedPoint に指定した画素と画素値の差が指定範囲内の画素を同じグループ (連結成分) と見なして塗りつぶしを行います。flags 引数で cv2.FLOODFILL_FIXED_RANGE を指定したかどうかで連結成分を探す方法が異なります。

  • cv2.FLOODFILL_FIXED_RANGE を指定した場合
    cv2.FLOODFILL_FIXED_RANGE を指定した場合、seedPoint との画素値の差が指定範囲内の画素は連結成分に追加されます。この場合、画素値が以下の範囲にある画素が連結成分として追加されます。

    $$ \text{src}({seedPoint}_x, {seedPoint}_y) − loDiff \le \text{src}(x, y) \le \text{src}({seedPoint}_x, {seedPoint}_y) + upDiff $$

  • cv2.FLOODFILL_FIXED_RANGE を指定しない場合
    cv2.FLOODFILL_FIXED_RANGE を指定しない場合、すでに見つかっている隣の連結成分との画素値の差が指定範囲内の画素は連結成分に追加します。

    $$ \text{src}(x’, y’) – loDiff \le \text{src}(x, y) \le \text{src}(x’, y’) + upDiff $$

塗りつぶし方法

まとめると、

  • cv2.FLOODFILL_FIXED_RANGE を指定した場合: seedPoint で指定した色と似た色は同じ領域と判定
  • cv2.FLOODFILL_FIXED_RANGE を指定しない場合: seedPoint から探索を開始し、輝度勾配が閾値未満は同じ領域と判定

flags の指定方法

flags 引数はビット列で塗りつぶしの方法を設定します。各ビットの設定によって、cv2.floodFill() 関数の動作が制御されます。

ビット 意味 許容値 デフォルト
1 ~ 8 近傍の種類 (4: 4 近傍、8: 8 近傍) {4, 8} 4
9 ~ 16 塗りつぶした画素に対応したマスクの位置の値も変更されるが、その値 [1, 255] 1
17 走査する際に塗りつぶすかどうかを判定する基準 {0, 1} 1
18 1 の場合、マスクのみ変更し、入力画像の塗りつぶしは行わない {0, 1} 0

フラグ指定方法の例

以下のコード例では、4 連結、マスクの値を 255 に設定し、cv2.FLOODFILL_FIXED_RANGEcv2.FLOODFILL_MASK_ONLY オプションを有効にしています。

flag = 4 | 255 << 8 | cv2.FLOODFILL_FIXED_RANGE | cv2.FLOODFILL_MASK_ONLY

画像内の特定の領域の塗りつぶす

sample.jpg

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


def imshow(img):
    """ndarray 配列をインラインで Notebook 上に表示する。"""
    ret, encoded = cv2.imencode(".jpg", img)
    display(Image(encoded))
In [2]:
# 画像を読み込む。
img = cv2.imread("sample.jpg")

# 幅、高さとも2ピクセルずつ大きいサイズのマスクを作成する。
img_h, img_w = img.shape[:2]
mask = np.zeros((img_h + 2, img_w + 2), dtype=np.uint8)

# 塗りつぶす。
retval, img, mask, rect = cv2.floodFill(
    img,
    mask,
    seedPoint=(100, 100),  # 塗りつぶしの開始点
    newVal=(0, 0, 255),  # 塗りつぶす色
    loDiff=(20, 20, 20),  # 画素値の差が -20 〜 20 の範囲は同じ領域と判断する
    upDiff=(20, 20, 20),
    flags=4 | 255 << 8,  # 4 連結、マスクの値を 255
)
print("retval", retval)  # retval 6339
print("rect", rect)  # rect (49, 42, 90, 90)
imshow(mask)
imshow(img)
retval 6146
rect (50, 43, 88, 88)

青色の円が赤色に塗りつぶされました。マスクには塗りつぶした領域が 255 で表されています。

マスク画像を使用する

mask の画素値が 0 のピクセルのみが塗りつぶしの対象になります。塗りつぶしの対象から外したい画素は、mask に 0 以外の値を設定しておきます。

以下の例では、cv2.circle() を使用してマスクに白い円を描画することで、その部分を塗りつぶしの対象から外しています。

In [3]:
# 画像を読み込む。
img = cv2.imread("sample.jpg")

# 幅、高さとも2ピクセルずつ大きいサイズのマスクを作成する。
img_h, img_w = img.shape[:2]
mask = np.zeros((img_h + 2, img_w + 2), dtype=np.uint8)
cv2.circle(mask, (150, 100), 70, color=100, thickness=-1)

# 塗りつぶす。
retval, img, mask, rect = cv2.floodFill(
    img,
    mask,
    seedPoint=(60, 100),
    newVal=(0, 0, 255),
    loDiff=(20, 20, 20),
    upDiff=(20, 20, 20),
    flags=4 | 255 << 8,
)

imshow(mask)
imshow(img)

cv2.floodFill() に渡したマスクの灰色の画素(255 以外の値)は塗りつぶされず、元の青色のまま残っていることがわかります。

指定した色を透過する

塗りつぶしの代わりに、背景の 1 点を指定して、その色に似た画素を cv2.floodFill() の結果として受け取ることができます。そのためには、flags に cv2.FLOODFILL_MASK_ONLY フラグを指定します。

In [4]:
# 画像を読み込む。
img = cv2.imread("sample.jpg")

# 幅、高さとも2ピクセルずつ大きいサイズのマスクを作成する。
img_h, img_w = img.shape[:2]
mask = np.zeros((img_h + 2, img_w + 2), dtype=np.uint8)

# 塗りつぶしを実行する。
flags = 4 | 255 << 8 | cv2.FLOODFILL_MASK_ONLY
cv2.floodFill(
    img,
    mask,
    seedPoint=(2, 2),
    newVal=(0, 0, 255),
    loDiff=(20, 20, 20),
    upDiff=(20, 20, 20),
    flags=flags,
)

# マスク作成時に追加した周囲1ピクセルは除く
mask = mask[1:-1, 1:-1]

# アルファチャンネル追加する。
dst = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)

# マスクの値が 255 のアルファチャンネルは0にする。
dst[mask == 255] = 0
cv2.imwrite("result.png", dst)
True

コメント

コメントする

目次