【opencv | python】2値化領域全てのホール(穴・中空部)を一度に塗りつぶす(埋める)方法

本記事の適用シーン

複雑な2値化画像のそれぞれの領域内の穴を一気に塗りつぶしたいとき

塗りつぶしの際にはcv2.floodFillという関数が用意されているのですが、windowsの標準のペイントツールと同じく

指定した座標から塗りつぶしを行い同じ色のピクセルのみ色を変更する

という処理となっていますので意外と使いどころが難しいです。

従いまして、以下のような画像の場合には白い線で領域が分断されているためcv2.floodFillを使ってやっても簡単には塗りつぶしができませんが、今回紹介する方法ですと、

どのような領域の形状、数、においても対応可能です。

画像が変わっても同じ処理で対応可能となりますので汎用性も高いです。

塗りつぶし処理の前後

概要

概要は以下のようになります。

ポイントとしては直接塗りつぶしを行うのではなくて、2値化した後に

一度輪郭点を抽出してから、再度塗りつぶし多角形を描写することで穴埋めを実現しています。

opencvの輪郭抽出は直線となる部分のみ輪郭点のとして抽出するため、多角形として描写することで元の輪郭と同じ曲線を表現することができます。

実際に手で塗りつぶした画像と今回紹介する方法で塗りつぶした画像は全く同じものとなります。

また、画像によっては2値化の閾値や cv2.adaptiveThresholdを使い、穴埋め可能なような2値化画像を作ることも大事になってきます。

紹介する塗りつぶし処理の流れ

全体のコードは以下のようになります。

参考にjupyter notebookのデータを添付しておきます。

#必要なライブラリの定義
import cv2
import numpy as np

#画像の読み込み
img_path = "test.bmp"
img = cv2.imread(img_path)

#2値化するためにグレー画像化
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  

#2値化
ret, region = cv2.threshold(img_gray, 1, 255, cv2.THRESH_BINARY)

#輪郭データの取得
contours,_ = cv2.findContours(region, 1, 2)

#塗りつぶし多角形を描写するためのゼロ埋め配列定義
#point:opencvの関数で扱えるように型をuint8で指定!
zero_img = np.zeros([region.shape[0], region.shape[1]], dtype="uint8")

#全ての輪郭座標配列を使って塗りつぶし多角形を描写
for p in contours:
    cv2.fillPoly(zero_img, [p], (255, 255, 255))

大事なポイントを説明します。

まずは輪郭データの取得のコードです。

#輪郭データの取得
contours,_ = cv2.findContours(region, 1, 2)

これはドキュメントを参考にしていただければと思いますが、

この処理を行うと自動で2値化した画像から各領域の輪郭の座標numpy配列データが入ったリストが取得できます。

以下がイメージ図となります。

輪郭抽出の出力のイメージ図

リストが取得できますので、以下のようにnp.zerosで真っ黒(=全て0)の画像を作成し、

for文で1つ1つ輪郭の座標numpy配列データを取り出して、cv2.fillPolyを使い白い(=255)多角形塗りつぶし図形を描写すれば良いです。

1つ注意点としてはnp.zerosで画像を作成する際にopencvで扱えるように数値の型を「uint8」にする必要があります。

#塗りつぶし多角形を描写するためのゼロ埋め配列定義
#point:opencvの関数で扱えるように型をuint8で指定!
zero_img = np.zeros([region.shape[0], region.shape[1]], dtype="uint8")

#全ての輪郭座標配列を使って塗りつぶし多角形を描写
for p in contours:
    cv2.fillPoly(zero_img, [p], (255, 255, 255))

最後に

今回は領域の塗りつぶす方法について紹介しました。

使いどころは結構あると思いますので、参考になったら幸いです。

コメント

タイトルとURLをコピーしました