canny edge detection : 画像処理によるエッジ検出 (python)

こんにちは.

今日は画像処理に関して,エッジ抽出法であるcannyのエッジ検出法を数か月前に実装したので紹介します.

想像よりも鮮明にエッジ検出ができるため,結構面白いです.笑

openCV などでも簡単にできますが,基本的には足し算掛け算でできるので numpy の配列を用いました.

データ解析でブラックボックスのソフトを使用することが多い今日ですが,原理を1から勉強するのも重要でありますね.

 

環境は以下の通りです.

win 8

python 3.5

anaconda 3-4.0.0

 

今回は python を用い,画像の配列化,配列の画像化は PIL というライブラリを使用しています.

PIL はターミナル上からインストールできます.

 

canny edge detection は以下の流れで行います.

  1. original image のノイズを減らすためガウシアンフィルタを施し平滑化.
  2. sobel フィルタを用い,x, y 方向それぞれに画素の一次微分を行う(勾配を計算する).
  3. grd_x, grd_y の勾配の合計をその画素の勾配の大きさとして保存.
  4. atan = (grd_y / grd_x) を求め,エッジの方向を決定し,細線化を行う.
  5. 閾値を用いて場合分けを行いエッジ判定し,エッジを検出する.

 

以下 python (Jupyter notebook 上) でのコードです.

グレースケールの画像での処理になります.

閾値の上限下限を設定することで結果が変わります.

 

インデント左揃えになっているのはご容赦下さい...

間違いなどあれば指摘いただけますと幸いです.

 

###########################################################
################### canny edge detection ######################
###########################################################

from PIL import Image
import numpy as np
import os
import math

##### ファイルのパス #####

file_path = '(画像があるディレクトリのパス)'


##### ディレクトリ作成 #####

if (os.path.exists(filter_path) == False):
os.mkdir(filter_path)
if (os.path.exists(grd_path) == False):
os.mkdir(grd_path)
if (os.path.exists(canny_path) == False):
os.mkdir(canny_path)

###################################################################

##### Im_originalに画像を取り込み,array_original(配列)に輝度値を配列として保存#####


im_original = Image.open(file_path + '\\(画像ファイル名, tif, png など)')

array_original = np.asarray(im_original)
width, height = im_original.size

 

###################################################################

##### 勾配強さを求める #####

##### 勾配をそれぞれ grd_x, grd_y とする #####
grd_x = np.zeros(height,width))
grd_y = np.zeros(height,width))
grd_sum = np.zeros(height,width))



##### sobel filter によるエッジ強さ計算 #####
for vrtcl in range (1, height-1):
for hrzn in range (1, width-1):
grd_x_array = np.array([array_original[vrtcl-1,hrzn-1]*(-1),array_original[vrtcl-1,hrzn]*(0),array_original[vrtcl-1,hrzn+1]*(1), array_original[vrtcl,hrzn-1]*(-2),array_original[vrtcl,hrzn]*(0),array_original[vrtcl,hrzn+1]*(2),array_original[vrtcl+1,hrzn-1]*(-1),array_original[vrtcl+1,hrzn]*(0),array_original[vrtcl+1,hrzn+1]*(1)])
g_x = np.sum(grd_x_array)
grd_y_array = np.array([array_original[vrtcl-1,hrzn-1]*(1),array_original[vrtcl-1,hrzn]*(2),array_original[vrtcl-1,hrzn+1]*(1),array_original[vrtcl,hrzn-1]*(0),array_original[vrtcl,hrzn]*(0),array_original[vrtcl,hrzn+1]*(0),array_original[vrtcl+1,hrzn-1]*(-1),array_original[vrtcl+1,hrzn]*(-2),array_original[vrtcl+1,hrzn+1]*(-1)])
g_y = np.sum(grd_y_array)
grd_x[vrtcl,hrzn] = (int)(g_x)
grd_y[vrtcl,hrzn] = (int)(g_y)
grd_sum[vrtcl,hrzn] = (grd_x[vrtcl,hrzn]**2+grd_y[vrtcl,hrzn]**2)**0.5

##### image にするために輝度値を 0~255 に収める #####
grd_sum = np.array(grd_sum, dtype = 'float')
grd_sum = grd_sum * 255 / np.max(grd_sum)

 

##### 勾配画像の保存 (grd ディレクトリ) #####
img_grd.save(grd_path + '\\grd.tif')

 


###### gradの方向より,非極大抑制を行う(細線化) #######

for vrtcl in range (1, height-1):
for hrzn in range (1, width-1):
grd_angl = math.atan2(g_y,g_x)
##### gradの方向の決定
##### 縦の場合
if (grd_angl>=(3/8*math.pi) or grd_angl<=(-3/8*math.pi)):
if grd_sum[vrtcl,hrzn]>(grd_sum[vrtcl-1,hrzn] and grd_sum[vrtcl+1,hrzn]):
grd_sum[vrtcl,hrzn]=grd_sum[vrtcl,hrzn]
else:
grd_sum[vrtcl,hrzn]=0
##### 横の場合
if (grd_angl>=(-1/8*math.pi)and grd_angl<=(1/8*math.pi)):
if grd_sum[vrtcl,hrzn]>(grd_sum[vrtcl,hrzn-1] and grd_sum[vrtcl,hrzn+1]):
grd_sum[vrtcl,hrzn]=grd_sum[vrtcl,hrzn]
else:
grd_sum[vrtcl,hrzn]=0
##### 斜めの場合(右上)
if (grd_angl>=(1/8*math.pi) or grd_angl<=(3/8*math.pi)):
if grd_sum[vrtcl,hrzn]>(grd_sum[vrtcl+1,hrzn-1] and grd_sum[vrtcl+1,hrzn-1]):
grd_sum[vrtcl,hrzn]=grd_sum[vrtcl,hrzn]
else:
grd_sum[vrtcl,hrzn]=0
##### 斜めの場合(左上)
if (grd_angl>=(-3/8*math.pi) or grd_angl<=(-1/8*math.pi)):
if grd_sum[vrtcl,hrzn]>(grd_sum[vrtcl+1,hrzn+1] and grd_sum[vrtcl-1,hrzn-1]):
grd_sum[vrtcl,hrzn]=grd_sum[vrtcl,hrzn]
else:
grd_sum[vrtcl,hrzn]=0

grd_sum = np.array(grd_sum, dtype = 'float')
grd_sum = grd_sum * 255. / np.max(grd_sum)

 

##### 閾値の設定(上限と下限) #####
maxVal = np.average(grd_sum)*3.0
minVal = np.average(grd_sum)*0.6
print(i, np.average(grd_sum))

edge_array = np.zeros(height,width))

 

##### 閾値で場合分け(上限より大きければエッジ,上限以下下限以上なら周りの状態で場合分けを行う) #####


for vrtcl in range (1, height-1):
for hrzn in range (1, width-1):
if (grd_sum[vrtcl,hrzn]>maxVal):
edge_array[vrtcl,hrzn] = 1
else:
edge_array[vrtcl,hrzn] = 0

for vrtcl in range (1, height-1):
for hrzn in range (1, width-1):
if (grd_sum[vrtcl,hrzn]>maxVal):
grd_sum[vrtcl,hrzn] = 255
if (grd_sum[vrtcl,hrzn]<maxVal and grd_sum[vrtcl,hrzn]>minVal):
if (1 == ([edge_array[vrtcl-1,hrzn-1] or edge_array[vrtcl-1,hrzn] or edge_array[vrtcl-1,hrzn+1] or
edge_array[vrtcl,hrzn-1] or edge_array[vrtcl,hrzn+1] or
edge_array[vrtcl+1,hrzn-1] or edge_array[vrtcl+1,hrzn] or edge_array[vrtcl+1,hrzn+1]])):
grd_sum[vrtcl,hrzn] = 255
else :
grd_sum[vrtcl,hrzn] = 0

if (grd_sum[vrtcl,hrzn]<minVal):
grd_sum[vrtcl,hrzn] = 0

 

##### 勾配画像の保存 (canny ディレクトリ) #####
img_grd.save(canny_path + '\\canny.tif')

print('process were correctly finished')