5.26. Çizgileri ve segmentleri bulma

Bazı sahne öznitelikleri bağlantılı renk bölgeleri değil, yönlendirilmiş düz kenarlardır: zemine boyanmış bir çizgi, iki yüzey arasındaki dikiş, basılı bir dikdörtgenin kenarı, bir kapı eşiğinin kenarı. Bunları bulması için nokta tespit edicisine sormak yanlış sorudur – kenar bir piksel genişliğindedir, nokta algoritması renkle alan ister ve yanıt boş veya gürültülü olarak geri gelir.

Yönlendirilmiş kenarlar için doğru tespit edici, Hough çizgi dönüşümüdür. image modülü bunu iki türde sunar: find_lines() sonsuz çizgiler döndürür (her çizgi görüntünün tamamı boyunca uzanır); find_line_segments() sonlu segmentler döndürür (her çizginin uç noktaları çerçevenin içindedir). Uygulamanın hangisine ihtiyaç duyduğu, ilgilenilen kenarların çerçevenin tamamında sürekli mi yoksa yalnızca bir kısmını mı kapsadığına bağlıdır.

5.26.1. Hough dönüşümü nasıl çalışır

Her iki tespit edici de aynı temel fikri paylaşır, bu nedenle onu bir kez anlamak işe yarar. image modülü önce her pikseli yönlendirilmiş bir kenar üzerinde bulunma olasılığına göre puanlamak için girişe Sobel tarzı bir kenar filtresi uygular. Ardından bu tür her kenar pikseli, üzerinde bulunabileceği tüm çizgiler için oy verir. En çok oyu toplayan çizgiler kazanır.

Bir çizgi, Hough uzayında iki sayıyla parametrelendirilir: theta, çizginin açısı (0 – 179 derece) ve rho, görüntü orijininden çizgiye dik mesafe (işaretli, piksel cinsinden). Görüntünün içerdiği her çizgi, (theta, rho) uzayında bir noktadır. Girişteki her kenar pikseli, konumuyla tutarlı her (theta, rho) kombinasyonuna bir oy katkıda bulunur – kavramsal olarak Hough uzayından geçen bir eğri. Bu tür birçok eğrinin kesiştiği yerde, birçok kenar pikseli aynı çizgi üzerinde uzlaşır ve bu kesişim bir tespittir.

Tespit edici, oy toplamları bir eşiği aşan Hough uzayındaki yerel maksimumları döndürür. Döndürülen her Line, her iki temsili de taşır: uç nokta biçimi için x1, y1, x2, y2 (sonsuz durum için görüntü sınırlarına kırpılmış), Hough biçimi için theta, rho ve sırasıyla boyut ve oy sayısı için length ve magnitude.

5.26.2. Sonsuz çizgiler

find_lines(), Hough dönüşümünü çalıştırır ve her biri görüntünün tamamı boyunca uzatılmış en güçlü çizgileri döndürür:

lines = img.find_lines(threshold=1500, theta_margin=25, rho_margin=25)

for l in lines:
    img.draw_line(l, color=(255, 0, 0))

threshold, bir çizginin kabul edilmesi için gereken minimum oy toplamıdır. Oy toplamı, katkıda bulunan her pikselin Sobel kenar büyüklüklerini toplar, dolayısıyla daha büyük threshold değerleri geçmek için daha uzun veya daha güçlü kenarlar gerektirir – bu da doğru değeri sahnenin yanı sıra görüntü çözünürlüğüne de bağımlı kılar (daha yüksek bir çözünürlükteki daha uzun bir çizgi daha fazla oy biriktirir), bu nedenle belirli uygulama için ayarlanması gerekir. Ayar yapmaya başlanacak kaba başlangıç noktaları olarak: net bir görüntüdeki mütevazı bir çizgi için 1000, zayıf kontrast veya kısa çizgiler için 500 veya altı, yanlış pozitif çizgilerin kenar gürültüsü kümeleri aracılığıyla oluştuğu yoğun sahneler için 2000 veya daha fazla.

theta_margin ve rho_margin, yakındaki maksimumların birleştirilmesini denetler. Tek bir fiziksel kenar, gerçek (theta, rho) değeri etrafında küçük bir yüksek oylu kova kümesi üretir ve tespit edici, döndürmeden önce her kümeyi tepe noktasına indirir. theta_margin=25 (derece), yönelimi 25 derece içinde olan tüm tepeleri birleştirir; rho_margin=25 (piksel), mesafesi 25 piksel içinde olan tepeleri birleştirir. Varsayılanlar makuldür; bunları artırmak daha az ve daha belirgin çizgiler döndürür, düşürmek ise daha fazla, bazen yinelenmiş çizgiler döndürür.

x_stride ve y_stride, find_blobs() yönteminde pikseller arasında adımladıkları gibi, oylama sırasında kenar pikselleri arasında adımlar. 2 ve 1 varsayılanları yaygın durum için işe yarar; bunları artırmak, çözünürlük pahasına aramayı hızlandırır. roi aramayı çerçevenin bir bölgesiyle kısıtlar; bu hem döndürülen çizgileri daraltır hem de işi azaltır.

Döndürülen her çizgi doğrudan çizilebilir: Line nesnesi, ön tarafından (x1, y1, x2, y2) uç nokta alanlarını okuyan draw_line() yöntemine doğrudan geçer. l.theta derece cinsinden açıdır ve tek bir karşılaştırmayla çizgiyi yatay, dikey veya çapraz olarak sınıflandırır. l.magnitude oy toplamıdır ve döndürülen çizgileri en güçlüden en zayıfa sıralar.

5.26.3. Çizgi segmentleri

find_lines(), çerçevenin tamamını kapsayan kenarlar için doğru tespit edicidir, ancak birçok gerçek kenar – basılı bir barkodun sol tarafı, bir etiketin üst kenarı, bir cetvelin görünen yüzü – görüntünün yalnızca bir kısmı boyunca uzanır. find_line_segments(), uç noktaları çerçevenin içinde olan sonlu segmentler döndürür:

segments = img.find_line_segments(merge_distance=5, max_theta_difference=10)

for s in segments:
    img.draw_line(s, color=(0, 255, 0))

Segment tespit edicisi, Hough uzayında oy vermek yerine doğrudan yönlendirilmiş kenar pikselleri boyunca izler ve sonuç, kısa düz koşuların bir koleksiyonudur. merge_distance, iki eş doğrultulu kısa koşunun kapsayıp yine de döndürülen tek bir segmente birleşebileceği maksimum piksel boşluğunu belirler; max_theta_difference, birleştiricinin bitişik koşular arasında kaç derecelik yönelime tolerans gösterdiğini belirler. Cömert bir birleştirme (merge_distance=10, max_theta_difference=15), bazen gerçekten ayrı kenarları köprülemek pahasına az sayıda uzun segment döndürür; katı bir birleştirme (merge_distance=0, max_theta_difference=5), birçok kısa segment döndürür ve uygulamanın bunları Python’da ayırmasına izin verir.

Sonuç nesneleri, find_lines() yönteminin döndürdüğü aynı Line türüdür ve aynı özelliklere sahiptir, bu nedenle bir işlem hattı her iki tür tespiti de aynı aşağı akış kod yolu üzerinden işleyebilir. Tek pratik fark, segmentlerin uç noktalarının görüntüdeki çizginin gerçek uçları olması, oysa sonsuz çizgilerin uç noktalarının çizginin görüntü sınırını geçtiği yer olmasıdır.

5.26.4. Her birinin ne zaman kullanılacağı

İki yöntem arasındaki seçim tek bir soruya iner: uygulama çizginin nerede durduğuyla ilgileniyor mu?

find_lines(), yanıt hayır olduğunda doğru araçtır. Çizgi takip eden bir robotun çizginin hangi yöne gittiğini ve çerçevenin alt kısmını nerede geçtiğini bilmesi gerekir; çizginin kendisi ufka ve ötesine uzanır. Bir ufuk tespit edicisi, görüntüdeki en güçlü yönlendirilmiş kenarı ister; ufkun nerede bittiğini bilmesi gerekmez.

find_line_segments(), yanıt evet olduğunda doğru araçtır. Basılı bir dikdörtgenin dört kenarını belirlemek, bilinen uç noktalara sahip dört segment gerektirir. Bir ekrana işaret eden bir parmağı izlemek, uç noktaları parmağın ucu ve tabanı olan kısa bir segmenti takip etmek demektir. Görünen bir çiziğin uzunluğunu ölçmek, segmentin piksel cinsinden gerçek kapsamını gerektirir.

Her iki tespit edici de ortak bir sınırlamayı paylaşır: kontrasta ihtiyaç duyarlar. Üzerine kuruldukları Sobel kenar filtresi parlaklık gradyanlarına tepki verir; eşit parlaklıktaki bir arka plana karşı renkli bir kenar (aynı parlaklıktaki yeşil bir duvarda kırmızı bir çizgi) hiçbir gradyan ve hiçbir tespit üretmez. Bu durum pratikte ortaya çıktığında, çözüm aramadan önce tek bir LAB kanalını doğru kontrasta sahip gri tonlamalı bir görüntü olarak çıkarmaktır – b kanalı seçili to_grayscale(), parlaklık kanalının tek başına düz olduğu yerde kırmızıyı yeşile karşı yalıtır – ve o kanal görüntüsünü çizgi tespit edicisine vermektir.