Canny Edge Detection (Görüntülerin Kenarlarının Algılanması)

Canny Edge Detection, görüntülerde kenar algılama algoritmaları arasında en popüler olanlarından. John  F. Canny denen bir arkadaş tarafından geliştirildiği için bu adı almaktadır. Olayın özü; elimizde bir görüntü var ve biz bu görüntü de ön planda olan nesnenin kenarlarını belirlemek istiyoruz. Nesne ön plan da değilse ne yapmak lazım, o da başka bir konu. 🙂

Canny’nin 4 ana adımı var. Bu dört adımı tek tek uygulayarak sonuca varabiliriz yada OpenCV kütüphanesinin “cv.Canny()” fonksiyonunu kullanabiliriz. Biz burada önce bu dört adımı uygulayarak bir sonuç elde edeceğiz ve daha sonra kütüphaneyi kullanarakta bir sonuç elde edip sonuçları karşılaştıracağız.

  1. Adım: Gürültü Azaltma (Noise Reduction)
  2. Adım: Görüntünün Yoğunluk Gradyanını Bulma (Finding Intensity Gradient of the Image)
  3. Adım: Maksimum Olmayanı Bastırma (Non-Maximum Supression)
  4. Adım: Histerezis Eşiği Uygulama (Hysteresis Thresholding)

Gürültü Azaltma (Noise Reduction)

Canny Kenar algılama algoritması, görüntülerde ki yüksek frekanslı bileşenleri artıran, düşük olanları ise bastıran bir algoritmadır. Görüntülerde kenarlar ve gürültüler yüksek frekanslı bölgeler oldukları için gürültülü bölgeler yükselmesin diye alçak geçiren bir filtre (low pass filter) uygulamak lazım ilkin. Canny bunun için Gauss filtresi kullanmaktadır. Genellikle 5×5 lik bir filtre uygulanır. O zaman önce ilgili kütüphanelerimizi import edelim, kurulu değilse pip install yapalım ve kullanacağımız görüntüyü yükleyip gürültü azaltma işlemini uygulayalım.


import cv2 as cv
import numpy as np

img=cv.imread('./1.jpg')

img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

img2=cv.GaussianBlur(img, (5,5), 0)

Kodlarda görüldüğü gibi görüntüyü yükledik, gray yaptık ve ardından 5×5 Gauss Alçak Geçiren filtresi uyguladık. Sonuçlar şöyle;

Görüntünün Yoğunluk Gradyanını Bulma (Finding Intensity Gradient of the Image)

Bu adımda görüntünün gradyanını hesaplayarak kenarların yoğunluğunu ve yönünü tespit edeceğiz. Yani kenar tespiti ilk bu adımda gerçekleştirilmiş olacak. Bildiğimiz gibi kenarlar piksel yoğunluğunun ani değişimlerine karşılık gelir ve bunu tespit etmenin en kolay yolu, görüntüye x ve y yönünde filtreler uygulamaktır. Bunun için Canny Algoritması, x ve y yönünde sobel filtresini görüntüyle konvüle etmektedir. Sobel hem yatay hem de dikey yönde ki gradyanları bulur. Kenarlar gradyan yönüne dik olduğundan, bu gradyanları kullanarak her piksel için kenar gradyanını ve yönünü bulmaya çalışacağız.

Kx ve Ky gibi iki adet Sobel Kernel’i oluşturup bunu Gaussian Low Pass Filter ettiğimiz görüntü ile konvüle edeceğiz.

Konvülüsyon sonucu oluşan Ix ve Iy’nin karelerinin toplamının karekökü bize kenar gradyanını (magnitude) verecek.

Ayrıca Iy’nin Ix’e oranının arctan değeri de bize kenar yönünü (direction) verecek.

Kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32)
Ky = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], np.float32)
Ix = cv.filter2D(img2, ddepth=-1, kernel=Kx)
Iy = cv.filter2D(img2, ddepth=-1, kernel=Ky)

G = np.hypot(Ix, Iy)
G = G / G.max() * 255
theta = np.arctan2(Iy, Ix)

img3=np.uint8(G)

Maksimum Olmayanı Bastırma (Non-Maximum Supression)

Yukarıda ki görüntülerde görüldüğü gibi kenarlar hala oldukça bulanık ve kalın. Bu nedenle kenarları inceltmemiz ya da başka bir deyişle en büyük kenarı bulmamız gerekir. Bu, Maksimum Olmayan Bastırma (Non-Maximum Supression) kullanılarak yapılır. Bunu yapmanın yolu; her piksel için komşu pikselleri yatay, dikey ve diyagonal yönlerde (0°, 45°, 90° ve 135°) bulup her pikseldeki gradyan yönünü de aşağıda gösterildiği gibi bu yönlerden birine yuvarlamamız gerekir.

Yuvarlamadan sonra, her piksel değerini gradyan yönündeki iki komşu pikselle bir döngü yardımı ile karşılaştıracağız. Eğer bu piksel yerel bir maksimum ise, kenar pikseli olarak kalacak aksi takdirde bastırılacak. Böyle yaparak sadece en büyük değerleri elde etmiş olacağız.


M, N = G.shape
Z = np.zeros((M,N), dtype=np.int32)
angle = theta * 180. / np.pi
angle[angle < 0] += 180

for i in range(1,M-1):
for j in range(1,N-1):
try:
q = 255
r = 255

#angle 0
if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180):
q = G[i, j+1]
r = G[i, j-1]
#angle 45
elif (22.5 <= angle[i,j] < 67.5):
q = G[i+1, j-1]
r = G[i-1, j+1]
#angle 90
elif (67.5 <= angle[i,j] < 112.5):
q = G[i+1, j]
r = G[i-1, j]
#angle 135
elif (112.5 <= angle[i,j] < 157.5):
q = G[i-1, j-1]
r = G[i+1, j+1]

if (G[i,j] >= q) and (G[i,j] >= r):
Z[i,j] = G[i,j]
else:
Z[i,j] = 0

except IndexError as e:
pass

img4=np.uint8(Z)

Görüldüğü gibi daha ince kenarlara sahip bir görüntü elde etmiş olduk. Fakat dikkat ettiyseniz bazı kenarlar diğerlerine göre daha parlak gözüküyor. Bunu da gidermek için son bir adım olarak Histerezis Eşiği Uygulayacağız (Hysteresis Thresholding).

Histerezis Eşiği Uygulama (Hysteresis Thresholding)

Maksimum olmayanı bastırma, görüntünün gerçek kenarlarını daha doğruya yakın bir şekilde çıkarmış oldu. Ancak belirttiğimiz gibi bazı kenarlar diğerlerinden daha parlak. Daha parlak olanlar güçlü kenarlar olabilir yada gürültü de olabilir. Canny, hangi kenarların gerçekten kenar olduğu ve hangilerinin olmadığı sorununu çözmek için Histerezis eşiklemeyi kullanır. Bu yöntemde, ‘Yüksek’ ve ‘Düşük’ olmak üzere iki eşik değeri belirleriz. Yoğunluğu, belirlediğimiz ‘Yüksek’  eşikten büyük olan tüm kenarlar kesin kenarlardır. Yoğunluğu, ‘Düşük’ eşikten az olan tüm kenarların ise kenar olmadığından emin oluruz. ‘Yüksek’ ve ‘Düşük’ eşikleri arasındaki kenarlar ise yalnızca kesin bir kenara bağlıysa kenar olarak sınıflandırılır, aksi takdirde kenar olmadığına karar verilir. Anlamak için bir örnek verelim.

Burada, A ve B ‘Yüksek’ eşiğinin üzerinde oldukları için kesin kenarlardır. Benzer şekilde, D kesin olmayan bir kenardır. Hem E hem de C zayıf kenarlardır ancak C kesin kenar olan B’ye bağlı olduğu için C de güçlü kenar olarak kabul edilir. Aynı mantıkla E de atılır. Bu şekilde görüntüde sadece güçlü kenarları elde edilmiş olur. Burada şu varsayımı göz ardı etmemek lazım; kenarların uzun çizgiler olduğu varsayılır.


yüksekesik = 40
dusukesik = 20

M, N = img4.shape
out = np.zeros((M,N), dtype= np.uint8)

strong_i, strong_j = np.where(img4 >= yüksekesik)
zeros_i, zeros_j = np.where(img4 < dusukesik)

weak_i, weak_j = np.where((img4 <= yüksekesik) & (img4 >= dusukesik))

out[strong_i, strong_j] = 255
out[zeros_i, zeros_j ] = 0
out[weak_i, weak_j] = 75

M, N = out.shape
for i in range(1, M-1):
for j in range(1, N-1):
if (out[i,j] == 75):
if 255 in [out[i+1, j-1],out[i+1, j],out[i+1, j+1],out[i, j-1],out[i, j+1],out[i-1, j-1],out[i-1, j],out[i-1, j+1]]:
out[i, j] = 255
else:
out[i, j] = 0

Başta belirttiğim gibi tüm bu işlemleri tek bir satır kod ile OpenCV kütüphanesinin cv.Canny() metodu ile de yapabilmekteyiz. Ancak bu metodun arka planında neler dönüyor anlamak adına böyle bir yazı kaleme almış olduk. Şimdi bu metod ile de bir sonuç elde edelim ve iki sonucu karşılaştıralım.


import cv2 as cv
import numpy as np

img=cv.imread('C:/Users/cuneyt.bayrak/Desktop/1.jpg')

img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

img2=cv.Canny(img,100,200,L2gradient=True)

cv.imshow('sonuç 2', img2)
cv.waitKey()

Sonuçların biraz farklı çıkması yüksek ve düşük eşik değerlerinin farklı olmasından kaynaklandı.

Umarım bu yazı Canny Kenar Algılama algoritmasını biraz olsun anlamamıza yardımcı olmuştur.

Kodların tamamını buradan indirebilirsiniz.

Paylaşmayı unutmayın!
0 0 votes
Article Rating
Subscribe
Bildir
guest
0 Yorum
Inline Feedbacks
View all comments