Görüntü Filtrelerini Uygulama ve Kenarları Algılama

Abdulsamet İLERİ
8 min readAug 27, 2018

--

Bu yazımızda temel görüntü işleme operatörlerini nasıl kullanacağımızı, kenarların nasıl algılandığını ve fotoğraflara çeşitli efektler uygulamak için görüntü filtrelerini nasıl kullanabileceğimizi göreceğiz.

Önceki yazımızda 2D Convolution’u ele almıştık. Eğer okumadıysanız bazı şeyler tam oturmayabilir. O yüzden okumanızı tavsiye ederim.

Blurring (Bulanıklaştırma) Filtresi

Bir alan içinde yer alan piksel değerlerinin ortalamasını ifade eder. Buna aynı zaman da Low Pass Filter de denilir. Low pass filter, düşük frekanslara izin veren ve daha yüksek frekansları engelleyen bir filtredir. Peki ya bir görüntüde frekans ne demek? Frekans piksel değerlerinin değişim hızını ifade eder. Bu yüzden keskin kenarların (sharped edges), yüksek frekanslı içerik olacağını söyleyebiliriz çünkü piksel değerleri o bölgede hızla değişir. Bu mantığa göre de, düz alanlar düşük frekanslı içerik olur. Low pass filter kenarları yumuşatır ki bu işlem görüntüdeki gürültüyü azaltmak veya daha az pikselli bir görüntü oluşturmak için kullanılır.

Low pass filter oluşturmanın basit bir yolu, pikselin (kernelin merkezindeki piksel) bulunduğu bölgedeki değerlerin ortalamasını almaktır. Kernel’in boyutunu, görüntünün ne kadar yumuşak olmasını istediğimize bağlı olarak değişir ve görüntü ona bağlı olarak etkilenir. Daha büyük bir boyut seçerseniz, daha geniş bir alan üzerinde ortalama olacaksınız ve buda yumuşatma etkisini artıracak. Şimdi low pass filter kernel’imize bakalım.

Blurring uygulamak için iki yöntemimiz var. Birincisi kernelimizi tanımlayıp filter2D fonksiyonu ile, diğeri ise doğrudan blur fonksiyonunu çağırmak ile. Örnekle görelim.

#1. Yol
kernel_3x3 = np.ones((3, 3), np.float32) / 9
output = cv2.filter2D(img, -1, kernel_3x3)
#2. Yol
output2 = cv2.blur(img, (5, 5))

filter2D ikinci parametresi depth’i, -1 vererek output resmimizin depth’i, kaynak resmimiz ile aynı olsun diyoruz. 2D Convolution anlatırken, kaynak matrisimiz sınırlarında piksellerin hesaplanması için çeşitli yöntemler var demiştik. Bu orayla alakalı. Detaylı bilgi

Blurring efekti

Burda kernel matrisimizin elemanlarının toplamının 1 olmasını istediğimizden 1/9 ile çarpıyoruz. Bu işleme normalization deniliyor. Bunu yapmamızın sebebi o pixel konumundaki intensity’yi (matrisde karşılık gelen değeri yani) artırmamak. Bu yüzden kernel’i görüntüye uygulaman önce bunu yapıyoruz. Eğer normalization yapmasaydık ne olurdu örnekle bakalım.

Elemanlar toplamı pozitif olursa parlar.
Elemanlar toplamı sıfır veya negatif olursa kararır.

Bundan dolayı kernel’i uygulamadan önce normalize etmemiz gerekmekte.

Sharpening (Keskinleştirme) Filtresi

İnsana verilmiş olan görsel algı, bir görüntünün kenarlarına ve ince detaylarına karşı son derece duyarlıdır. Bunun sebebi kenarların ve ince detayların yüksek frekanslı bileşenler tarafından oluşturulmasıdır. Görüntüdeki yüksek frekanslı bileşenleri zayıflatır veya kaldırırsak, görüntümüzün görsel kalitesi bu nispette azalacaktır. Fakat tersi işlemi düşünürsek yani görüntüdeki yüksek frekanslı bileşenleri geliştirirsek bu görsel kalitede bir iyileşmeye yol açacaktır. Image sharpening (görüntü keskinleştirmesi), bir görüntüdeki kenarları ve ince ayrıntıları vurgulayan, geliştiren herhangi bir geliştirme tekniğini ifade eder. [1]

# kernelleri tanımlıyoruz.
kernel_sharpen_1 = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
kernel_sharpen_2 = np.array([[1,1,1], [1,-7,1], [1,1,1]])
kernel_sharpen_3 = np.array([[-1,-1,-1,-1,-1],
[-1, 2, 2, 2,-1],
[-1, 2, 8, 2,-1],
[-1, 2, 2, 2,-1],
[-1,-1,-1,-1,-1]]) / 8.0

# tanımladığımız kerneli, görüntüye uyguluyoruz.
output_1 = cv2.filter2D(img, -1, kernel_sharpen_1)
output_2 = cv2.filter2D(img, -1, kernel_sharpen_2)
output_3 = cv2.filter2D(img, -1, kernel_sharpen_3)

Eğer kodu yakından incelerseniz, Sharpening level’ın kullandığımız kernele göre değiştiğini göreceksiniz.

Fakat kullandığımız bu kerneller ile output görüntümüzü yapay olarak geliştirmiş olduk. Görüntümüzün daha doğal görünmesini istiyorsak bir edge enhancement filter kullanmalıyız. Temel konsept aynı kalacak fakat gaussian kernel ile bu işlemi yapacağız. Bu kernel, kenarları geliştirerek görüntüyü düzeltmemize yardımcı olacak, böylece görüntü daha doğal görünecektir.

Embossing (Kabartma)

Görüntü kabartması, bir görüntünün her pikselinin, orijinal görüntüdeki light/dark sınırlara bağlı olarak, bir highlight(vurgu) veya shadow(gölge) ile değiştirildiği bir bilgisayar grafiği tekniğidir. Düşük kontrastlı alanlar gri bir arka planla değiştirilir. Belirli bir bölgede çok fazla kontrast varsa, kabartma yaptığımız yöne bağlı olarak bunu beyaz bir pikselle (vurgu) veya karanlık bir pikselle (gölge) değiştireceğiz. Sonuç olarakta, bu filterin bir görüntüye uygulanması sonucu ortaya çıkan output (filtered image), original görüntüdeki her konumun renk değişimi ifade edecektir. Örnekle inceleyelim. [2]

# kuzeyden güneye
kernel_emboss_1 = np.array([[0,-1,0], [0,0, 0], [0,1,0]])
# doğudan batıya
kernel_emboss_2 = np.array([[0,0,0], [1,0,-1], [0,0,0]])

vertical_emboss = cv2.filter2D(img_emboss_input, -1, kernel_emboss_1) + 128
horizontal_emboss = cv2.filter2D(img_emboss_input, -1, kernel_emboss_2) + 128

Yöne bağlı olarak kabartma yaptığımızı şöylemiştik. Peki bunun mantığı nedir? Yani kernel matris yöne göre nasıl dolduruluyor?

Matris üzerinde -1 tarafından(başlangıç noktası) +1 tarafına(bitiş noktası) doğru bir doğru çizdiğinizi hayal edin.

Kullanılan primary kerneller

Kullandığımız kernellerden görebildiğimiz gibi, mevcut piksel değerini sadece belirli bir yöndeki komşu piksel değerlerinin farkı ile değiştiriyoruz.

Kernel matrislerinin toplamı sıfıra düştüğünden, çıktı görüntüsü neredeyse tamamen siyah bir arka plana sahiptir (yukarıda örnekle açıklamıştık), sadece kenarlar görünür. Bu durumuda her piksele ekstra 128 (0–255 yarısı) değerini ekleyerek gri tonlu bir arka plan oluşturuyoruz.

Edge Detection (Kenar algılama)

Kenar algılama, bir resimdeki nesnelerin sınırlarını bulmak için kullanılan bir tekniktir ve çıktı olarak binary image verir. Genellikle, bu kenarları belirtmek için siyah bir arka plan üzerinde beyaz çizgiler çizeriz. Kenar algılamayı high pass filtering (yüksek geçişli filtreleme) işlemi olarak düşünebiliriz. Yüksek geçiş filtresi, yüksek frekanslı içeriğin geçmesine ve düşük frekanslı içeriğin bloklanması sağlar. Daha önce söylediğimiz gibi, kenarlar yüksek frekanslı içeriklerdir. Kenar tespitinde, bu kenarları korumak ve diğer her şeyi atmak, çıkarmak istiyoruz. Bu nedenle, yüksek geçişli bir filtrenin eşdeğeri olan bir kernel oluşturmalıyız.

Sobel filtresi olarak bilinen basit edge detection filtresi ile başlıyalım. Kenarlar hem yatay hem de dikey yönlerde ortaya çıkabileceğinden, Sobel filtresi aşağıdaki iki kernelden oluşur:

Soldaki kernel yatay kenarları algılar ve sağdaki kernel ise dikey kenarları. OpenCV, Sobel filtresini belirli bir görüntüye doğrudan uygulamak için bir fonksiyon(Sobel) sunar. Kenarları tespit etmek için Sobel filtrelerini kullanmanın kodu şöyledir:

sobel_horizontal = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
sobel_vertical = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)

Parametre olarak verilen cv2.CV_64F daha önceki yazıda anlattığımız image matrixdeki dışta kalan piksellerin nasıl handle edileceği ile alakalı. Aynı filter2D fonksiyonunda belirttiğimiz gibi yani.

Sobel fonksiyonun 3. ve 4. parametresi dx ve dy’dir. Eğer dx’e 1, dy’ye 0 verirsek bu horizontal (yatay) kenarları algılayacak. dx’e 0, dy’ye 1 verirsekte vertical (dikey) kenarları algılayacak.

Bu iş için aynı zamanda laplacian filter ve canny filter de kullanabiliriz.

Laplacian için,

laplacian = cv2.Laplacian(img, cv2.CV_64F)

Canny için,

canny = cv2.Canny(img, 50, 200)

Gördüğümüz gibi Canny edge dedector’un kalitesi çok daha iyi. Parametre olarak aldığı iki sayı, eşikleri (threshold) belirtir. İkinci argümana düşük eşik değeri (low threshold) ve üçüncü argümana yüksek eşik değeri (high threshold) denir. Eğer gradient değeri yüksek eşik değerinden fazla ise, güçlü bir kenar olarak işaretlenir. Canny kenar detektörü bu noktadan kenarı takip etmeye başlar ve gradient değeri, düşük eşik değerinin altına düşene kadar süreci devam ettirir

Erosion ve Dilation

Morfolojik dönüşümler, görüntü şeklini temel alan bazı basit işlemlerdir. Normalde binary görüntüler üzerinde gerçekleştirilir (grayscale üzerinde de çalışan versiyonları var). İki input’a ihtiyaç duyarlar. Biri original image’miz, diğeri ise işlemin yapısına karar veren structuring element (yapı elemanı) veya kernel’dir. Erosion ve Dilation’da iki temel morfolojik operatördür.

Erosion

Erosion temel fikri toprak erozyonu gibidir, ön plandaki nesnenin sınırlarını aşındırır. [3] Bu cidden ilginç bir tanım. ( Örnekte oturacak. )

Peki ne yapıyor? Kernel, görüntü üzerinde kaydırılır (2D convolution). Orijinal görüntüdeki (ya 1 ya da 0) bir piksel, yalnızca kernelin altındaki tüm piksellerin 1 olması durumunda 1 sayılacaktır, aksi takdirde aşınır (sıfırlanır). Yani sıfır değeri atanır.

erosion işlemi

Dilation

Dilation, erosion’un tam tersidir. Yani orijinal görüntüde bir piksel, yalnızca kernelin altındaki piksellerden birinin 1 olması durumunda 1 sayılır, yoksa sıfırdır. Böylelikle görüntüdeki beyaz bölge genişler(dilate olur) ve foreground(ön plandaki) objelerin size’ı artar.

dilation işlemi
erosion = cv2.erode(img,kernel,iterations = 1)
dilation = cv2.dilate(img,kernel,iterations = 1)

Erode ve dilate fonksiyonların aldığı üçüncü parametre, erode/dilate işlemlerinin ne kadar olacağını ifade eder. Temel olarak, elde edilen görüntüye ardışık olarak erode/dilate işlemi uygular.

Median Filter

Medyan filtre, genellikle bir görüntüden gelen gürültüyü gidermek için kullanılan doğrusal olmayan bir dijital filtreleme tekniğidir. Böyle bir gürültü giderme işlemi de, daha sonraki işlemlerin sonuçlarını iyileştirmek için kullanılan tipik bir ön işlem adımıdır. Medyan filtreler tuz ve biber(örnekle bağdaştırabilirsiniz) gürültülerini giderme kullanılır.

output = cv2.medianBlur(img, ksize=7)

Sonuç,

Web ve mobil kameralarında yaygın olarak kullanılır. Bu kameralar üzerinde yapılan işlemlerden önce bu filteri kullanır daha sonra işleme devam ederiz.

Örnekteki görüntüde gördüğümüz gibi, birçok yeşil piksel var. Görüntünün kalitesini düşürüyorlar ve onlardan kurtulmamız gerekiyor. İşte medyan filterin kullanışlı olduğu yerlerden biri.

Her pikselin etrafındaki NxN bölgesine (kernel size’a bağlı) bakarız ve bu sayıların medyan değerini seçeriz. İzole edilmiş piksellerin, bu durumda, yüksek değerlere sahip olmaları nedeniyle, medyan değeri almak bizi bu değerler kurtarır ve görüntümüzü düzleştirir. Çıktı görüntüsünde görebileceğiniz gibi, medyan filtresi tüm bu izole piksellerden bizi kurtardı ve görüntümüzü daha temiz bir hale geldi.

Bilateral (İki taraflı) Filtering

Bilateral bir filtre, görüntüler için doğrusal olmayan, kenar koruyucu ve gürültü azaltıcı bir yumuşatma filtresidir. Her pikselin yoğunluğunu, yakındaki piksellerden hesapladığı ortalama yoğunluk değerleri ile değiştirir.

Bilateral filtreleme ilginç bir kavramdır ve performansı bir Gauss filtresinden çok daha iyidir. Bilateral filtrelemenin iyi tarafı, kenarları koruduğu, Gaussian filtresinin ise her şeyi eşit bir şekilde düzleştirdiği yönündedir.

Örnekle bakalım.

img_gaussian = cv2.GaussianBlur(img, (13,13), 0) # Gaussian Kernel Size 13x13
img_bilateral = cv2.bilateralFilter(img, 13, 70, 50)

İki outputu yakından incelerseniz, Gauss filtrelenmiş görüntüdeki kenarların bulanık göründüğünü fark edeceksiniz. Genellikle, görüntüdeki kaba alanları düzeltmek ve kenarları sağlam tutmak istediğimizden bilateral filtre kullanmak çok daha iyi oluyor.

inşAllah anlatabilmişimdir :)

--

--