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ă
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)
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)
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)
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()
Încă un set de imagini:
Salut, Sunt incepator si te rog sa ma ajuti si pe mine, daca vrei, cu un programel sau macar cu o ideie despre el, care sa faca:
Am o lista de culori (segmentate in HSV), sa zicem 40 culori si vreau sa selectez o culoare (sa zicem 12). Baleind peste culori cand ajung la peramertri culorii 12 vreau sa fiu avertizat (o variabila booleana).
Multumesc.
Salutare Costel, scuze de răspunsul întârziat. Dacă mai e relevant pentru tine, da-mi un semn pe mail – andrei.avram@gmail.com. Nu inteleg exact ce incerci sa faci …