Pynote

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

OpenCV - floodFill で塗りつぶし、背景の透過を行う方法

floodFill

cv2.floodFill() で指定した点と似た色の領域を塗りつぶすことができる。

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

引数

  • image: 1または3チャンネルで型が 8bit または浮動小数点数の画像。
    • flags 引数に FLOODFILL_MASK_ONLY フラグが指定されていない場合、inplace で変更される。
  • mask: image に1画素分パディングした型が 8bit の画像。
  • seedPoint: 開始する点
  • newVal: 連結成分を置き換える点
  • loDiff: 連結成分を探す際の下限
  • upDiff: 連結成分を探す際の上限
  • flags: フラグ

返り値

  • retval:
  • image: 塗りつぶし後の画像
  • mask: マスク
  • rect: 連結成分に外接する長方形

flags の指定方法

  • 1~8bit:
    • 4: 4近傍
    • 8: 8近傍
  • 9~16bit: 塗りつぶした画素に対応したマスクの位置の値も変更されるが、その値。1~255で指定する。デフォルトは1。
  • 17bit: 走査する際に塗りつぶすかどうか判定する基準。fixed range の場合、seedPoint の画素値との比較になる。指定しない場合は、近傍の画素値との比較になる。
    • cv2.FLOODFILL_FIXED_RANGE で定義されている。
  • 18bit: 指定した場合は、マスクのみ変更し、入力画像の塗りつぶしは行わない。
    • cv2.FLOODFILL_MASK_ONLY で定義されている。

loDiff、upDiff の意味

fixed range でない場合

src(x', y') - loDiff \le src(x, y) \le src(x', y') + upDiff

fixed range の場合

src(seedPoint.x, seedPoint.y) - loDiff \le src(x, y) \le src(seedPoint.x,seedPoint.y) + upDiff

サンプルコード

入力画像

塗りつぶしを行う。

import cv2
import numpy as np

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

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

retval, img, mask, rect = cv2.floodFill(
    img, mask, seedPoint=(100, 100), newVal=(0, 0, 255)
)
print("retval", retval)  # retval 6339
print("rect", rect)  # rect (49, 42, 90, 90)

# 結果を保存する。
cv2.imwrite("mask.png", mask)
cv2.imwrite("img.png", img)

floodFill 後のマスク画像

出力画像

マスクを使用した例

mask の画素値が0のピクセルのみ走査対象なので、塗りつぶしの対象外としたい画素は0以外の値を mask に設定しておけばよい。
下記の例では、マスクに circle() で白い円を描画することで、その部分を塗りつぶしの対象から外している。

import cv2
import numpy as np

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

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

# 幅、高さとも2ピクセルずつ大きいサイズのマスクを作成する。
retval, img, mask, rect = cv2.floodFill(
    img, mask, seedPoint=(60, 100), newVal=(0, 0, 255)
)
print("retval", retval)  # retval 6339
print("rect", rect)  # rect (49, 42, 90, 90)

# 結果を保存する。
cv2.imwrite("mask.png", mask)
cv2.imwrite("img.png", img)

floodFill 前のマスク画像

出力画像

指定した色を透過する。

背景を透過したい場合などは下記の手順で行う。

import cv2
import numpy as np

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

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

# 塗りつぶしを実行する。
flags = 4 | 255 << 8 | cv2.FLOODFILL_MASK_ONLY
print(bin(flags))  # 0b101111111100000100

cv2.floodFill(img, mask, seedPoint=(2, 2), newVal=(0, 0, 255), flags=flags)
print("retval", retval)  # retval 6339
print("rect", rect)  # rect (49, 42, 90, 90)

# マスクの値が 255 の画素に対応する画素の透過度を 0 (透明) にする。
rgba = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)  # アルファチャンネル追加
mask = mask[1:-1, 1:-1]  # マスク作成時に追加した周囲1ピクセルは除く
rgba[mask == 255] = 0  # マスクの値が 255 の画素は (0, 0, 0, 0) にする。

# 結果を保存する。
cv2.imwrite("mask.png", mask)
cv2.imwrite("result.png", rgba)

floodFill() 後のマスク画像

結果画像