Finalmente siamo arrivati al primo impiego della Computer Vision per il nostro robottino, facendo uso delle librerie open source OpenCV. Esse sono un potente strumento, che consente con relativa semplicita` di creare applicazioni che impiegano object recognition, facial recognition, tracking, etc. Altra cosa interessante e` la possibilita` di usarle in Python. Infatti, esistono diversi binding, ufficiali e non. Noi abbiamo usato il binding ufficiale. Il problema e` la scarsa documentazione e la difficolta` nel reperire esempi in rete: sul sito di OpenCV la documentazione relativa a Python e` poco aggiornata, imprecisa e a volte non proprio corretta. Per questo motivo, non si escude in futuro un passaggio a SimpleCV.

In questo primo approccio all’object detection e tracking, abbiamo pensato di fare le cose nel modo piu` semplice possibile. Infatti, la tecnica utilizzata fa uso soltanto del colore, per individuare l’oggetto da tracciare: nel nostro caso un accendino. Questa modalita` e` tanto semplice quanto limitata, rispetto a tecniche piu` avanzate e complesse, che richiedono piu` potenza di calcolo; negli esperimenti futuri, certamente verranno sperimentate.

Lo script sviluppato fa essenzialmente i seguenti passaggi:

  1. cattura un frame dalla webcam
  2. crea una maschera per il colore dell’oggetto da tracciare, partendo dal range specificato in formato HSV
  3. usa la maschera per estrarre l’oggetto dal resto dell’immagine
  4. calcola l’area della superfice rilevata e se abbastanza grande esegue gli ultimi due punti
  5. calcola il centro della superfice
  6. se il centro calcolato e` vicino alle estremita` laterali del frame catturato, il bot gira rispettivamente a destra o a sinistra; se invece il centro e` vicino all’estremita` inferiore o superiore, il bot va avanti o indietro.

Imporre un limite minimo di dimensione dell’area rilevata e` utile per evitare falsi positivi. Inoltre, se l’oggetto da rilevare avesse una forma sferica, si potrebbe usare l’area misurata per rilevare se questo si allontana o si avvicina.

Per ridurre il rumore nell’immagine iniziale, prima del terzo passaggio, si potrebbe applicare dello smoothing. Ad esempio, si potrebbe usare un filtro di tipo Blur, che ovviamente richiede computazione aggiuntiva. Sarebbe anche possibile applicare dei filtri all’immagine binaria ottenuta al punto 3: Erosion e/o Dilation.

Il range HSV e` stato trovato manualmente, aiutandosi con il seguente codice:

import cv2.cv as cv
x = 0
y = 0
def on_mouse(event,x,y,flag,param):
if(event==cv.CV_EVENT_MOUSEMOVE):
globals()['x'] = x
globals()['y'] = y

cv.NamedWindow("Picker", 1)
frame = cv.CaptureFromCAM(1)
carattere = cv.InitFont(cv.CV_FONT_HERSHEY_SIMPLEX, 0.4, 0.9, 0, 2, 4)

while True:
img = cv.QueryFrame(frame)
hsv = cv.CreateImage(cv.GetSize(img), 8, 3)
cv.CvtColor(img, hsv, cv.CV_BGR2HSV)
cv.SetMouseCallback("Picker",on_mouse, 0);
valori=cv.Get2D(hsv,y,x)
cv.PutText(img,str(valori[0])+","+str(valori[1])+","+str(valori[2]), (x,y),carattere, (118,50,255))
cv.ShowImage("Picker", img)
print "Hue:",valori[0],"    Saturation:",valori[1],"     Value:",valori[2]

if cv.WaitKey(10) == 27:
break

Lo script sopra riportato e` essenzialmente un picker HSV per OpenCV.

Il codice Python che implementa le funzionalita` di cui si e` discusso prima e da` al robot capacita` visive e` a seguire:

import cv2
import cv2.cv as cv
from pyfirmata import Arduino, util
import numpy

######### get_delta determina se andare a destra o a sinistra
def get_delta(loc, span, centre_tolerance):
    framecentre = span/2
    delta = framecentre - loc
    if abs(delta) < centre_tolerance:
        delta = 0
    else:
    	if (delta < 0):
    		delta = 1
    	else:
    		delta = 2
    return delta

######### Arduino functions
board = Arduino('/dev/ttyACM0')

DELAY=0.04
DELAY2=0.1

DI = board.get_pin('d:10:o')
DA = board.get_pin('d:11:o')
SI = board.get_pin('d:12:o')
SA = board.get_pin('d:13:o')

def destra():
	SI.write(True)
	DI.write(False)
	DA.write(True)
	SA.write(False)
	board.pass_time(DELAY)
	stop()

def sinistra():
	SI.write(False)
	DI.write(True)
	DA.write(False)
	SA.write(True)
	board.pass_time(DELAY)
	stop()

def stop():
	SI.write(False)
	DI.write(False)
	DA.write(False)
	SA.write(False)

def indietro():
	SI.write(True)
	DI.write(True)
	DA.write(False)
	SA.write(False)
	board.pass_time(DELAY2)
	stop()

def avanti():
	SI.write(False)
	DI.write(False)
	DA.write(True)
	SA.write(True)
	board.pass_time(DELAY2)
	stop()

######## Inizio codice OpenCV
capture = cv.CaptureFromCAM(1) # seleziona camera

while True:
    img = cv.QueryFrame(capture) # cattura il frame

    hsv_img = cv.CreateImage(cv.GetSize(img), 8, 3) # converte l'immagine in HSV
    cv.CvtColor(img, hsv_img, cv.CV_BGR2HSV)
    thresholded_img =  cv.CreateImage(cv.GetSize(hsv_img), 8, 1) # immagine binaria
    cv.InRangeS(hsv_img, (40, 60, 50), (60, 180, 180), thresholded_img) # con i valori HSV trovati manualmente, "estrae" il colore desiderato

    moments = cv2.moments(numpy.asarray(cv.GetMat(thresholded_img)), 1) # calcola il momento
    area = moments['m00'] # calcola l'area rilevata

    if(area > 150): #controlla che l'area non sia troppo "piccola"
	# determina le cordinate x e y del centro dell'oggetto
	x = moments['m10']/area
	y = moments['m01']/area

       # determina se girare a destra o a sinistra
	delta1 = get_delta(int(x), img.width, 70)
	if(delta1 == 0):
		stop()
	elif (delta1 == 1):
		destra()
	else:
		sinistra()

       # determina se andare avanti o indietro
	delta2 = get_delta(int(y), img.height, 80)
	if(delta2 == 0):
		stop()
	elif (delta2 == 1):
		avanti()
	else:
		indietro()

Al di fuori della parte di codice relativa alla computer vision, il resto e` lo stesso del post precedente.
Ovviamente, per poter eseguire lo script e` necessario che sia installato OpenCV, che su Ubuntu e` disponibile gia` pacchettizzato.

Il video dimostrativo e` a seguire. Purtroppo i movimenti non fluidi del robot sono dovuti all’essenza dei regolatori di velocità sui motori, che presto installeremo.

Nei prossimi post, si indaghera` su come migliorare il lavoro svolto finora. STAY TUNED!!!