pythonのOpenCVでfloodFillを使ってみたよ


やあ子供たち。花粉症に悩まされていないか。くしゃみばかりしてないで、マスクをするとか、薬を飲むとか、ちゃんとやっているか対策。
今日はOpenCVの連続領域塗りつぶし用関数floodFill()のお話だよ。floodFill()というのは、昔のPC88のPaint()文なんかに似た動作をする、塗りつぶし機能だよ。同関数は本来のC++実装では、マスクというものを指定するバージョンと指定しなくてもいいバージョンとがあるのだけれども、どうやら現状の python による実装では、マスク指定のものしかラップされてないみたいなので、マスクの意味もちゃんと理解して、これを作成し、引数として渡してあげないと、floodFill()は使えないぞ。今日の流れとしては、MatPlotLibで作成した上のような画像内の指定箇所からfloodFill処理を起動することを考えていくよ。
このマスクっていうのはどういう意味というかまどう準備すればいいのかというと、本来塗りつぶしたい画像があって、それとは別に、マスク画像というものを作ってあげるんだね。しかもこのマスク画像は本来の画像のサイズに対し、上下左右とも、+1ピクセル太らせてあげなくてはならないんだ。マスク画像というのは、いわば垣根だね。塗りつぶしには、塗りつぶしを開始する画像上の位置を指定してあげるのだけれど、この開始点からh始めてどんどん領域が広がっていくが、対応するマスク画像上の位置に、白ピクセルがあれば、それは垣根なのでそれ以上塗りつぶしにいかないということがあるんだね。
さてさて、floodFillの最小限サンプルとして以下コードを公開するぞ。

import numpy as np
import matplotlib.pyplot as plt
import cv2

#必ずUTF-8にて保存のこと

def main():

  # *** 閉曲線の作成 *** 
  x=[]
  y=[]
  for itht in range(361):
    tht = itht*np.pi/180.0
    num_polygon = 7
    r = 2+1*np.sin(tht*num_polygon)
    x.append(r*np.cos(tht))
    y.append(r*np.sin(tht))
  # *** プロット処理 *** 
  plt.figure( figsize=(3,3) )
  plt.plot( x, y )
  # *** プロット画像の保存 ***
  plt.savefig("tak.png")
  # *** 画像をOpenCV画像としてロード *** 
  img = cv2.imread("tak.png")
  # *** エッジ検出画像をマスク画像として用意 ***
  mask = cv2.Canny(img, 100, 200) 
  # *** マスク画像の拡張。(画像の上下左右に1ラインずつ拡張)
  mask = cv2.copyMakeBorder(mask, 1, 1, 1, 1, 
    cv2.BORDER_REPLICATE)
  # *** 領域塗りつぶしと結果の表示
  cv2.floodFill( img, mask, (120,120), (0,255,255))
  sz = img.shape[:2]
  cv2.floodFill( img, mask, (int(sz[0]/2), int(sz[1]/2)), (0,255,0))
  cv2.imshow("canny",mask)
  cv2.imshow("flood",img)
  cv2.waitKey()

if __name__=='__main__':
  main()

ま途中画像ファイルを介する必要があるのか?というところだけど、まメモリ上でやりとりすればわざわざファイルに書き出さなくてもいんじゃねみたいは方法がわかる人がいれば教えてくれ。今回はおじさんはファイルを介すという安直なソリューションを選んだわけだけどな。
さてさて、このコードがいろいろ備忘録になるわけですよ。matplotlibと、OpenCV、それぞれの勉強になるよな。例えばこの中には、
(MATPLOTLIB)

  • 曲線形状の作成とプロットのやり方
  • 実際のプロット処理
  • 画像の保存(MATPLOTLIB)

(OPENCV)

  • 画像のロード
  • エッジ抽出(CANNY)
  • 画像を太らせる方法(cv2.copyMakdBorder)
  • 実際の領域塗りつぶし実行と結果画像の表示

といった具合にだ。だから記事にしたんだよ。おじさん自分のためにな。実行結果の画面が以下のような感じだよ。

はい、ちゃんとこの葉っぱのような形状の内側を緑で、そして、その外側を黄色で塗り分けることができたね。
さて今日はもうこの辺で。チャオ!