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!!!

Riprendendo il discorso del post precedente, stiamo continuando gli esperimenti con il robot. Sempre nell’ottica d’introdurre la computer vision, per fare object detection, abbiamo equipaggiato il bot con un netbook Aspire One della Acer e una webcam esterna. Questa aggiunta consente al robot di avere piu` potenza di calcolo, una connessione WIFI e di superare molti dei limiti che si hanno con il solo uso di Arduino: un aumento di versatiita` che consente di avere maggiori applicazioni.

GigginoBot + Aspire One + Webcam

GigginoBot + Aspire One + Webcam

L’idea e` quella di usare il PC come cervello, su cui far girare tutto il software che permette di prendere decisioni e di comunicare con il mondo, rendendo Arduino solo una mera interfaccia con  motori e sensori vari. Il PC e` collegato ad Arduino tramite cavo USB. Ovviamente, i programmi che controlleranno il bot saranno scritti in Python 😉

Ma come far interfacciare Python con Arduino??? si potrebbe progettare una soluzione personalizzata, come e` stato fatto per GraphDuino. Ma li` le cose erano molto semplici… qui, andando avanti nel progetto, le cose potrebbero diventare piu` complesse. Serve una soluzione che possa essere scalabile. Perche` reinventare la ruota??? Esiste gia` qualcosa che fa al caso nostro: Firmata. Questo protocollo consente di avere il controllo di Arduino, attraverso qualsiasi software che lo implementa. Per giunta, esistono gia` librerie Python che lo implementano…

Lato Python, la scelta della libreria e` ricaduta su pyFirmata, in quanto sembra essere regolarmene mantenuta e supporta versioni recenti del protocollo Firmata. Per installare la libreria basta seguire le istruzioni riportate sul sito. Lato Arduino, nelle ultime versioni dell’IDE,  Firmata e` disponibile out of the box: e` uno sketch che basta compilare e caricare.

Firmata - Arduino IDE

Firmata – Arduino IDE

Per testare questo nuovo connubio,  si e` scelto di rendere GigginoBot controllabile da remoto, sfruttando la connessione dati WIFI. Inoltre, e` stata usata la webcam mandando in streaming le immagini riprese, rendendo il robot controllabile, anche laddove non ci fosse contatto visivo.

Per comandare i movimenti del robot, si e` pensato di usare i tasti di direzione. Per implementare questa funzionalita`, senza perdere tempo nella realizzazione di interfacce grafiche, si e` fatto uso del modulo Curses. A seguire vi e` lo script Python completo e commentato.

#import moduli
import curses
from pyfirmata import Arduino, util

stdscr = curses.initscr()
curses.cbreak() # input senza invio
stdscr.keypad(1)
curses.halfdelay(1) # timeout per evitare che getch() resti in attesa di input

#messaggio di benvenuto
stdscr.addstr(0,10,"GigginoBot RC Command Utility")
stdscr.addstr(1,10,"...premi 'q' per uscire! o i tasti di direzione per muovere il bot :-)")
stdscr.refresh()

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

#setup pin digitali, per controllo motori
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')

#funzioni per gestione movimento
def destra():
    SI.write(True)
    DI.write(False)
    DA.write(True)
    SA.write(False)

def sinistra():
    SI.write(False)
    DI.write(True)
    DA.write(False)
    SA.write(True)

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)

def avanti():
    SI.write(False)
    DI.write(False)
    DA.write(True)
    SA.write(True)

key = ''
while key != ord('q'): #cicla fin quando l'utente non digita il tasto 'q'
    key = stdscr.getch() #cattura tasti
    if key == curses.KEY_UP:
        avanti()
    elif key == curses.KEY_DOWN:
        indietro()
    elif key == curses.KEY_RIGHT:
        destra()
    elif key == curses.KEY_LEFT:
        sinistra()
    else:    #se l'utente non preme nessun tasto
        stop()

curses.endwin()

La vera e propria capacita` di controllo remoto e` stata fatta via SSH. Si accede tramite la Secure Shell al PC del robot, e si lancia lo script, che gira in locale.

Per lo streamig del flusso video della webcam, e` stato usato MJPG-streamer. Per installarlo su Ubuntu e` necessario seguire i seguenti passi:

svn co https://mjpg-streamer.svn.sourceforge.net/svnroot/mjpg-streamer mjpg-streamer
cd mjpg-streamer/mjpg-streamer
make clean all
export LD_LIBRARY_PATH=.

Probabilmente, prima di riuscirlo a compilare,  sara` anche necessario installare i seguenti pacchetti:

sudo apt-get install libjpeg-dev imagemagick

Per lanciarlo:

sudo mjpg_streamer -i "./input_uvc.so  -d /dev/video0  -y -f 15 -n -r 160x120" -o "./output_http.so -w ./www"

Fatto cio`, ci si potra` collegare tramite un browser alla macchina, specificando la porta di ascolto del server e vedere lo stream video!

Voila`! Abbiamo realizzato un “giocattolo” che puo` aiutarci a tenere d’occhio la casa, quando non ci siamo. Infatti esponendo il PC del bot su Internet, potremmo gestirlo da qualunque posto.

Siamo molto pigri. Questa cosa e` accertata. E` per questo che parlando tra noi, un giorno, abbiamo deciso di avventurarci nella costruzione di un robot. Un robot vassoio, che possa portare le bevande e i cibi dalla cucina fino al divano del soggiorno. Abbiamo fatto due conti e due ricerche e abbiamo compreso quanto difficile possa essere realizzare un “giocattolo” del genere. I problemi sono tanti. In particolare, quelli legati allo SLAM, ovvero la capacita` del robot di autocostruirsi una mappa dell’ambiente, localizzarsi all’interno di essa e navigare autonomamente, magari facendo tutto questo su una piattaforma embedded.

Abbiamo cosi` deciso di iniziare da qualcosa di piu` semplice, tenendo in mente l’obbiettivo finale. Una serie di prototipi, per iniziare a giocare un po` con i sensori, ci hanno aiutato a comprendere meglio il pasticcio in cui ci stavamo imbrigliando.

E` nato cosi` GigginoBot 1.0, unendo Arduino Uno e un sensore SR04 ad ultrasuoni alle parti di un vecchio robot che avevamo.

GigginoBot

GigginoBot

Con uno sketch di poche decine di linee e sfruttando la libreria NewPing, il piccolo bot riusciva a muoversi evitando molti degli oggetti che trovava sul proprio cammino. Ovviamente, aveva tanti limiti. Un solo sensore ad ultrasuoni ha una visione di pochi gradi ed e` poco performante nel rilevare superfici che non lasciano rimbalzare l’ultrasuono. Inoltre, la struttura del robot imponeva forti limiti di spazio, che ci avrebbero creato problemi, qualora avessimo voluto introdurre la computer vision. Lo spazio richiesto per l’inserimento di una camera e di  hardware piu` potente, oltre che alla relativa alimentazione, ci suggeriva di aumentare le dimensioni.

GigginoBot 2.0

GigginoBot 2.0

Partendo da una cassa di legno, che inizialmente conteneva tre pregiate bottiglie di vino, ha preso forma GigginoBot 2.0, la naturale evoluzione del primo prototipo. Nella parte inferiore della cassa, sono state applicate le tre ruote. Due sono motrici ed una, quella anteriore, e` in grado di ruotare lungo il suo asse grazie ad un cuscinetto a sfere.

GigginoBot 2.0: ruote e motori

GigginoBot 2.0: ruote e motori

La visione frontale e laterale e` affidata a due sensori a ultrasuoni, ancorati a due staffe di metallo e resi mobili tramite l’ausilio di due servomotori. La mobilità dei sensori consente di avere un maggiore angolo di visuale, rispetto ad una configurazione non mobile, oltre alla possibilita` di usare i sensori per i rilevamenti laterali, senza necessita` di far muovere l’intero corpo  del bot. Le limitazioni della tecnologia ad ultrasuoni, impongono l’uso di un sensore ad infrarossi. Questo e` stato posizionato  nella paratia frontale ed e` fondamentale per la rilevazione di corpi invisibili agli ultrasuoni

GigginoBot2.0: sensori ausiliari

GigginoBot2.0: sensori ausiliari

Sono stati aggiunti anche un sensore PIR, un DHT11 di umidita` e temperatura, uno di luminosita`, uno di suono e un ulteriore sensorore ad infrarossi utile per il line following e per rilevare la presenza di gradini da evitare. L’interno della scatola, racchiude Arduino, un accellerometro a tre assi e i due pacchi batteria, rispettivamente per l’alimentazione dei motori e dell’Arduino.

GigginoBot 2.0: parte interna

GigginoBot 2.0: parte interna

Lo sketch e` di circa 400 linee di codice. Allo stato attuale il bot utilizza solo una parte dei sensori: i due ad ultrasuoni, l’infrarosso centrale, quello di temperatura. Tutta la programmazione e` stata fatta intorno alla libreria NewPing, che permette di strutturare con facilita` un programma per la navigazione autonoma, basata sulla rilevazione di piu` sensori e con l’ausilio degli interrupt di Arduino. Attivato il bot tramite il pulsante di accensione, esso funziona nel modo seguente:

  1. viene rilevata la temperatura dell’ambiente, che verra` usata per migliorare i calcoli relativi alla rilevazione ad ultrasuoni, poiche` la velocita` del onda emessa dal sensore varia al variare della temperatura dell’aria;
  2. il bot inizia ad andare avanti mentre l’infrarosso centrale e gli ulltrasuoni rilevano anterirmente la presenza di ostacoli. Gli ultrasuoi compiono una rotazione di 30 gradi, assicurando una visione anteriore completa;
  3.  se viene rilevato un ostacolo da uno dei  due sensori ad ultrasuoni, il bot vira nella direzione opposta, fin quando entrambi i sensori piu` l’ultrarossi non rilevano via libera.
  4. se tutti e tre i sensori di visione frontale rilevano un ostacolo, il bot ruota i sensori ad ultrasuono in modo da opporli l’uno con l’altro ed avere una visione laterale. In questa configurazione  valuta quale lato abbia maggior mobilita`, dopodiche` riposiziona i sensori anteriormente e inizia a girare verso quella direzione, fin quando non avra` sufficente spazio per andare avanti.

Un video dimostrativo e` a seguito.

La prossima evoluzione prevede l’uso della computer vision, per identificare un volto e poi seguirlo. STAY TUNED! 🙂

Tre anni fa, quasi per gioco, un noto capo officina delle nostre ben amate terre, ci chiese se fosse possibile estrapolare da un shock absorber testing machine obsoleto, un grafico che mostrasse,  in funzione di due variabili (escursione e forza-peso), il comportamento dinamico di un ammortizzatore. La macchina in questione, la “Precisa” della Emmetec, restituiva solo i valori massimi e minimi in kg/f relativi dell’ammortizzatore, letti attraverso una cella di carico.

Emmetec Precisa

Emmetec Precisa

Per raggiungere il nostro abbiettivo, ovvero tracciare un grafico relativo al funzionamento della sospensione, era necessario un secondo dato:  l’estenzione della sospensione. Tale dato e` stato ottenuto attraverso l’installazione di un potenziometro lineare. Questa configurazione ci ha permesso di mettere in relazione i valori fornitici dalla cella di carico con i dati relativi all’estenzione.

Potenziometro in dettaglio

Potenziometro in dettaglio

Potenziometro lineare

Potenziometro lineare

Veniamo all’elettronica utilizzata, e ai sui collegamenti con Arduino. Per leggere l’escursione della sospensione, come e` stato gia` detto precedentemente, abbiamo utilizzato un potenziometro lineare. Arduino non fa altro che leggere la differenza di potenziale, che varia al variare della resistenza interna del potenziometro, restituendoci con opportuni calcoli, quella che e` la posizione della sospensione.

Cella di Carico

Cella di Carico

Per la cella di carico il discorso e` leggermente diverso, in quanto la load-cell non e` altro che un ponte di wheatstone. Senza dilungarci in nozioni di elettrotecnica, basta sapere che, come il potenzionetro, il ponte di wheatstone restituisce una differenza di potenziale, variabile al variare della flessione della cella di carico. Il problema e` che le variazioni di corrente espresse in mV non sono interpretabili da Arduino.

Elettronica

Elettronica

Come abbiamo risolto questo problema? Il problema e` stato risolto inserendo tra Arduino e la cella di carico un circuito amplificatore. La scelta tra le tante pensate e` ricaduta su un circuito integrato INA125p della Texas Instruments. La particolarita` di questo integrato sta nel fatto che la circuiteria di controllo e` pressapoco nulla, e che non necessita di alimentazione esterna, fonte di rumore, e di altri componenti inutili! Questo ci ha permesso di ridurre al minimo il rumore e di realizzare un sistema ad hoc! Per tutti i collegamenti basta guardare la figura.

Collegamenti con Arduino

Collegamenti con Arduino

Il potenziometro e la cella di carico sono stati quindi collegati ad Arduino, che leggendo i valori li comunica tramite seriale ad un PC, sul quale un applicativo scritto in Python  grafica i dati rilevati.

GraphDuino

GraphDuino

Lo sviluppo dell’applicazione “GraphDuino” e` partito da un semplice script, che permetteva di acquisire i dati e graficarli mediante l’uso della libreria Matplotlib, fino ad arrivare a un’applicazione piu` complessa dotata di GUI in TKinter. L’ultima versione di GraphDuino e` in grado di:

  • acquisire tramite porta seriale i valori inviati da Arduino in formato x#y
  • visualizzare un grafo elaborando i dati acquisiti, convertendoli dalla forma “raw”, inviata da Arduino, a dati reali mediante un’opportuna formula
  • visualizzare i valori di massimo e minimo in kg/f
  • sattare il tipo di sospensione (fork o mono)
  • comparare grafici.
  • salvare i dati raw, un’immagine del grafico, un pdf con il grafico e i dati della sospensione testata
GraphDuino

GraphDuino