ブンバボーンな毎日

RubyやPython, JavaScriptなど勉強したことなど、IT関連の記事を書いています

python画像処理8 膨張と縮小

こんにちは、エンジニアのさもです

今回は画像の膨張、縮小化を実装してみたいと思います。

目次

はじめに

今回対象とする画像は白黒画像です。

膨張化とは、白黒画像の白色(背景でない部分)部分を膨張させ、黒色(背景)部分の面積を小さくすることです。

逆に、縮小化とは、白色部分を縮小させ、黒色部分の面積を大きくすることです。

こんなことをして何がうれしいかというと、膨張と縮小を組み合わせて適応することで、ノイズやゴミの影を消すことが出来ます。

例えば以下のような画像を見てください

f:id:s-uotani-zetakansu:20170911163528p:plain

白色の部分にゴミのようなノイズがたくさん入っています。

この画像へ膨張・縮小を行ってみると以下のようになります

f:id:s-uotani-zetakansu:20170911173334p:plain

ふちが少し変になってしまいましたが、ノイズがかなり消えています。

今回はこんなことをしていきます。

膨張

といってもアルゴリズムはとても簡単です。

あるピクセルに注目して、そのピクセルと8近傍が、1ピクセルでも白を含んでいれば、注目ピクセルを白にします。

以下実装例です

from PIL import Image, ImageDraw
import numpy as np

def dilation(img):
  w, h = img.size
  image_pixcels = np.array(list(img.getdata())).reshape(h, w,)
  filtered_pixcels = np.zeros((h, w,))
  for x in range(w):
    for y in range(h):
      x1 = max(0, x - 1)
      x2 = min(x + 1, w -1)
      y1 = max(0, y - 1)
      y2 = min(y + 1, h - 1)
      if (image_pixcels[y1:y2 + 1, x1:x2 + 1] == 255).any():
        filtered_pixcels[y][x] = 255
      else:
        filtered_pixcels[y][x] = 0
  filtered_img = Image.new('L', (w, h))
  filtered_img.putdata(filtered_pixcels.reshape(w * h, 1))
  return filtered_img

filename = "e.png"
img = Image.open("../images/" + filename).convert("L")

img = dilation(img)
img.save("./dil_" + filename)

上で表示したノイズ入りの「絵」の画像へ適応すると以下のようになります

f:id:s-uotani-zetakansu:20170911172454p:plain

まだ少しノイズが残っていますね。

もう一度適応するとこんな感じです

f:id:s-uotani-zetakansu:20170911172606p:plain

ノイズは消えました!

絵の部分がかなり太くなったので戻すのと、今度は白色のノイズを消すために、縮小を行います

縮小

膨張の全く逆になります

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

from PIL import Image, ImageDraw
import numpy as np

def erosion(img):
  w, h = img.size
  image_pixcels = np.array(list(img.getdata())).reshape(h, w, 1)
  filtered_pixcels = np.zeros((h, w,))
  for x in range(w):
    for y in range(h):
      x1 = max(0, x - 1)
      x2 = min(x + 1, w -1)
      y1 = max(0, y - 1)
      y2 = min(y + 1, h - 1)
      if (image_pixcels[y1:y2 + 1, x1:x2 + 1] == 0).any():
        filtered_pixcels[y][x] = 0
      else:
        filtered_pixcels[y][x] = 255
  filtered_img = Image.new('L', (w, h))
  filtered_img.putdata(filtered_pixcels.reshape(w * h, 1))
  return filtered_img

filename = "e.png"
img = Image.open("../images/" + filename).convert("L")

img = erosion(img)
img.save("./dil_" + filename)

違う部分は、メソッド名と、以下のところです

      if (image_pixcels[y1:y2 + 1, x1:x2 + 1] == 255).any():
        filtered_pixcels[y][x] = 255
      else:
        filtered_pixcels[y][x] = 0

ちょうど255と0が入れ替わっています。

dilation(img)の引数に0か255を渡すようにすればメソッドは1つにまとめることも出来ますね。

膨張2回のあとに縮小を2回行うと以下のようになります

f:id:s-uotani-zetakansu:20170911173232p:plain

適応回数を変えてみる

膨張4回、縮小4回

f:id:s-uotani-zetakansu:20170911173334p:plain

すっかりきれいになりました

縮小2回、膨張2回

f:id:s-uotani-zetakansu:20170911173818p:plain

・・・。順番が逆になるとだめなんですね

膨張、縮小を交互に4回繰り返す

f:id:s-uotani-zetakansu:20170911174106p:plain

変わってないですね。

最後に

最後までお読みいただきありがとうございました。

アルゴリズムは凄く簡単ですが、思っていた以上に綺麗になって面白かったです。

読者登録をしていただけると、ブログを続ける励みになりますので、よろしくお願いします。