Pynote

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

OpenCV - floodFill() を使った塗りつぶし、背景の透過など

概要

OpenCV の floodFill() を使って塗りつぶし、背景の透過を行う方法を紹介する。


floodFill()

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 matplotlib.pyplot as plt
import numpy as np

def imshow(img):
    if img.ndim == 2:
        plt.imshow(img, cmap='gray')
    else:
        plt.imshow(img[...,::-1])
    plt.axis('off')
    plt.show()

塗りつぶしを行う。

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

# 幅、高さとも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)
imshow(mask)
imshow(img)

floodFill() 後のマスク画像

結果画像

マスクを使用した例

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

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

# 幅、高さとも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)
imshow(mask)

floodFill() 前のマスク画像

retval, img, mask, rect = cv2.floodFill(
    img, mask, seedPoint=(60, 100), newVal=(0, 0, 255))

imshow(img)

結果画像

指定した色を透過する。

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

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

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

# 塗りつぶしを実行する。
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)

imshow(mask)

floodFill() 後のマスク画像

# マスクの値が 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('result.png', rgba)

結果画像