5.26. Detectarea liniilor și a segmentelor

Unele caracteristici ale scenei nu sunt regiuni conexe de culoare, ci muchii drepte orientate: o linie vopsită pe podea, îmbinarea dintre două suprafețe, latura unui dreptunghi tipărit, marginea unei uși. A cere detectorului de blob-uri să le găsească este întrebarea greșită – muchia are lățimea de un pixel, algoritmul de blob-uri vrea suprafață-cu-culoare, iar răspunsul revine gol sau zgomotos.

Detectorul potrivit pentru muchii orientate este transformata Hough pentru linii. Modulul image o expune în două variante: find_lines() returnează linii infinite (fiecare linie se extinde pe toată imaginea); find_line_segments() returnează segmente finite (fiecare linie are capete în interiorul cadrului). Care dintre ele este necesară pentru aplicație depinde de faptul dacă muchiile de interes sunt continue pe tot cadrul sau acoperă doar o parte din el.

5.26.1. Cum funcționează transformata Hough

Ambele detectoare împărtășesc aceeași idee de bază, așa că merită înțeleasă o singură dată. Modulul image rulează mai întâi un filtru de muchii de tip Sobel pe intrare pentru a evalua fiecare pixel după probabilitatea ca acesta să se afle pe o muchie orientată. Fiecare astfel de pixel de muchie votează apoi pentru toate liniile pe care s-ar putea afla. Liniile care adună cele mai multe voturi câștigă.

O linie este parametrizată în spațiul Hough prin două numere: theta, unghiul liniei (0 – 179 de grade), și rho, distanța perpendiculară de la originea imaginii la linie (cu semn, în pixeli). Fiecare linie pe care o conține imaginea este un punct în spațiul (theta, rho). Fiecare pixel de muchie din intrare contribuie cu un vot pentru fiecare combinație (theta, rho) consistentă cu poziția sa – conceptual, o curbă prin spațiul Hough. Acolo unde se intersectează multe astfel de curbe, mulți pixeli de muchie sunt de acord asupra aceleiași linii, iar acea intersecție este o detectare.

Detectorul returnează maximele locale din spațiul Hough ale căror totaluri de voturi depășesc un prag. Fiecare obiect Line returnat poartă ambele reprezentări: x1, y1, x2, y2 pentru forma cu capete (decupată la limitele imaginii în cazul infinit), theta, rho pentru forma Hough, precum și length și magnitude pentru dimensiune și, respectiv, numărul de voturi.

5.26.2. Linii infinite

find_lines() rulează transformata Hough și returnează cele mai puternice linii, fiecare extinsă pe toată imaginea:

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 este totalul minim de voturi pentru ca o linie să fie acceptată. Totalul de voturi însumează magnitudinile muchiilor Sobel ale fiecărui pixel contribuitor, astfel încât valori threshold mai mari cer muchii mai lungi sau mai puternice pentru a trece – ceea ce face ca valoarea potrivită să depindă atât de rezoluția imaginii (o linie mai lungă la o rezoluție mai mare acumulează mai multe voturi), cât și de scenă, deci trebuie reglată pentru aplicația concretă. Ca puncte de plecare aproximative pentru reglaj: 1000 pentru o linie modestă într-o imagine clară, 500 sau mai puțin pentru contrast slab sau linii scurte, 2000 sau mai mult pentru scene aglomerate unde liniile fals-pozitive se formează prin grupuri de zgomot de muchie.

theta_margin și rho_margin controlează fuzionarea maximelor apropiate. O singură muchie fizică produce un grup mic de containere cu voturi multe în jurul valorilor sale reale (theta, rho), iar detectorul restrânge fiecare grup la vârful său înainte de a-l returna. theta_margin=25 (grade) fuzionează orice vârfuri aflate la mai puțin de 25 de grade ca orientare; rho_margin=25 (pixeli) fuzionează vârfurile aflate la mai puțin de 25 de pixeli ca distanță. Valorile implicite sunt rezonabile; creșterea lor returnează mai puține linii, mai distincte, iar scăderea lor returnează mai multe linii, uneori duplicate.

x_stride și y_stride parcurg pixelii de muchie în timpul votării, la fel cum parcurg pixelii în find_blobs(). Valorile implicite de 2 și 1 funcționează pentru cazul obișnuit; creșterea lor accelerează căutarea cu prețul rezoluției. roi restrânge căutarea la o regiune a cadrului, ceea ce atât limitează liniile returnate, cât și reduce volumul de lucru.

Fiecare linie returnată poate fi desenată direct: obiectul Line se transmite direct în draw_line(), care citește câmpurile cu capetele (x1, y1, x2, y2) din partea sa din față. l.theta este unghiul în grade, care clasifică linia drept orizontală, verticală sau diagonală printr-o singură comparație. l.magnitude este totalul voturilor, care sortează liniile returnate de la cea mai puternică la cea mai slabă.

5.26.3. Segmente de linie

find_lines() este detectorul potrivit pentru muchiile care acoperă tot cadrul, dar multe muchii reale – latura stângă a unui cod de bare tipărit, marginea de sus a unei etichete, latura vizibilă a unei rigle – acoperă doar o parte din imagine. find_line_segments() returnează segmente finite ale căror capete se află în interiorul cadrului:

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

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

Detectorul de segmente urmărește direct de-a lungul pixelilor de muchie orientați, în loc să voteze în spațiul Hough, iar rezultatul este o colecție de traseuri scurte și drepte. merge_distance stabilește decalajul maxim, în pixeli, pe care îl pot acoperi două trasee scurte coliniare și totuși să fuzioneze într-un singur segment returnat; max_theta_difference stabilește câte grade de orientare tolerează procesul de fuzionare între trasee adiacente. O fuzionare generoasă (merge_distance=10, max_theta_difference=15) returnează un număr mic de segmente lungi, cu prețul de a uni uneori muchii cu adevărat separate; o fuzionare strictă (merge_distance=0, max_theta_difference=5) returnează multe segmente scurte și lasă aplicația să le organizeze în Python.

Obiectele rezultat sunt de același tip Line ca cele returnate de find_lines(), cu aceleași proprietăți, astfel încât un proces poate prelucra oricare dintre tipurile de detectare prin aceeași cale de cod din aval. Singura diferență practică este că capetele segmentelor sunt capetele reale ale liniei din imagine, în timp ce capetele liniilor infinite sunt oriunde linia traversează marginea imaginii.

5.26.4. Când să folosești fiecare variantă

Alegerea între cele două metode se reduce la o singură întrebare: aplicația ține cont de locul în care se oprește linia?

find_lines() este instrumentul potrivit atunci când răspunsul este nu. Un robot care urmărește o linie trebuie să știe în ce direcție merge linia și unde traversează partea de jos a cadrului; linia însăși se întinde până la orizont și dincolo de el. Un detector de orizont vrea cea mai puternică muchie orientată din imagine; nu are nevoie să știe unde se termină orizontul.

find_line_segments() este instrumentul potrivit atunci când răspunsul este da. Identificarea celor patru laturi ale unui dreptunghi tipărit necesită patru segmente cu capete cunoscute. Urmărirea unui deget care indică un afișaj înseamnă urmărirea unui segment scurt ale cărui capete sunt vârful și baza degetului. Măsurarea lungimii unei zgârieturi vizibile necesită extinderea reală a segmentului, în pixeli.

Ambele detectoare împărtășesc o limitare comună: au nevoie de contrast. Filtrul de muchii Sobel pe care se bazează reacționează la gradientele de luminozitate; o muchie colorată pe un fundal la fel de luminos (o linie roșie pe un perete verde cu aceeași luminanță) nu produce niciun gradient și nicio detectare. Atunci când acest caz apare în practică, soluția este extragerea unui singur canal LAB ca imagine în tonuri de gri, cu contrastul potrivit, înainte de căutare – to_grayscale() cu canalul b selectat izolează roșul față de verde acolo unde canalul de luminanță singur este plat – și transmiterea acelei imagini de canal către detectorul de linii.