OpenCV 3.X -Python ile Geometrik Dönüşümlerin Resimlere Uygulanması

Abdulsamet İLERİ
9 min readAug 25, 2018

Bu yazımızda resimlerin okunması, görüntülenmesi ve kayıt edilmesini, color spaces (renk uzayları) dönüşümlerini, translation (yer değiştirme), rotation (döndürme), scaling (ölçekleme) işlerini, affine ve projective dönüşümlerin Python da OpenCV ile nasıl yapıldığını anlatmaya çalışacağız.

PyCharm ile bir python projesi oluşturup, main.py adlı dosyamı ekliyorum. Ve images adlı bir klasör açıp, içine örnek olarak seçtiğim input.jpg dosyasını atıyorum. (Kullanacağımız cv2 ve numpy library’lerin virtual enviroment’ime(venv) ekli olduğundan emin olduktan sonra başlayabiliriz.)

1-) Görüntüleri okunması, görüntülenmesi ve kaydedilmesi

OpenCV-Python’da bir resmi nasıl yükleyebileceğimizi görelim. main.py dosyamın içine aşağıdaki satırları yazıyorum.

import cv2
img = cv2.imread('./images/input.jpg')
cv2.imshow('Ornek resim', img)
cv2.waitKey()

Çalıştırdıktan sonra sonucu görelim.

input.jpg

Şimdi kodumuzu satır satır açıklamaya çalışalım. İlk satır ile OpenCV kütüphanemizi ekliyoruz ki içerdiği fonksiyonları kodumuz içerisinde kullanabilelim. İkinci satırımızda dosya yolu verilen resmimizi cv2.imread fonksiyonu ile okuyoruz ve img adlı değişkende saklıyoruz.

OpenCV, görüntüleri saklamak için NumPy veri yapılarını kullanır. Bunu test etmek için kodunuza print(type(img)) satırını eklediğinizde <class ‘numpy.ndarray’> çıktısını alacaksınız. (nd, n boyutlu demek)

Üçüncü satırımızda okuduğumuz resmi yeni bir pencerede gösteriyoruz. cv2.imshow fonksiyonuna verdiğimiz ilk parametre bu pencerenin başlığını, ikinci parametre de gösterilecek olan resmi ifade ediyor.

Son olarak yazdığımız cv2.waitKey(n) fonksiyonu, OpenCV de kulanılan keyboard binding fonksiyonudur. Parametre olarak içine bir argüman alır ve bu argüman milisaniye cinsinden süreyi ifade eder. Bu süre boyunca program durur, devam etmek için sürenin bitmesini veya herhangi bir keyboard event’i ile karşılaşmayı bekler. Eğer parametre geçirilmezse, 0 değeri (forever demek) default olarak verilir ki bu süre kısıtı olmadan keyboard event dinlemeye yarar.

OpenCV bir görüntüyü yüklemek için çeşitli yollar sunar. Ve bunu da görüntü yükleme fonksiyonu olan cv2.imread fonksiyonun 2.parametresine flag olarak belirtiriz. Şöyle ki gri tonlama modunda, renkli bir resmi yüklemek için cv2.IMREAD_GRAYSCALE flag’ını veriyoruz.

gray_img = cv2.imread('images/input.jpg', cv2.IMREAD_GRAYSCALE)
GRAYSCALE Çevirimi

ve oluşturduğumuz bu dosyayıda

cv2.imwrite('images/output.jpg', gray_img)

olarak kayıt edebiliyoruz. Ve istersek kayıt ederken de, resmimizin formatını yine bir flag ekliyerek değiştirebiliyoruz.

cv2.imwrite('images/output.png', img, [cv2.IMWRITE_PNG_COMPRESSION])

2-) Görüntü Renk Uzayları

Computer vision ve image processing alanında, color spaces(renk uzayları) kavramı renkleri organize etmenin belirli, özel yollarını ifade eder. Bir renk alanı aslında iki şeyin, bir renk modelinin ve bir mapping function (eşleme fonksiyonu) birleşimidir. Renk modellerini tercih etmemizin sebebi, tuples kullanarak piksel değerlerini temsil etmemize yardımcı olmasıdır. Mapping function dediğimiz verilen renk modelini, o model kullanılarak temsil edilecek renklerin kümesine eşler. Yararlı birçok farklı renk alanı vardır. (RGB, YUV, HSB gibi) Sadece verilen problem için doğru olan renk alanını seçmemiz gerekiyor.

RGB: Muhtemelen en popüler renk alanı. Kırmızı, Yeşil ve Mavi anlamına gelir. Bu renk alanında, her bir renk, kırmızı, yeşil ve mavi ağırlıklı bir kombinasyon olarak temsil edilir. Böylece her piksel değeri kırmızı, yeşil ve maviye karşılık gelen üç sayı ile bir tuple olarak temsil edilir. (Mesela beyaz elde etmek demek R:255, G:255, B:255 değeri vermek demektir.) Her bir değer 0 ile 255 arasında değişir.

YUV: RGB birçok amaç için iyi olsa da, real life application’lar için çok sınırlıdır. Bu yüzden insanlar yoğunluk bilgisini, renk bilgisinden ayırmak için farklı yöntemler düşünmeye başladılar ve YUV ortaya çıktı. Y, parlaklık veya yoğunluğu belirtir, U / V kanalları ise, renk bilgilerini temsil eder. Bu model birçok uygulamada iyi çalışır çünkü insana verilen görsel sistem, yoğunluk bilgisini renk bilgisinden çok farklı algılar.

HSV: Tabi sorunlarımız her ne kadar YUV iyi olsa da çözülmedi ve insanlar, bir insanın renkleri nasıl algıladığını düşünmeye başladılar sonra HSV ortaya çıktı. HSV, Ton, Doygunluk ve Değer anlamına gelir. Bu model, renklerin en temel özelliklerinden üçünü ayırıp farklı kanalları kullanarak bunları temsil ettiğimiz silindirik bir sistemdir ve insan görsel sisteminin rengi nasıl anladığıyla yakından ilgilidir. Ayrıca bu model ile görüntüler handle edilirken bizlere esneklik sağlar.

3) Renk Uzaylarını Dönüştürme

Tüm renk uzayları göz önünde bulundurulduğunda OpenCV’de yaklaşık 190 dönüşüm seçeneği sunulmuştur. Mevcut tüm flaglerin değerini görmek isterseniz, Python Shell’inize aşağıdakı kodu ekleyebilirsiniz.

import cv2
print([x for x in dir(cv2) if x.startswith('COLOR_')])

Herhangi bir renk uzayını başka bir renk uzayına dönüştürebiliriz. Örnek olarak cvtColor fonksiyonu ile çevireceğimiz img’yi ve hangi color space conversion uygulayacağını söyleyebiliriz.

gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

4) Görüntü Kanallarını Bölme

Mesela YUV için convert işlemini ele alalım.

yuv_img = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)

Sonuca baktığımızda

Bu, orijinal görüntünün bozulmuş bir sürümü gibi görünebilir, ancak değil. Üç kanalı ayıralım:

yuv_img = cv2.imread('./images/input.jpg')
y,u,v = cv2.split(yuv_img)
cv2.imshow('Y channel', y)
cv2.imshow('U channel', u)
cv2.imshow('V channel', v)

Evin pencerelerine dikkat edin..

yuv_img değişkenimiz type’ını yazdırdığımızda numpy classı olduğunu görmüştük. Bilindiği üzere numpy dimentional seçim operatörleri sağlar ve biz de kolaylıkla slice işlemi yapabiliriz. yuv_img.shape değerini print edersek (325, 630, 3) değerini görürüz ki bu 3D arraydir.

5) Görüntü Kanallarını Birleştirme

Şimdi input resmimizi okuyacağız, ayrı kanallara split ile ayırıp, bunlar üzerinde farklı kombinasyon oluşturup, birleştireceğiz ve değişikleri yani oluşan efektleri resmimizi nasıl etkilediğine bakacağız.

import cv2
img = cv2.imread('./images/input.jpg')
g,b,r = cv2.split(img)
gbr_img = cv2.merge((g,b,r))
rbr_img = cv2.merge((r,b,r))
cv2.imshow('GRB', gbr_img)
cv2.imshow('RBR', rbr_img)
cv2.waitKey()

Burada, farklı renk yoğunlukları elde etmek için kanalların nasıl yeniden birleştirilebileceğini görebiliyoruz:

6) Görüntü Üzerinde Yer Değiştirme İşlemleri

Bu bölümde resimleri nasıl kaydıracağımı göreceğiz. Resmimizi referans çerçevemiz üzerinde taşımak istediğimizi varsayalım. Computer vision terminolojisinde, bu translation olarak adlandırılır.

Öncelikle translation demek basitçe x ve y koordinatları üzerinde eklemeler, çıkarmalar yaparak resmi kaydırmak anlamına geliyor. Ve translation yapılırken, bir translation_matrix (T) tanımlamamız gerekiyor.

Burada tx ve ty değerleri x ve y için translation değerleridir. Yani biz tx değeriyle x birim sağa gideceksin, ty değeriyle y birim aşağıda gideceksin deriz. (Örnek üzerinde daha iyi oturacak.) Oluşturduğumuz translation matrix’i resmimize uygulamak için OpenCV’nin bize sunduğu warpAffine fonksiyonunu kullanıyoruz. Bu fonksiyonun aldığı 3. parametre ile de ortaya çıkacak resmin satır ve sütünlarının sayısını tanımlıyoruz. Örneğimize bakalım,

import cv2
import numpy as np
img = cv2.imread('images/input.jpg')
num_rows, num_cols = img.shape[:2]
translation_matrix = np.float32([ [1,0,70], [0,1,110] ])
img_translation = cv2.warpAffine(img, translation_matrix, (num_cols,num_rows), cv2.INTER_LINEAR)
cv2.imshow('Translation', img_translation)
cv2.waitKey()

Sonuca bakalım,

num_rows, num_cols = img.shape[:2]

resmimizin satır ve sütün değerini alıyoruz. Burda python slice operatörü kullandık. (325, 625) tarzında bir şey dönüyor.

translation_matrix = np.float32([ [1,0,70], [0,1,110] ])

Burda translation matriximizi numpy instance kullanarak, 70 birim sağa(tx), 110 birim aşağıya(ty) gidecek şekilde tanımlıyoruz.

np.float32([[1, 0, 0], [0, 1, 0]]) original image, hiç shifting yok.np.float32([[1, 0, 70], [0, 1, 0]]) 70 birim x ekseninde sağa kaydır.np.float32([[1, 0, 0], [0, 1, 110]]) 110 birim, y ekseninde aşağıya kaydır.np.float32([[1, 0, -30], [0, 1, 0]]) 30 birim, x ekseninde sola 
kaydır.
np.float32([[1, 0, 0], [0, 1, -100]]) 100 birim, y ekseninde yukarıya kaydır.img_translation = cv2.warpAffine(img, translation_matrix, (num_cols,num_rows), cv2.INTER_LINEAR)

img üzerinde, translation matrixi, orjinal kolon ve satır değerleri üzerine uygula. Şimdi orjinal satır ve sütün değerini kullandığımızdan resmimiz kırpılacak çünkü yer yok, sığmıyor. Kırpmayıda önlemek istersek de

img_translation = cv2.warpAffine(img, translation_matrix, (num_cols + 70 , num_rows + 110), cv2.INTER_LINEAR)

Sonuç,

Resmi container pencerenin ortasına taşımak istediğinizi varsayalım;

import cv2
import numpy as np
img = cv2.imread('images/input.jpg')
num_rows, num_cols = img.shape[:2]
translation_matrix = np.float32([ [1,0,70], [0,1,110] ])
img_translation = cv2.warpAffine(img, translation_matrix, (num_cols + 70,
num_rows + 110))
translation_matrix = np.float32([ [1,0,-30], [0,1,-50] ])
img_translation = cv2.warpAffine(img_translation, translation_matrix,
(num_cols + 70 + 30, num_rows + 110 + 50))
cv2.imshow('Translation', img_translation)
cv2.waitKey()

Ayrıca, çevirinin boş sınırlarını bir piksel interpolation yöntemi ile doldurmanıza izin veren iki tane daha argüman, borderMode ve borderValue vardır;

img_translation = cv2.warpAffine(img, translation_matrix, (num_cols,
num_rows), cv2.INTER_LINEAR, cv2.BORDER_WRAP, 1)
Containerde boş, siyah yerleri doldurduk.

7-) Resmi Döndürme

Burada, belirli bir görüntüyü belirli bir açıdan nasıl döndüreceğimizi göreceğiz.

rotation_matrix = cv2.getRotationMatrix2D((num_cols/2, num_rows/2), 30, 0.7)

OpenCV bize bu iş için getRotationMatrix2D fonksiyonunu sunuyor.

Bu fonksiyon ile ilk argüman olarak döndürüleceği merkez noktasını, sonra derece cinsinden dönme açısını ve sonunda görüntü için bir ölçekleme faktörü belirtebiliriz. Görüntüyü% 30 oranında küçültmek için 0.7 kullanıyoruz ki çerçeveye sığsın.

30 derece döndürdük.
img_translation = cv2.warpAffine(img, translation_matrix, (2*num_cols, 2*num_rows))

yeterli yer vererek yine resmimizin kırpılmamasını önleyebiliriz.

8-) Resmi Ölçeklendirme

Computer vision’un en yaygın operasyonu resmi yeniden boyutlandırmaktır. Nasıl yapıldığını görelim.

Bir resmi yeniden boyutlandırdığımızda, piksel değerlerini doldurmanın birden fazla yolu vardır. Bir görüntüyü büyütürken, piksel değerlerini piksel konumları arasında doldurmamız gerekir. Bir görüntüyü küçültürken, temsil edecek en iyi değerini almamız gerekiyor. Tamsayı olmayan bir değerle ölçeklendirdiğimizde, görüntülerin kalitesini koruyabilmemiz için değerleri uygun bir şekilde interpolation etmemiz gerekir. Interpolation yapmak için birçok yol vardır. Bir görüntü büyütüyorsak, doğrusal veya kübik interpolation kullanmayı tercih ederiz. Bir görüntüyü daraltırsak, alan tabanlı interpolation kullanırız. Kübik interpolation, hesaplama bakımından daha karmaşıktır ve dolayısıyla doğrusal interpolation’dan daha yavaştır. Bununla birlikte, ortaya çıkan görüntünün kalitesi daha yüksek olacaktır. Bir örnekle bakalım.

img_scaled = cv2.resize(img,None,fx=1.2, fy=1.2, interpolation=cv2.INTER_LINEAR)

OpenCV, görüntü ölçeklendirmesi elde etmek için resize adı verilen bir fonksiyon sunar. Bir boyut belirtmezseniz (None), x ve y ölçekleme faktörlerini bekler. Örneğimizde, görüntü 1.2 faktörü ile büyütülecektir.

1.2 Faktörü ile resmimiz büyültüldü.

9-) Affine Transformations

Bu bölümde, 2D görüntülerin çeşitli genelleştirilmiş geometrik dönüşümlerini ele alacağız. warpAffine fonksiyonunu yukarıdaki örneklerde kullanmıştık. Şimdi ise biraz arka planına bakmamız gerek.

Affine dönüşümlerinden bahsetmeden önce, Öklid dönüşümlerinin ne olduğunu öğrenelim. Öklid dönüşümleri uzunluk ve açı ölçülerini koruyan bir tür geometrik dönüşümdür. Geometrik bir şekil alır, öklid dönüşümünü uygularsak şekil değişmeyecektir. Bu işlem sonucu şekil, döndürülmüş, kaydırılmış gibi görünebilir, ancak temel yapı değişmeyecektir. Teknik olarak, çizgiler çizgiler halinde kalacak, düzlem düzlem kalacak, kareler kareler kalacak ve daireler daireler kalacak.

Affine dönüşümlerine geri dönersek, Öklid dönüşümlerinin genelleşmiş hali olduklarını söyleyebiliriz. Affine dönüşümleri altında, çizgiler çizgiler halinde kalacaktır, ancak kareler, dikdörtgenler veya paralelkenarlar haline gelebilir. (Örnekte oturacak.) Temel olarak, affine dönüşümler uzunlukları ve açıları korumaz.

Bir affine transformation matrix oluşturmak istiyorsak, öncelikle kontrol noktalarını belirlememiz gerekir. Bu noktaları tanımladıktan sonra, hangi noktalara mapped olacaklarına karar vermemiz gerekir. Örnek olarak input ve output resimlerinden 3 nokta alacağız.

Kaynak resimden alacağımız 3 noktayı, hedef resimde karşılık gelen 3 noktaya map etmek istiyoruz.

src_points = np.float32([[0,0], [cols-1,0], [0,rows-1]])
dst_points = np.float32([[0,0], [int(0.6*(cols-1)),0],
[int(0.4*(cols-1)),rows-1]])
affine_matrix = cv2.getAffineTransform(src_points, dst_points)
img_output = cv2.warpAffine(img, affine_matrix, (cols,rows))
cv2.imshow('Output', img_output)

Sonuç

kaynak resimde [0,0] noktasını => çıktı görüntünün [0,0] noktasına,

[kaynak resmin bittiği(1.bölgede) x noktasının bir eksiğini , 0] => 0*6 ile çarpılmış haline

[0, kaynak resmin bittiği(4.bölgede) y noktasının bir eksiğini] => 0*4 ile çarpılmış haline map ediyoruz.

src_points = np.float32([[0,0], [cols-1,0], [0,rows-1]])
dst_points = np.float32([[0,0], [int(0.6*(cols-1)),0],

Yani şu kodla şu haritalamayı yaptık,

inşAllah anlaşılmıştır :) Mesela

src_points = np.float32([[0,0], [cols-1,0], [0,rows-1]])
dst_points = np.float32([[cols-1,0], [0,0], [cols-1,rows-1]])

Yani resmin tersini alacak, sonuç

10-) Projective Transformations

Affine dönüşümleri iyidir, ancak belirli kısıtlamalar getirirler. Öte yandan, projective bir dönüşüm bize daha fazla özgürlük verir. Yansıtmalı dönüşümleri anlamak için, yansıtmalı geometrinin nasıl çalıştığını anlamamız gerekir. Temel olarak bakış açısı değiştiğinde bir görüntüye ne olduğunu tanımlarız. Örneğin, üzerinde çizilmiş bir kare olan bir kağıdın önünde duruyorsanız, kare gibi görünecektir. Ama, o kâğıt parçayı eğmeye başladığınız zaman, kare bir yamuk gibi görünmeye başlar. Projective dönüşümler, bu dinamikliği güzel, matematiksel şekilde ele geçirmemizi sağlar. Bu dönüşümler ne boyutları ne de açıları korur, ancak insidansı ve çapraz oranı korurlar.

src_points = np.float32([[0,0], [cols-1,0], [0,rows-1], [cols-1,rows-1]])
dst_points = np.float32([[0,0], [cols-1,0], [int(0.33*cols),rows-1],
[int(0.66*cols),rows-1]])
projective_matrix = cv2.getPerspectiveTransform(src_points, dst_points)
img_output = cv2.warpPerspective(img, projective_matrix, (cols,rows))

Sonuç

Kaynak görüntüde dört kontrol noktası seçilir ve bunları hedef resimle eşleştiririz. Paralel çizgiler, dönüşümden sonra paralel çizgiler olmayacaktır. Dönüşüm matrisini almak için getPerspectiveTransform adlı bir fonksiyon kullanıyoruz.

Şimdilik bu kadar daha sonra zamanım olursa Kenarları Algılama ve Görüntü Filtrelerini Uygulama üzerinde duracağız.

inşAllah anlașılmıștır. :)

--

--