5.26. Hitta linjer och segment

Vissa scensärdrag är inte sammanhängande färgområden utan orienterade raka kanter: en målad linje på golvet, fogen mellan två ytor, sidan av en tryckt rektangel, kanten på en dörröppning. Att be blobdetektorn att hitta dem är fel fråga – kanten är en pixel bred, blobalgoritmen vill ha yta-med-färg, och svaret kommer tillbaka tomt eller brusigt.

Den rätta detektorn för orienterade kanter är Hough-linjetransformen. Bildmodulen tillhandahåller den i två varianter: find_lines() returnerar oändliga linjer (varje linje sträcker sig över hela bilden); find_line_segments() returnerar ändliga segment (varje linje har ändpunkter inuti bildrutan). Vilken applikationen behöver beror på om kanterna av intresse är kontinuerliga över hela bildrutan eller bara sträcker sig över en del av den.

5.26.1. Hur Hough-transformen fungerar

Båda detektorerna delar samma grundidé, så det lönar sig att förstå den en gång. Bildmodulen kör först ett Sobel-liknande kantfilter på indata för att poängsätta varje pixel efter hur sannolikt det är att den ligger på en orienterad kant. Varje sådan kantpixel röstar sedan på alla linjer den skulle kunna ligga på. De linjer som samlar flest röster vinner.

En linje parametriseras i Hough-utrymmet av två tal: theta, linjens vinkel (0 – 179 grader), och rho, det vinkelräta avståndet från bildens origo till linjen (med tecken, i pixlar). Varje linje bilden innehåller är en punkt i (theta, rho)-utrymmet. Varje kantpixel i indata bidrar med en röst till varje (theta, rho)-kombination som är förenlig med dess position – konceptuellt en kurva genom Hough-utrymmet. Där många sådana kurvor korsar varandra är många kantpixlar överens om samma linje, och den korsningen är en detektering.

Detektorn returnerar de lokala maxima i Hough-utrymmet vars rösttotaler överstiger ett tröskelvärde. Varje returnerad Line bär båda representationerna: x1, y1, x2, y2 för ändpunktsformen (klippt till bildens gränser i det oändliga fallet), theta, rho för Hough-formen, samt length och magnitude för storlek respektive antal röster.

5.26.2. Oändliga linjer

find_lines() kör Hough-transformen och returnerar de starkaste linjerna, var och en utsträckt över hela bilden:

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 är den minsta rösttotalen för att en linje ska accepteras. Rösttotalen summerar Sobel-kantmagnituderna för varje bidragande pixel, så större threshold-värden kräver längre eller starkare kanter för att godkännas – vilket gör att det rätta värdet beror på bildens upplösning (en längre linje vid en högre upplösning ackumulerar fler röster) såväl som på scenen, så det måste ställas in för den specifika applikationen. Som grova startpunkter att ställa in från: 1000 för en måttlig linje i en tydlig bild, 500 eller lägre för svag kontrast eller korta linjer, 2000 eller mer för röriga scener där falskt positiva linjer bildas genom kluster av kantbrus.

theta_margin och rho_margin styr sammanslagning av närliggande maxima. En enda fysisk kant producerar ett litet kluster av högröstade fack runt sin verkliga (theta, rho), och detektorn kollapsar varje kluster till sin topp innan den returnerar. theta_margin=25 (grader) slår samman alla toppar inom 25 graders orientering; rho_margin=25 (pixlar) slår samman toppar inom 25 pixlars avstånd. Standardvärdena är rimliga; att höja dem returnerar färre, mer distinkta linjer och att sänka dem returnerar fler, ibland dubblerade linjer.

x_stride och y_stride stegar genom kantpixlar under röstningen, på samma sätt som de stegar genom pixlar i find_blobs(). Standardvärdena 2 och 1 fungerar för det vanliga fallet; att höja dem snabbar upp sökningen på bekostnad av upplösning. roi begränsar sökningen till ett område av bildrutan, vilket både snävar in de returnerade linjerna och minskar arbetet.

Varje returnerad linje går att rita direkt: Line-objektet skickas rakt in i draw_line(), som läser (x1, y1, x2, y2)-ändpunktsfälten från dess början. l.theta är vinkeln i grader, som klassificerar linjen som horisontell, vertikal eller diagonal i en enda jämförelse. l.magnitude är rösttotalen, som sorterar de returnerade linjerna från starkast till svagast.

5.26.3. Linjesegment

find_lines() är den rätta detektorn för kanter som sträcker sig över hela bildrutan, men många verkliga kanter – vänstra sidan av en tryckt streckkod, ovankanten på en etikett, den synliga sidan av en linjal – löper bara över en del av bilden. find_line_segments() returnerar ändliga segment vars ändpunkter är inuti bildrutan:

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

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

Segmentdetektorn spårar längs orienterade kantpixlar direkt, snarare än att rösta i Hough-utrymmet, och resultatet är en samling korta raka sträckor. merge_distance sätter det maximala pixelglapp som två kollinjära korta sträckor kan spänna över och ändå slås samman till ett returnerat segment; max_theta_difference sätter hur många graders orientering sammanslagaren tolererar mellan intilliggande sträckor. En generös sammanslagning (merge_distance=10, max_theta_difference=15) returnerar ett litet antal långa segment på bekostnad av att ibland överbrygga genuint separata kanter; en strikt sammanslagning (merge_distance=0, max_theta_difference=5) returnerar många korta segment och låter applikationen sortera ut dem i Python.

Resultatobjekten är av samma Line-typ som find_lines() returnerar, med samma egenskaper, så ett flöde kan bearbeta endera sortens detektering genom samma nedströms kodväg. Den enda praktiska skillnaden är att segmentens ändpunkter är linjens faktiska ändar i bilden, medan de oändliga linjernas ändpunkter är där linjen korsar bildens kant.

5.26.4. När man ska använda vilken

Valet mellan de två metoderna kokar ner till en enda fråga: bryr sig applikationen om var linjen slutar?

find_lines() är det rätta verktyget när svaret är nej. En linjeföljande robot behöver veta åt vilket håll linjen går och var den korsar nederkanten av bildrutan; linjen själv löper till horisonten och bortom. En horisontdetektor vill ha den starkaste orienterade kanten i bilden; den behöver inte veta var horisonten tar slut.

find_line_segments() är det rätta verktyget när svaret är ja. Att identifiera de fyra sidorna av en tryckt rektangel kräver fyra segment med kända ändpunkter. Att spåra ett finger som pekar mot en skärm innebär att följa ett kort segment vars ändpunkter är fingrets spets och bas. Att mäta längden på en synlig repa kräver segmentets faktiska utsträckning i pixlar.

Båda detektorerna delar en gemensam begränsning: de behöver kontrast. Sobel-kantfiltret de bygger på reagerar på ljusstyrkegradienter; en färgad kant mot en lika ljus bakgrund (en röd linje på en grön vägg med samma luminans) producerar ingen gradient och ingen detektering. När det fallet dyker upp i praktiken är lösningen att extrahera en enda LAB-kanal som en gråskalebild med rätt kontrast före sökningen – to_grayscale() med b-kanalen vald isolerar rött mot grönt där enbart luminanskanalen är platt – och lämna den kanalbilden till linjedetektorn.