Color tracking cu OpenCV și python

Ziceam că voi reveni cu un mic post despre cum se poate face quick’n’dirty color tracking cu OpenCV și python. De ce OpenCV? Pentru că are tot ce-i trebuie. De ce python? Pentru că pentru explicații și pentru fast prototyping nu cred ca este alternativă mai bună (e ușor de citit, ușor de scris, chiar dacă aplicația finală va trebui rescrisă probabil în C).

Pe scurt, sarcina pe care o aveam de rezolvat a fost identificarea unei anumite zone de culoare, a formei și a dimensiunii ei, posibil și locația ei în imagine, ca să putem să ne ferim de adversar și să nu îl lovim.

Post-ul ăsta există nu doar pentru amintirea mea a unor chestii descoperite pe diferite site-uri de alții ca mine / sau de săpat prin documentație, ci și pentru că mi s-a părut foarte bizar să nu găsești decât frânturi de explicații. Și cred că poate fi interesant și pentru non-tehnici, pentru că python e foarte ușor de urmărit :)În primul rând, OpenCV oferă structuri foarte fericite pentru încărcarea imaginilor de pe disc, care se potrivesc apoi foarte bine cu preluarea de frame-uri de la o cameră (practic, sunt doar câteva linii de cod de schimbat pentru a trece de la un static frame la preluarea de imagini de la cameră).

Spre exemplu, ca să iei un frame de la o cameră, nu e mai mult de făcut decât:

capture = cv.CaptureFromCAM(1)
img = cv.QueryFrame(capture)

# sau, pentru o imagine de pe hard

img = cv.LoadImage("/home/yeti/Desktop/roz5.jpg", CV_LOAD_IMAGE_COLOR)

Pornim de la o imagine de 640 x 480, luată cu o cameră ieftină și necăjită

original

Acum, pentru a găsi culoarea pe care o căutam în imaginea cu care lucrăm, putem folosi mai multe strategii, în funcție de scenariu, dar de multe ori e mai simplu să trecem de la RGB (sau BGR, cum e reprezentarea în OpenCV) la sistemul de culori HSV (Hue, Saturation, Value), de unde vom folosi Hue (de ce, aici)

# facem o imagine noua, unde va fi stocata reprezentarea HSV
hsv_img = cv.CreateImage(cv.GetSize(img), IPL_DEPTH_8U, 3)
# ne trebuie o imagine cu un singur canal unde sa extragem componenta Hue
tmp_img = cv.CreateImage(cv.GetSize(img), IPL_DEPTH_8U, 1)
# transfomarea BGR -> HSV
cv.CvtColor(img, hsv_img, cv.CV_BGR2HSV)
# extragerea componentelor H, S, V din imaginea initiala (se procedeaza la fel si pentru BGR)
cv.Split(hsv_img, tmp_img, None, None, None)
componenta Hue (1 canal)

Apoi, am găsit pe un site pe care acum nu îl mai găsesc, am învățat o șmecherie, pentru a reduce din zgomotul din imagine, se face down-sampling și după up-sampling, și din aproximațiile făcute, chestiile micuțe dispar :)

 

# ne trebuie o imagine suplimentara - in care se stocheaza temporar imaginea down-sampled
tmp2 = cv.CreateImage((cv.GetSize(img)[0] / 2, cv.GetSize(img)[1] / 2), IPL_DEPTH_8U, 1)
# in jos
cv.PyrDown(tmp_img, tmp2)
# in sus
cv.PyrUp(tmp2, tmp_img)
după resampling

Apoi, cu imaginea asta de un canal rezultata, in care o parte din zgomot a fost scoasă, încercăm să găsim petele de culoare care se potrivesc cu culoarea noastră. De notat că imaginea asta are un singur canal, de la 0 la 255, și de asemenea de notat că, în funcție de lumiozitate și alți asemenea factori s-ar putea să fie nevoie să lățim un pic intervalul de căutare.

# in imaginea color_mask vom pastra practic o imagine binara (0 = punctul nu e in interval, 255 = e in interval)
color_mask = cv.CreateImage(cv.GetSize(img), IPL_DEPTH_8U, 1)
# functia InRangeS va binariza practic imaginea, si tot ce e intre 150 - 174 va iesi 255, restul 0
cv.InRangeS(tmp_img, cv.Scalar(150), cv.Scalar(174), color_mask)
binarizare - a se vedea ca nu doar baliza noastra este identificată ca roz

Ok, și avem acum o imagine binarizată, care poate conține unul sau mai multe obiecte care s-au potrivit pe ce căutam noi. Acum am vrea să vedem dacă găsim obiectul nostru – putem căuta un pătrat (4 laturi, convex), dar în cazul nostru am preferat să căutam cel mai mare poligon de culoarea respectivă.

 

Ca să facem asta, putem folosi o funcție care caută contururi în imaginea noastră binară. Apoi putem trece prin toate contururile, si incercam sa gasim un maxim intre ele, care va fi obiectul nostru. Mai folosim un pic de OpenCV magic, functia ApproxPoly, care face un fel de interpolare și mai reduce din zgomot, în funcție de coeficientul pe care îl primește funcția.

cv.NamedWindow("show")

storage = cv.CreateMemStorage(0)
# cautam in imaginea binarizata contururi
contours = cv.FindContours(color_mask, storage, mode = CV_RETR_LIST, method = CV_CHAIN_APPROX_NONE)
# pregatim color_mask, care acum este reciclabila, pentru a afisa in ea contururile
cv.Zero(color_mask)

# initializari
c = contours
index = 0
max_area = 0
max_result = None

if len(contours):
    while c.h_next() != None:
        current = c
        c = c.h_next()

        # cautam cel mai mare poligon
        if cv.ContourArea(current) > max_area:
            result = cv.ApproxPoly(current, storage, CV_POLY_APPROX_DP, cv.ArcLength(current) * 0.02, 0);
            max_result = result
            max_area = cv.ContourArea(current)

    if not max_result is None:
        # desenam contururile
        cv.DrawContours(color_mask, max_result, cv.ScalarAll(255), cv.ScalarAll(128), 2, thickness = -1)
        # si le afisam
        cv.ShowImage("show", color_mask)
        # si asteptam sa se apese ceva, ca sa vedem rezultatul
        cv.WaitKey()
și rezultatul final

Încă un set de imagini:

Original
Componenta Hue
Resample
binarizare (inranges)
rezultat final

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *

Acest sit folosește Akismet pentru a reduce spamul. Află cum sunt procesate datele comentariilor tale.