Hough Dönüşümü ve Şekil Tespiti
2026
Önceki derslerde Canny ve Sobel gibi operatörlerle kenar tespiti yapmayı öğrendik.
Ancak kenar tespiti bize sadece “burada bir değişim var” diyen pikselleri (binary harita) verir. Bu piksellerin bir doğru mu yoksa bir daire mi oluşturduğunu matematiksel olarak bilmiyoruz.
Soru: Bu dağınık kenar noktalarını (pikselleri) nasıl anlamlı geometrik şekillere dönüştürebiliriz?
Cevap: Hough Dönüşümü (Hough Transform).
Bir doğruyu Kartezyen koordinat sisteminde (\(y\) ve \(x\)) şu şekilde ifade ederiz:
\[ y = mx + c \]
Sorun: Dikey bir doğru düşünün (\(x = 5\)). Bu doğrunun eğimi (\(m\)) sonsuzdur. Bilgisayarlar sonsuz değerleri sevmez ve hesaplamada sorun çıkarır. Bu yüzden Hough dönüşümünde farklı bir koordinat sistemi kullanmalıyız.
Hough dönüşümü, doğruları temsil etmek için Rho (\(\rho\)) ve Theta (\(\theta\)) parametrelerini kullanır.
\[ \rho = x \cos \theta + y \sin \theta \]
Bu gösterim sayesinde dikey doğrular dahil her türlü doğru sorunsuz ifade edilebilir.
Hough dönüşümünün en can alıcı noktası burasıdır:
Görüntü uzayındaki noktaların Hough uzayında kesişmesi:
OpenCV’de iki tür Hough Doğru Tespiti fonksiyonu vardır:
cv2.HoughLines)
cv2.HoughLinesP)
| Özellik | HoughLines (Standart) | HoughLinesP (Olasılıksal) |
|---|---|---|
| Çıktı | (ρ, θ) sonsuz doğru | (x₁, y₁, x₂, y₂) segment |
| Hız | Yavaş | Çok hızlı |
| Örnekleme | Tüm pikseller | Seçili pikseller |
| Boşluk toleransı | Zayıf | Güçlü |
| Gürültü | Daha hassas | Daha dayanıklı |
| Köşe analizi | Zor | Kolay |
| Pratik kullanım | Teorik | Gerçek uygulamalara uygun |
lines = cv2.HoughLinesP(
image, # Kenar görüntüsü (Canny çıktısı)
rho, # Piksel cinsinden uzaklık çözünürlüğü (genelde 1)
theta, # Radyan cinsinden açı çözünürlüğü (genelde np.pi/180)
threshold, # Bir doğrunun kabul edilmesi için gereken minimum oy sayısı
minLineLength, # Bu değerden kısa çizgiler reddedilir
maxLineGap # Aynı doğru üzerindeki noktalar arası izin verilen maksimum boşluk
)import cv2
import numpy as np
# Görüntüyü Oku
img = cv2.imread('yol.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 1. Adım: Kenar Tespiti (Canny)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
# 2. Adım: Olasılıksal Hough Dönüşümü
# threshold=100: En az 100 pikselin oy verdiği doğrular
# minLineLength=100: En az 100 piksel uzunluğundaki çizgiler
# maxLineGap=10: 10 piksele kadar olan boşlukları doldur
lines = cv2.HoughLinesP(
edges,
1,
np.pi/180,
100,
minLineLength=100,
maxLineGap=10
)
# Çizgileri Orijinal Görüntüye Çiz
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
# cv2.imshow('Hough Lines', img)Kenarlardan doğru tespit ederken, her kenar noktasının Hough uzayında bir eğri oluşturduğunu ve bu eğrilerin kesiştiği noktaların doğruyu verdiğini görmüştük.
Daire tespitinde de benzer bir fikir kullanılır:
Önce basit bir senaryo düşünelim:
Bu, görüntü uzayındaki (image space) manzaramızdır:
Dairenin denklemini hatırlayalım:
\[ (x - a)^2 + (y - b)^2 = r^2 \]
Burada: - \((a, b)\): dairenin merkezi - \(r\): yarıçap
Şimdi işi basitleştirmek için yarıçapın bilindiğini varsayalım (\(r\) sabit).
Tek bir kenar noktasına bakalım, örneğin \(P_1(x_1, y_1)\). Bu nokta için denklem:
\[ (x_1 - a)^2 + (y_1 - b)^2 = r^2 \]
Bu kez bilinmeyenler \(a\) ve \(b\) (merkezin koordinatlarıdır). Bu denklem, merkez uzayında \((a, b)\):
Yani:
Görüntü uzayında tek bir nokta, merkez uzayında olası merkezleri gösteren bir daire üretir.
Aynı mantığı diğer noktalar için de uygularsak:
Bu daireler ortak bir noktada kesişirlerse, bu nokta:
“Bu kenar noktalarının hepsinin üzerinde bulunduğu, yarıçapı \(r\) olan dairenin merkezidir.”
Bu, çizgisel Hough ile birebir paraleldir:
Gerçek görüntülerde:
Bu durumda parametre uzayı 3 boyutludur:
Her kenar noktası için, çok sayıda olası yarıçap değerine karşılık:
Sorun:
Bu 3B akümülatör hem çok yavaş, hem de bellek açısından maliyetli olabilir.
Bu yüzden pratikte doğrudan 3B Hough yapmak yerine, daha akıllı stratejiler kullanılır.
OpenCV, daire tespitini hızlandırmak için gradyan bilgisini kullanır:
Sonuç:
cv2.HoughCircles Fonksiyonunun MantığıOpenCV’de daire tespiti için kullanılan fonksiyon:
circles = cv2.HoughCircles(
image, # Gri seviye görüntü (Canny uygulanmamış hali!)
method, # Genelde cv2.HOUGH_GRADIENT
dp, # Akümülatör çözünürlüğü (1: aynı, 2: yarısı)
minDist, # Tespit edilen daire merkezleri arası minimum mesafe
param1, # Canny için yüksek eşik (düşüğü otomatik olarak bunun yarısı)
param2, # Dairenin kabul edilmesi için gereken minimum oy
minRadius, # Minimum yarıçap
maxRadius # Maksimum yarıçap
)import cv2
import numpy as np
img = cv2.imread('bozuk_paralar.jpg', 0)
img = cv2.medianBlur(img, 5) # Gürültü azaltmak kritik
circles = cv2.HoughCircles(
img,
cv2.HOUGH_GRADIENT,
1,
20,
param1=50,
param2=30,
minRadius=0,
maxRadius=0
)
circles = np.uint16(np.around(circles))
cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
for i in circles[0, :]:
# Dış çemberi çiz (Yeşil)
cv2.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)
# Merkezi çiz (Kırmızı)
cv2.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)
# cv2.imshow('Tespit Edilen Paralar', cimg)| Özellik | Doğru Tespiti (HoughLinesP) |
Daire Tespiti (HoughCircles) |
|---|---|---|
| Girdi | Kenar Haritası (Canny çıktısı - Binary) | Gri Seviye Görüntü (Blur uygulanmış) |
| Yöntem | Olasılıksal Oylama | Gradyan Yönü + Oylama |
| Hız | Hızlı | Göreceli Yavaş |
| Parametreler | Rho, Theta, Threshold | MinDist, Param1, Param2 |
| Kullanım | Şerit, Barkod, Tablo çizgileri | Göz bebeği, Hücre, Para, Trafik işaretleri |
Bir odaya küçücük bir pencereden baktığınızı hayal edin. Sadece o pencerenin içini görebiliyorsunuz. Amacımız, pencerenin nerede olduğunu anlamak.
Pencereyi (Kırmızı Kare) görüntü üzerinde hafifçe sağa, sola, yukarı veya aşağı kaydıralım. Pencerenin içindeki görüntü ne kadar değişiyor?
Görsel olarak 3 durum vardır:
Pencere gökyüzü gibi boş bir alandaysa, onu ne tarafa hareket ettirirseniz ettirin, pencerenin içindeki görüntü değişmez. * Sonuç: Burası ilgi çekici değil.
Pencere bir binanın kenarındaysa: * Kenar boyunca (yukarı-aşağı) hareket ettirirseniz görüntü değişmez. * Kenara dik (sağa-sola) hareket ettirirseniz görüntü aniden değişir. * Sonuç: Burası bir kenar, ama tam konumunu belirlemek zor (kenar boyunca nerede?).
Pencere bir binanın sivri ucundaysa (köşe): * Pencereyi hangi yöne (sağ, sol, yukarı, aşağı, çapraz) hareket ettirirseniz ettirin, görüntü ciddi şekilde değişir. * Sonuç: İşte aradığımız benzersiz nokta burası!
Bu “değişimi” matematiksel bir formüle dökelim.
Görüntü üzerindeki penceremiz \((x,y)\) noktasında olsun. Bu pencereyi \((u, v)\) kadar (örneğin sağa 1, yukarı 1 piksel) kaydıralım.
Eski pencere ile yeni pencere arasındaki farkı hesaplamamız lazım. Buna Hata Kareler Toplamı (SSD - Sum of Squared Differences) diyoruz ve \(E(u,v)\) ile gösteriyoruz.
\[ E(u,v) = \sum_{x,y} \underbrace{w(x,y)}_{\text{Pencere}} [ \underbrace{I(x+u, y+v)}_{\text{Kaydırılmış}} - \underbrace{I(x,y)}_{\text{Orijinal}} ]^2 \]
Her bir piksel için pencereyi her yöne kaydırıp farkı hesaplamak bilgisayar için çok yavaştır. Harris burada bir matematiksel “hyle” (yaklaşım) kullanır: Taylor Serisi.
Bir fonksiyonun (görüntünün) bir sonraki adımda ne olacağını, o anki eğimine (türevine) bakarak tahmin edebiliriz.
\[ I(x+u, y+v) \approx I(x,y) + I_x u + I_y v \]
Bu tahmini, yukarıdaki ana formülümüze koyarsak, o karmaşık \(E(u,v)\) formülü şu zarif matris çarpımına dönüşür:
\[ E(u,v) \approx \begin{bmatrix} u & v \end{bmatrix} \underbrace{\begin{bmatrix} \sum I_x^2 & \sum I_x I_y \\ \sum I_x I_y & \sum I_y^2 \end{bmatrix}}_{M \text{ Matrisi}} \begin{bmatrix} u \\ v \end{bmatrix} \]
İşte bütün olay bu \(M\) matrisidir! Bu 2x2’lik matris, o bölgedeki tüm değişimin özetidir.
\[ M = \sum_{x,y} w(x,y) \begin{bmatrix} I_x^2 & I_x I_y \\ I_x I_y & I_y^2 \end{bmatrix} \]
Şimdi elimizde her piksel için hesaplanmış bir \(M\) matrisi var. Bu matrisin geometrik şeklini anlamak için Özdeğerlerine (\(\lambda_1, \lambda_2\)) bakarız.
Bu özdeğerleri bir Elips gibi düşünebilirsiniz: * \(\lambda_1\): Elipsin uzun yarıçapı (En hızlı değişim yönü). * \(\lambda_2\): Elipsin kısa yarıçapı (En yavaş değişim yönü).
Bunu grafik üzerinde görselleştirelim:
Türevler (değişim) her yönde çok azdır. Elips bir noktadan farksızdır. * Görsel: Boş küme.
Bir yönde değişim çok büyük (Kenara dik), diğer yönde çok küçüktür (Kenar boyunca). * Görsel: İnce uzun bir elips (Sosis gibi).
Her iki yönde de değişim çok büyüktür. * Görsel: Büyük ve şişman bir daire/elips.
Grafik Özeti:
flowchart TD
A["lambda 2"] -->|"Küçük"| B["lambda 1 Küçük"]
B -->|"Bölge:"| C("Düz Alan")
A -->|"Küçük"| D["lambda 1 Büyük"]
D -->|"Bölge:"| E("Kenar")
A -->|"Büyük"| F["lambda 1 Küçük"]
F -->|"Bölge:"| G("Kenar")
A -->|"Büyük"| H["lambda 1 Büyük"]
H -->|"Bölge:"| I{"KÖŞE"}
style I fill:#f96,stroke:#333,stroke-width:4pxBilgisayarda her piksel için özdeğer (\(\lambda\)) hesaplamak (“Karekök” işlemi gerektirdiği için) maliyetlidir.
Harris, zekice bir matematiksel numara ile özdeğerleri hesaplamadan sonuca ulaşan bir skor fonksiyonu (\(R\)) geliştirmiştir:
\[ R = \det(M) - k \cdot (\text{trace}(M))^2 \]
Burada: * \(\det(M) = \lambda_1 \cdot \lambda_2\) (Özdeğerlerin çarpımı) * \(\text{trace}(M) = \lambda_1 + \lambda_2\) (Özdeğerlerin toplamı) * \(k\): Deneysel bir sabit (Genellikle 0.04 - 0.06 arası)
Görsel işleme hattı şöyledir:
Bu teorik adımların koddaki karşılığı sadece birkaç satırdır:
# 1. Görüntüyü Al
img_gray = cv2.imread("image.jpg", 0)
# 2, 3, 4 ve 5. Adımları OpenCV tek fonksiyonla yapar:
# blockSize=2 -> Pencere boyutu
# ksize=3 -> Sobel türev boyutu
# k=0.04 -> Harris sabiti
dst = cv2.cornerHarris(img_gray, blockSize=2, ksize=3, k=0.04)
# 6. Eşikleme (Görselleştirme)
# En yüksek skorun %1'inden büyük olanları kırmızı yap
img[dst > 0.01 * dst.max()] = [0, 0, 255]Bilgisayarla Görü – OMÜ | 🏠