• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Recognizing Hand Gestures using WebCams
 

Recognizing Hand Gestures using WebCams

on

  • 859 views

Design and implementation of a new pointing system through the use of webcams and hand-tracking.

Design and implementation of a new pointing system through the use of webcams and hand-tracking.

Statistics

Views

Total Views
859
Views on SlideShare
855
Embed Views
4

Actions

Likes
0
Downloads
14
Comments
0

1 Embed 4

http://www.graphitech.it 4

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Recognizing Hand Gestures using WebCams Recognizing Hand Gestures using WebCams Document Transcript

    • ` UNIVERSITA DEGLI STUDI DI TRENTO Facolt` di Science Matematiche Fisiche e Naturali a Corso di Laurea in Informatica Principi di Computer Graphics Riconoscere i gesti della mano tramite WebCam Studenti: Massimiliano Bernab`e Andrea Gastaldello Anno accademico 2004–2005
    • 1 Descrizione del problema Il problema che ci ´ stato sottoposto consistema nella ideazione e realiz- e zazione di un nuovo sistema di puntamento che, utilizzando due webcam, potesse seguire il movimento della mano riproducendolo nel mondo 3D. Il sistema avrebbe dovuto anche riconoscere alcuni comandi dati con partico- lari posizioni della mano. Ci si prefiggeva l’obbiettivo di giungere ad avere due macchine distinte per la cattura del movimento e la visualizzazione del mondo 3D. 2 Stato dell’Arte Al momento in cui ci siamo applicati al problema esistevano gi` diversi a metodi per il riconoscimento della gestualit` della mano.Infatti erano gi` a a state realizzate varie tipologie di guanti, con sensori pi` o meno precisi per u il riconoscimento della posizione delle dita. Erano stati ideati, per esempio, alcuni metodi per il riconoscimento della gestualit` della mano in un’im- a magine, con algoritmi pi` o meno sofisticati che si basano sul confronto di u immagini. Interessante ´ il progetto che, nel 1997, ha consentito di arrivare e al risultato di implementare un algoritmo per il riconoscimento della ges- tualit` basato sulla forma della mano in 2 dimensioni. Il sistema ottenuto a permetteva di riprodurre movimenti della mano naturali riducendo il ru- more dovuto allo sfondo. Questi sistemi sono il frutto di ricerche che hanno diversi campi di applicazione,ad esempio potrebbero sviluppare un sistema di riconoscimento del linguaggio dei segni al servizio di persone con pro- blemi di handicap oppure potrebbero facilitare la comunicazione tra uomo e macchina. Queste sono le precedenti esperienze nel settore specifico di cui abbiamo trovato notizie, ma sicuramente esistono progetti simili. 3 Installazione Il software ´ un’estensione della libreria OpenTracker e le sue funzioni vi e sono incluse. Per l’installazione c’´ bisogno di alcune librerie di supporto e che sono le seguenti: ACE The Adaptive Communication Environment, a wrapper library for system services. ArToolKit ARToolKit applications allow virtual imagery to be superim- posed over live video of the real world.Although this appears magical it is not. The secret is in the black squares used as tracking markers. Coin3D-2 Is an OpenGL based, retained mode 3D graphics rendering library. 1
    • OpenCV Means Intel Open Source Computer Vision Library. It is a collec- tion of C functions and few C++ classes that implement some popular algorithms of Image Processing and Computer Vision. Xerces A validating XML parser library for C++ and Java. Dopo aver installato queste librerie, per ciascuna di esse si deve aggiun- gere una variabile d’ambiente che contiene il percorso nel quale si trova la libreria. Riporto un esempio di come settare le variabili: ACEROOT=C:GraphicsACE - Per ACE ARTOOLKITROOT=C:GraphicsArToolKit - Per ArToolKit COIN3DDI=C:GraphicsCoin3D-2 - Per Coin3D-2 OPENCVROOT=C:GraphicsOpenCV - Per OpenCV XERCESCROOT=C:GraphicsXerces - Per Xerces Devo anche aggiungere una variabile d’ambiente per OpenTracker che si chiama OTROOT alla quale devo assegnare “il path dove copio il mio software”opentraker Poi devo assegnare alla variabile d’ambiente PATH tutte le variabile sopra citate seguite da “bin”. Ora il sistema ´ installato e devo procedere con la configurazione me- e diante i due file XML come ´ spiegato nel paragrafo 4.1. e Il passo successivo all’installazione del software ´ il posizionamento delle e webcam. Per illustrare la posizione delle camere rispetto alla mano imma- giniamo un piano cartesiano: sull’asse delle ascisse disponiamo una web- cam, in grado quindi di riprendere dall’alto la mano, mentre la seconda la disponiamo sull’asse delle ordinate, quindi in grado di riprendere la mano lateralmente. Mentre le due camere risultano in uno spazio tridimensionale fisse e poste sullo stesso piano verticale, la mano si muove liberamente. 2
    • 4 Come si usa Per eseguire il sistema devono essere eseguite entrambi le sue parti: • il sistema di Tracking • il mondo 3D Entrambe le parti hanno una file di configurazione XML che contiene i settaggi dei software, vedremo in seguito come questi file sono fatti. Una volta compilato il file di configurazione le due parti del software possono essere eseguite da linea di comando passando come parametro proprio il file di configurazione. Esempio: > cvsample MyHandTracker.xml > world3d MyWorld3d.xml Per il tracker c’´ una sessione di configurazione da fare in cui si devono e settare i punti della mano in entrambe le finestre che vengono presentate, come in figura 1. Si deve settare la porzione di immagine che rappresenta og- Figura 1: Schermata di configurazione del sistama di tracking ni dito e il retro della mano. Per passare da una parte all’altra della mano si deve premere un tasto tra 1 e 6 e successivamente, evidenziando la porzione di immagine corrispondente alla parte di mano che si intende settare, come conferma appare una elisse che circoscrive la sezione. Dopo aver configurato tutti punti della mano premendo il tasto ’s’ si avvia il tracker e la comuni- ´ cazione con il software che modella il mondo 3D. E anche possobile tornare alla fase di configurazione in un secondo momento premendo il tasto ’c’. 3
    • Segue la sequenza completa di tasti utilizzati. 1 Retro della mano. 2 Pollice. 3 Indice. 4 Medio. 5 Anulare. 6 Mignolo. s Fine della configurazione. c Configurazione. ESC Termine del programma. 4.1 File di configurazione Le due parti del sistema necessitano di un file di configurazione. Iniziamo con l’esaminare il file di configurazione del sistema di tracking. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE OpenTracker SYSTEM "myClient.dtd"> <?xml-stylesheet type="text/xsl" href=".myClient.xsl"?> <OpenTracker> <configuration> <ConsoleConfig interval="1" headerline="webCam Tracker"/> <MyHandConfig /> </configuration> <ConsoleSink comment="MyHand Source 1" active="on" DEF="netSource1"> <NetworkSink name="myHandClient" number="0" multicast-address="localhost" port="12346"> <MyHandSource frontalCamPos="20 15 52" lateralCamPos="80.0 15.0 10.0" frontalPlane="42 30" lateralPlane="30 20" frontalMovie="./video/VideoSopra3.avi" lateralMovie="./video/VideoLato3.avi"/> </NetworkSink> </ConsoleSink> </OpenTracker> Figura 2: Esempio di file di configurazione per il sistema di tracking. Come possiamo notare nella figura 2, che rappresenta un esempio di file di configurazione per il sistema di tracking, esso ha una sola sezione nella quale viene settato l’indirizzo e la porta al quale spedire i dati e all’inter- no del tag MyHandSource i parametri di configurazione delle webcam e gli eventuali nomi dei file dei filmati. Gli attributi obbligatori frontalCamPos e lateralCamPos, rappresentano la posizione della camera orizzontale e ver- ticale rispetto al punto d’origine(vedi figura 3), frontalPlane e lateralPlane, 4
    • rappresentano, invece, le dimensioni del piano reale di riferimento orizzon- tale e verticale. Gli altri due attributi frontalMovie e latgeralMovie non sono obbligatori e rappresentano il nome del file contenente rispettivamente la ripresa dall’alto e quella laterale. Figura 3: Posizionamento delle camere rispetto all’origine Vediamo ora come ´ fatto il file di configurazione del Mondo 3D. Come e possiamo notare nella figura 4, che rappresenta un esempio di file di config- urazione per il mondo 3D, esso ´ diviso in tre parti principali evidenziate da e un asterisco: 1. l’indirizzo dell’host e la porta sulla quale riceve i dati; 2. il nome della callback che viene richiamata quando arriva un pachetto; 3. la definizione di un comando: assegnando un nome e la configurazione della mano che lo rappresenta, settandone il range valido di apertura delle dita. Sono stati implementati i seguenti comandi: CommandSelect Seleziona l’oggetto nelle vicinanze. CommandMove Sposta l’oggetto nelle vicinanze. CommandRGBChange Cambia il colore dell’oggetto nelle vicinanze. CommandScaleChange Cambia la scala dell’oggetto nelle vicinanze. 5
    • <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE OpenTracker SYSTEM "myWorld3d.dtd"> <?xml-stylesheet type="text/xsl" href=".myWorld3d.xsl"?> <!-- multicas addr: mettere indirizzo di world3d --> <OpenTracker> <configuration> <ConsoleConfig interval="1" headerline="World3D"/> </configuration> <!-- sorgente dei dati per il palmo della mano e delle dita --> <ConsoleSink comment="ALL Hand Source" active="off" DEF="incomingDataSource"> [*] <NetworkSource number="0" multicast-address="1.1.1.12" port="12346"/> </ConsoleSink> <!-- binding tra callback e sorgente dei dati --> <ConsoleSink comment="Hand Reference Node" active="off"> [*] <Callback name="IncomingDataSourceCB"> <Ref USE="incomingDataSource"/> </Callback> </ConsoleSink> <ConsoleSink comment="Hand Command Select" active="off"> [*] <HandCommand name="CommandSelect" thumbRateMin="0.0" thumbRateMax="0.4" indexfingerRateMin="0.0" indexfingerRateMax="0.4" middlefingerRateMin="0.0" middlefingerRateMax="0.4" ringfingerRateMin="0.0" ringfingerRateMax="0.4" littlefingerRateMin="0.0" littlefingerRateMax="0.4" /> </ConsoleSink> </OpenTracker> Figura 4: Esempio di file di configurazione per il mondo 3D. 6
    • 5 Architettura Il sistema ´ facilmente divisibile in due parti: e • la prima che utilizzando OpenCV cattura da WebCam i filmati e segue la mano calcolandone la posizione e i movimenti; • la seconda che rappresenta la mano nel mondo 3D e la fa muovere basandosi sui dati che riceve. Per far si che le due parti comunichino abbiamo utilizzato Opentracker, un particolare middleware che permette di far colloquiare due applicazioni sep- arate. Esso si basa su una architettura sviluppata per essere una flessibile soluzione per l’implementazione dei processi legati al tracking dei disposi- tivi di imput ed al flusso di dati utilizzati da ambientazioni virtuali. Questo ha permesso all’applicazione di funzionare tramite un flusso di dati gener- ici attraverso la rete e di essere in modo nativo trasparente alla stessa. E ´ anche possibile stabilire nuovi tipi di interazioni mediante la configurazione o l’estensione di file XML. Le nuove classi di OpenTracker, implementate per i nostri scopi, leggono i dati provenienti dalle WebCam, effettuano le necessarie trasformazioni, e inviano i dati cos` modificati verso l’altra ap- ı plicazione che utilizzando OpenTracker leggera i dati e li memorizzer` in a una classe che rappresenta lo stato della mano, questa classe verra poi us- ata dall’applicazione stessa che rappresenta la mano del mondo 3D.Come rappresentato nella figura 5. L’estensione della libreria di OpenTracker ´ e Figura 5: Architettura del Sistema avvenuta espandendo l’architettura ad albero per la gestione del flusso dati proveniente dal dispositivo di tracking con un modulo ad-hoc per il nostro dispositivo. 7
    • 5.1 Nuovo sistema di tracking Per il nuovo sistema di tracking abbiamo esteso la libreria di OpenTracker con un nuovo modulo. Questo modulo rappresenta un nodo nell’albero e viene configurato tramite XML. Come mostra la figura 6 il modulo ´ di- e viso in diverse classi: MyHandModule, che implementa nella loro struttura le funzioni necessarie alla costruzione della struttura dati, al filtraggio ed alla formattazione dei valori per il passaggio successivo verso la classe cor- rispondente, incaricata di fungere da contenitore dei dati, MyHandSource. Ci sono anche delle altre classi di supporto (MyHand, My3dPoint, MyPoint) alle quali il modulo delega di tracciare la mano nel mondo reale. Figura 6: Architettura del Sistema 8
    • 5.2 Architettura di World3d L’albero di scena del mondo virtuale pu´ essere scomposto in tre parti: o 1. gli assi 2. gli oggetti di scena 3. la mano Organizzati come in figura 7. Gli assi sono costituiti da 3 cilindri di diverso colore orientati opportunamente. Il soparator wSep contiene la parte del mondo virtuale sulla quale si pu´ agire: la mano e gli oggetti. o Figura 7: L’albero di scena Il Separator worldSeparator isola gli oggetti sui quali l’utente pu´ com- o piere operazioni: una sfera un cono e un cubo, ognuno con un colore carat- teristico e una matrice di rototraslazione che ne indica la posizione e l’ori- entamento (Figura 8). La mano che rappresenta nel mondo virtuale la mano dell’utente ´ a sua e volta composta da 7 parti(Figura 9): • il palmo, limitato da palmSeparator; • cinque dita, ognuna limitata da un separator; • una sfera, che serve per individuare le collisioni tra la mano e gli altri oggetti, la loro selezionatura. 9
    • Figura 8: L’albero di scena per gli oggetti del mondo virtuale Figura 9: L’albero di scena per la mano Le parti di scene graph che identifacano ogni dito e il palmo della mano hanno una struttura identica (Figura 10): viene applicato un nodo SoColor ad un ShapeKit. Il ShapeKit contine un nodo Complexity mediante il quale si pu´ variare la precisione nel renderizzare le strutture, la matrice dei punti o di controllo e una superficie NURBS. Figura 10: L’albero di scena per il palmo 10
    • 6 Problemi e soluzioni 6.1 Trovare le coordinate di un punto nello spazio reale Per trovare un punto nello spazio reale sono state utilizzate due funzioni ed una struttura dati apposita. Si calcolano due rette, ciascuna passante per una camera e per il punto che si prende in esame. Successivamento viene identificato il punto su ogni retta pi´ vicino all’altra retta, poi si calcola il u punto medio tra i due punti. Da file di configurazione vengono lette la posizione delle due telecamere nello spazio reale e le dimensioni del piano di riferimento. Le due telecamere devono essere localizzate in una posizione azzimutale rispetto al piano di riferimento. Chiamando la funzione calculateRealPos() si individuano i due punti sulle rette pi´ vicini e se ne calcola il punto medio. u struct myLine3d_s { int coordGen; bool IsFixed[3]; float FirstPoint[3]; float Coef[3]; float SecondPoint[3]; }; typedef struct myLine3d_s myLine3d; La struttura racchiude tutti i dati utili per valutare una retta nello spazio utilizzando un parametro di riferimento: int coordGen ´ la coordinata che genera le altre: 0=x 1=y 2=z; e bool IsFixed ´ un vettore che tiene traccia se ogni coordianta varia in e funzione della coordinata generatrice oppure ´ fissa; e float FirstPoint il vettore dei primi spostamenti: in 0:x, 1:y, 2:z; float Coef vettore dei coefficenti float SecondPoint il vettore dei secondi spostamenti: in 0:x, 1:y, 2:z. La formula che viene utilizzata per il calcolo della posizione di ogni punto su una retta data ´ la seguente (nel caso di coordinata generatrice z): e input : val p(x, y, z) c(x, y, z) l = X c − Xp m = Y c − Yp n = Z c − Zp (val − Zp ) l (val − Zp ) m output = + Xp , + Yp , val n n 11
    • Questa formula viene chiamata per le coordinate non fisse, all’interno del metodo evaluate della classe My3dPoint. void My3dPoint::evaluate(myLine3d line, float val, float pos[3]) { for(int i=0;i<3;i++) { if(i==line.coordGen){pos[i]=val;} else{ if(line.IsFixed[i]){ pos[i]=line.SecondPoint[i]; }else{ pos[i]=(val-line.FirstPoint[line.coordGen])* line.Coef[i]+line.SecondPoint[i]; } } } } Per assegnare correttamente i campi della struttura MyLine3d si deve chiamare la funzione buildLine3d della classe My3dPoint. Questa funzione ha come parametri 2 punti dello spazio reale, uno dato dalla posizione della telecamera, l’altro ´ dato dalla proiezione del punto reale sul piano indi- e viduato dalla camera. La struttura MyLine3d ´ stata ideata in modo che e sia possibile scorrere sulla retta determinata dai due punti, variando un parametro e la funzione buildLine costruisce la struttura in modo da gestire anche le linee parallele ad uno qualsiasi degli assi. bool My3dPoint::buildLine3d(myLine3d & line, float CameraPos[3],float PointPos[3]) { if(0.1f > fabs(CameraPos[coord_X]-PointPos[coord_X])){ if(0.1f > fabs(CameraPos[coord_Y]-PointPos[coord_Y])){ if(0.1f > fabs(CameraPos[coord_Z]-PointPos[coord_Z])){ cout<< "ERRORE, punti troppo vicini" <<endl;return false; }else{ cout<<"eq Retta: z=k"<<endl; line.coordGen=coord_Z; line.IsFixed[coord_X]=true; line.IsFixed[coord_Y]=true; line.IsFixed[coord_Z]=false; line.Coef[coord_X]=0.0f; line.Coef[coord_Y]=0.0f; line.Coef[coord_Z]=1.0f; line.FirstPoint[coord_X]=0.0f; line.FirstPoint[coord_Y]=0.0f; line.FirstPoint[coord_Z]=0.0f; line.SecondPoint[coord_X]=PointPos[coord_X]; line.SecondPoint[coord_Y]=PointPos[coord_Y]; line.SecondPoint[coord_Z]=0.0f; } }else{ if(0.1f > fabs(CameraPos[coord_Z]-PointPos[coord_Z])){ cout<<"eq Retta: y=k"<<endl; line.coordGen=coord_Y; line.IsFixed[coord_X]=true; line.IsFixed[coord_Y]=false; line.IsFixed[coord_Z]=true; line.Coef[coord_X]=0.0f; 12
    • line.Coef[coord_Y]=1.0f; line.Coef[coord_Z]=0.0f; line.FirstPoint[coord_X]=0.0f; line.FirstPoint[coord_Y]=0.0f; line.FirstPoint[coord_Z]=0.0f; line.SecondPoint[coord_X]=PointPos[coord_X]; line.SecondPoint[coord_Y]=0.0f; line.SecondPoint[coord_Z]=PointPos[coord_Z]; }else{ cout<<"eq Retta: ay + bz +c =0"<<endl; line.coordGen=coord_Z; line.IsFixed[coord_X]=true; line.IsFixed[coord_Y]=false; line.IsFixed[coord_Z]=false; line.Coef[coord_X]=0.0f; line.Coef[coord_Y]=(CameraPos[coord_Y]-PointPos[coord_Y]) /(CameraPos[coord_Z]-PointPos[coord_Z]); line.Coef[coord_Z]=1.0f; line.FirstPoint[coord_X]=0.0f; line.FirstPoint[coord_Y]=0.0f; line.FirstPoint[coord_Z]=PointPos[coord_Z]; line.SecondPoint[coord_X]=PointPos[coord_X]; line.SecondPoint[coord_Y]=PointPos[coord_Y]; line.SecondPoint[coord_Z]=0.0; } } }else{ if(0.1f > fabs(CameraPos[coord_Y]-PointPos[coord_Y])){ if(0.1f > fabs(CameraPos[coord_Z]-PointPos[coord_Z])){ cout<<"eq Retta x=k"<<endl; line.coordGen=coord_X; line.IsFixed[coord_X]=false; line.IsFixed[coord_Y]=true; line.IsFixed[coord_Z]=true; line.Coef[coord_X]=1.0f; line.Coef[coord_Y]=0.0f; line.Coef[coord_Z]=0.0f; line.FirstPoint[coord_X]=0.0f; line.FirstPoint[coord_Y]=0.0f; line.FirstPoint[coord_Z]=0.0f; line.SecondPoint[coord_X]; line.SecondPoint[coord_Y]=PointPos[coord_Y]; line.SecondPoint[coord_Z]=PointPos[coord_Z]; }else{ cout<<"eq Retta ax + bz + c =0"<<endl; line.coordGen=coord_Z; line.IsFixed[coord_X]=false; line.IsFixed[coord_Y]=true; line.IsFixed[coord_Z]=false; line.Coef[coord_X]=(CameraPos[coord_X]-PointPos[coord_X]) /(CameraPos[coord_Z]-PointPos[coord_Z]); line.Coef[coord_Y]=0.0f; line.Coef[coord_Z]=1.0f; line.FirstPoint[coord_X]=0.0f; line.FirstPoint[coord_Y]=0.0f; line.FirstPoint[coord_Z]=PointPos[coord_Z]; line.SecondPoint[coord_X]=PointPos[coord_X]; line.SecondPoint[coord_Y]=PointPos[coord_Y]; line.SecondPoint[coord_Z]=0.0; } }else{ if(0.1f > fabs(CameraPos[coord_Z]-PointPos[coord_Z])){ 13
    • cout<<"eq Retta ax + by + c = 0"<<endl; line.coordGen=coord_Y; line.IsFixed[coord_X]=false; line.IsFixed[coord_Y]=false; line.IsFixed[coord_Z]=true; line.Coef[coord_X]=(CameraPos[coord_X]-PointPos[coord_X]) /(CameraPos[coord_Y]-PointPos[coord_Y]); line.Coef[coord_Y]=1.0; line.Coef[coord_Z]=0.0f; line.FirstPoint[coord_X]=0.0f; line.FirstPoint[coord_Y]=PointPos[coord_Y]; line.FirstPoint[coord_Z]=0.0f; line.SecondPoint[coord_X]=PointPos[coord_X]; line.SecondPoint[coord_Y]=0.0f; line.SecondPoint[coord_Z]=PointPos[coord_Z]; }else{ cout<<"eq Retta ax + by + cz + d = 0"<<endl; line.coordGen=coord_Z; line.IsFixed[coord_X]=false; line.IsFixed[coord_Y]=false; line.IsFixed[coord_Z]=false; line.Coef[coord_X]=(CameraPos[coord_X]-PointPos[coord_X]) /(CameraPos[coord_Z]-PointPos[coord_Z]); line.Coef[coord_Y]=(CameraPos[coord_Y]-PointPos[coord_Y]) /(CameraPos[coord_Z]-PointPos[coord_Z]); line.Coef[coord_Z]=1.0f; line.FirstPoint[coord_X]; line.FirstPoint[coord_Y]; line.FirstPoint[coord_Z]=PointPos[coord_Z]; line.SecondPoint[coord_X]=PointPos[coord_X]; line.SecondPoint[coord_Y]=PointPos[coord_Y]; line.SecondPoint[coord_Z]=0.0f; } } } return true; } Infine la funzione calculateRealPos individua l’intersezione tra le due rette e, nel caso in cui le rette non si incrocino, trova il punto medio tra i punti pi´ vicini sulle rette. u 6.2 Costruire un dito e farlo piegare Un dito della mano visualizzata nel mondo 3d ´ formato da una NURBS, e la quale viene determinata in funzione di una matrice di punti di controllo e da due vettori di nodi, uno per le posizioni orizzontali della superficie e l’altro per le posizioni verticali della superficie. Nel scene graph ogni dito ´ delimitato da un SoSeparator e il sottoalbero e contiene un colore e un shapekit che identifica la superficie (Figura 11). I nodi vengono inseriti nell’albero di scena mediante la funzione buildfin- ger. SoSeparator * buildFinger(const float red,const float green,const float blue, const char controlPointsName[]){ const int UPOINTS = 7; 14
    • Figura 11: L’albero di scena per un dito const int VPOINTS = 5; float fingerKnotsU[UPOINTS + 4] = {0,0,0,0,1,2,3,4,4,4,4}; float fingerKnotsV[VPOINTS + 4] = {0,0,0,0,1,2,2,2,2}; SoSeparator * fingerSeparator = new SoSeparator; SoShapeKit * finger = new SoShapeKit; SoBaseColor *color=new SoBaseColor(); color->rgb.setValue(red,green,blue); SoComplexity *complexity = new SoComplexity; complexity->value = 1.0f; //0=veloce, 1=complesso SoCoordinate3 *controlPts = new SoCoordinate3; controlPts->setName(controlPointsName); SoNurbsSurface *surface = new SoNurbsSurface; surface->numUControlPoints = UPOINTS; surface->numVControlPoints = VPOINTS; surface->uKnotVector.setValues(0, (UPOINTS+4), fingerKnotsU); surface->vKnotVector.setValues(0, (VPOINTS+4), fingerKnotsV); finger->setPart("complexity",complexity); finger->setPart("coordinate3",controlPts); finger->setPart("shape",surface); fingerSeparator->addChild(color); fingerSeparator->addChild(finger); return fingerSeparator; Per costruire la matrice dei punti di controllo di un dito sono necessari soltanto 3 parametri: la posizione nel mondo 3d dell’inizio del dito, l’angolo rispetto all’asse X e un valore compreso tra 0 e 1 che indica di quanto il dito ´ piegato. e Vengono calcolati 3 livelli di framework per individuare i punti di con- trollo: • Il framework di livello 1 connette gli estremi di falange, falangina e falangetta. Al variare del parametro di apertura i punti del framework di livello 1 si spostano su circonferenze che hanno come centro il punto precedente. 15
    • • Il framework di livello 2 viene determinato partendo dal framework di livello 1. I punti si muovono su una circonferenza che ha come centro il punto di livello 1 e come raggio 1. La circonferenza ´ orientata secondo e l’orientamento del dito. Al variare del parametro di apertura i punti si muovono sulla circonferenza. • Il framework di livello 3 viene determinato utilizzando come base il framework di livello 2. I punti di terzo livello sono posizionati lat- eralmente rispetto ai punti di secondo livello. Essi si muovono su una circonferenza posta su un piano parallelo alla circonferenza su cui si spostano i punti di secondo livello. I dati cos´ calcolati vengono assegnati nella posizione corretta della ma- ı trice dei punti di controllo. Figura 12: Particolare delle dita const float red componente in rosso del colore del dito - parametro; const float green componente in verde del colore del dito - parametro; const float blue componente in blu del colore del dito - parametro; const char controlPointsName vettore che contiene il nome del nodo dei controlPoints della NURBS - parametro; 16
    • Figura 13: Particolare della mano const int UPOINTS numero dei punti di controllo orizzontali - locale; const int VPOINTS numero dei punti di controllo verticali - locale; float fingerKnotsU vettore dei nodi orizzontali - locale; float fingerKnotsV vettore dei nodi verticali - locale; Inizialmente si creano i vettori dei nodi in verticale e in orizzontale, il valore di ogni nodo in relazione agli altri indica quanto vicino si desidera che passi la curva rispetto al punto di controllo. I primi 4 nodi e gli ultimi 4 vengono settati allo stesso valore in quanto si desidera che la curva inizi e termini proprio nel punto di controllo. SoSeparator * modifyFinger (SoSeparator * fingerSeparator, float startPoint[3], float rotation, float openrate, const char controlPointsName[]){ float rAngle=rotation; float oAngle = (openrate) * M_PI / 2; float frameworkL1[3],frameworkL2[3],frameworkL3[3]; float toAngle; const int UPOINTS = 7; const int VPOINTS = 5; SoNode * tmpNode = fingerSeparator->getByName(controlPointsName); SoCoordinate3 * fingerControlPoints; 17
    • if(tmpNode->getTypeId() == SoCoordinate3::getClassTypeId()){ fingerControlPoints = (SoCoordinate3*) tmpNode; }else{ cout<<"ERRORE: Finger Control Points non trovati"<<endl; return fingerSeparator; } int i; for(i=0;i<4;i++) { //parte variabilie if(i==0){ toAngle = M_PI *3/2 + oAngle; frameworkL1[0]=startPoint[0]; frameworkL1[1]=startPoint[1]; frameworkL1[2]=startPoint[2]; }else if (i==1) { toAngle = M_PI *3/2 + oAngle*1.5; frameworkL1[0]+=sin(oAngle)*5; frameworkL1[1]+=cos(oAngle)*cos(rAngle)*5; frameworkL1[2]+=cos(oAngle)*sin(rAngle)*5; }else if (i==2) { oAngle += (openrate) * M_PI / 2; toAngle = M_PI *3/2 + oAngle*1.25; frameworkL1[0]+=sin(oAngle)*4; frameworkL1[1]+=cos(oAngle)*cos(rAngle)*4; frameworkL1[2]+=cos(oAngle)*sin(rAngle)*4;} else { oAngle += (openrate) * M_PI / 2; toAngle = M_PI *3/2 + oAngle; frameworkL1[0]+=sin(oAngle)*3; frameworkL1[1]+=cos(oAngle)*cos(rAngle)*3; frameworkL1[2]+=cos(oAngle)*sin(rAngle)*3;} //parte fissa frameworkL2[0]=frameworkL1[0]+sin(toAngle); frameworkL2[1]=frameworkL1[1]+cos(toAngle)*cos(rAngle); frameworkL2[2]=frameworkL1[2]+cos(toAngle)*sin(rAngle); fingerControlPoints->point.set1Value((UPOINTS*i+0),frameworkL2); frameworkL3[0]=frameworkL2[0]; frameworkL3[1]=frameworkL2[1]+cos(rAngle+M_PI/2); frameworkL3[2]=frameworkL2[2]+sin(rAngle+M_PI/2); fingerControlPoints->point.set1Value((UPOINTS*i+1),frameworkL3); frameworkL3[0]=frameworkL2[0]; frameworkL3[1]=frameworkL2[1]+cos(rAngle-M_PI/2); frameworkL3[2]=frameworkL2[2]+sin(rAngle-M_PI/2); fingerControlPoints->point.set1Value((UPOINTS*i+5),frameworkL3); frameworkL2[0]=frameworkL1[0]+sin(toAngle+M_PI); frameworkL2[1]=frameworkL1[1]+cos(toAngle+M_PI)*cos(rAngle); frameworkL2[2]=frameworkL1[2]+cos(toAngle+M_PI)*sin(rAngle); fingerControlPoints->point.set1Value((UPOINTS*i+3),frameworkL2); frameworkL3[0]=frameworkL2[0]; frameworkL3[1]=frameworkL2[1]+cos(rAngle+M_PI/2); frameworkL3[2]=frameworkL2[2]+sin(rAngle+M_PI/2); fingerControlPoints->point.set1Value((UPOINTS*i+2),frameworkL3); frameworkL3[0]=frameworkL2[0]; frameworkL3[1]=frameworkL2[1]+cos(rAngle-M_PI/2); frameworkL3[2]=frameworkL2[2]+sin(rAngle-M_PI/2); fingerControlPoints->point.set1Value((UPOINTS*i+4),frameworkL3); frameworkL2[0]=frameworkL1[0]+sin(toAngle); frameworkL2[1]=frameworkL1[1]+cos(toAngle)*cos(rAngle); frameworkL2[2]=frameworkL1[2]+cos(toAngle)*sin(rAngle); fingerControlPoints->point.set1Value((UPOINTS*i+6),frameworkL2); 18
    • } if(i==4){ for(int k=0;k<7;k++){ fingerControlPoints->point.set1Value((UPOINTS*i+k),frameworkL1); } } return fingerSeparator; } SoSeparator * fingerSeparator separator del dito - parametro; float startPoint vettore che racchiude le coordinate del punto da dove inizia il dito - parametro; float rotation angolo di rotazione del dito in radianti - parametro; float openrate indica il grado di apertura del dito, se il dito ´ comple- e tamente chiuso il parametro varr´ 0.0, mentre, se ´ completamente a e aperto, varr´ 1.0 - parametro; a char controlPointsName vettore che contiene il nome del nodo dei con- trolPoints della NURBS - parametro; float rAngle angolo di rotazione del dito rispetto all’origine del dito stesso - locale; oAngle angolo di apertura, in radianti - locale; frameworkL1 scheletro di guida di livello 1 per punti di controllo - locale; frameworkL2 scheletro di guida di livello 2 per punti di controllo - locale; frameworkL3 scheletro di guida di livello 3 per punti di controllo - locale; toAngle angolo di apertua temporaneo - locale const int UPOINTS numero dei punti di controllo orizzontali - locale; const int VPOINTS numero dei punti di controllo verticali - locale; Inizialmente si recupera dall’albero di scena la matrice dei punti di controllo, e poi si controlla che il cast sia corretto. Ogni frame di livello 1 ´ spostato dal precedente dipendentemente dal- e l’angolo di rotazione del dito e dall’angolo di apertura del dito. I frame di livello 2 e di livello 3 vengono immessi nella matrice dei punti di controlli nella posizione opportuna. Per chiudere il dito nella parte terminale vengono settati tutti i punti di controllo al framework di livello 1. Infine la funzione ritorna il separator del dito modificato. 19
    • 6.3 Riconoscere un comando Modificando le impostazione del file di configurazione ´ possibile customiz- e zare i comandi che vengono impartiti al software. Basta inserire un tag HandCommand impostando gli attributi • name • thumbRateMin • thumbRateMax • indexfingerRateMin • indexfingerRateMax • middlefingerRateMin • middlefingerRateMax • ringfingerRateMin • ringfingerRateMax • littlefingerRateMin • littlefingerRateMax. L’attributo name indica il nome del comando, i comandi supportati sono CommandSelect, CommandMove, CommandRGBChange e Command- ScaleChange, mentre, tramite gli altri attributi, ´ possibile modificare il e range valido di apertura di ogni dito. Le configurazioni lette nel file di input vengono memorizzate in un’is- tanza della classe HandStatus. Chiamando poi il metodo testCommand() ´ e possibile riconoscere se la conformazione della mano ´ compatibile con un e comando. 6.4 Spostamento e rotazione della mano Per applicare delle rotazioni e delle traslazioni alla rappresentazione della mano nel mondo 3d si ´ scelto di inserire nell’albero di scena, prima della e rappresentazione della mano, un nodo SoMatrixTransform. Ogni qualvolta la posizione o la rotazione della mano cambia, mediante le funzioni cange- HandPosition() e cangeHandRotation(), la matrice di rototraslazione viene scomposta, modificata in modo opportuno e successivamente ricomposta. La classe handStatus ´ rappresentativa dello stato della mano. In essa e vengono memorizzate le aperture delle dita,la posizione della mano e la sua rotazione. Vengono anche memorizzati i comandi che la mano pu´ compiere. o 20
    • Tramite chiamata al metodo insertCommand() ´ possibile inserire un nuovo e comando, mentre chiamando testCommand, si controlla se la conformazione della mano ´ compatibile con un comando, se il test ´ positivo viene ritornato e e il nome del comando, in caso contrario ritorna una stringa vuota. class handStatus { private: int nCommand; SbString cmdName[30]; float vecCommand[30][10]; public: handStatus(); virtual ~handStatus(); float handPosition[3]; float angle; float deltaAngle; float thumbRate; float indexfingerRate; float middlefingerRate; float ringfingerRate; float littlefingerRate; void setData(const State & event); bool insertCommand(const char name[50], float thumbRateMin, float thumbRateMax, float indexfingerRateMin, float indexfingerRateMax, float middlefingerRateMin, float middlefingerRateMax, float ringfingerRateMin, float ringfingerRateMax, float littlefingerRateMin, float littlefingerRateMax ); SbString testCommand(); }; La funzione cangeHandPosition() serve per modificare la posizione della mano nel mondo virtuale, secondo lo status memorizzato in un’istanza della classe HandStatus. La matrice di rotazione della mano viene scomposta nelle sue componenti di traslazione, rotazione, scala e orientamento della scala, poi si assegna alla traslazione il nuovo valore. La matrice di rototraslazione viene successivamente ricomposta e aggiornata. void cangeHandPosition(){ SbMatrix *m_tmp = new SbMatrix(handMatrixTransform->matrix.getValue()); SbVec3f t; SbRotation r; SbVec3f s; SbRotation so; m_tmp->getTransform(t,r,s,so); t[0]=myHandStatus->handPosition[0]; t[1]=myHandStatus->handPosition[1]; t[2]=myHandStatus->handPosition[2]; if((t[0]<-0.1f)||(t[1]<-0.1f)||(t[2]<-0.1f)){return;} cout << "T:" << t[0] << " " << t[1] << " " << t[2] << endl; m_tmp->setTransform(t,r,s); 21
    • handMatrixTransform->matrix.setValue(*m_tmp); } Mediante la funzione cangeHandRotation() ´ possibile variare l’angolo di e rotazione della mano nel mondo virtuale. Il funzionamento ´ molto simile a e cangeHandPosition(), con l’unica differenza che si modifaca la componente di rotazione della matrice di rotatraslazione invece che la componente di traslazione. void cangeHandRotation(){ SbMatrix *m_tmp = new SbMatrix(handMatrixTransform->matrix.getValue()); SbVec3f t,s; SbRotation r,so; m_tmp->getTransform(t,r,s,so); SbRotation rr; rr.setValue(SbVec3f(1, 0, 0),myHandStatus->deltaAngle); myHandStatus->deltaAngle=0.0f; SbMatrix sb1; SbMatrix sb2; rr.getValue(sb1); r.getValue(sb2); sb1.multRight(sb2); SbVec3f t_tmp, s_tmp; SbRotation so_tmp; sb1.getTransform (t_tmp, r, s_tmp, so_tmp); m_tmp->setTransform(t,r,s); handMatrixTransform->matrix.setValue(*m_tmp); } 6.5 Ricostruire i punti di una mano nel caso in cui non sia possibile trovare la posizione delle dita o del palmo Nel caso in cui dall’analisi dell’immagine ottenuta dalle telecamere non sia possibile identificare la posizione di alcune dita il software provvede a calco- larne una posizione approssimata. Il calcolo avviene utilizzando la posizione del palmo e traslandosi nello spazio in una posizione opportuna. I punti possono essere ricostruiti anche se nessun dito ´ visibile. e Nel caso in cui sia il palmo della mano a non venir riconosciuto dal software, la posizione approssimata viene calcolata utilizzando la posizione dell’indice, in quanto da test effettuati questa ´ risultata la pi´ affidabile. e u La chiamata al metodo getPoint della classe mHand d´ come risultato la a posizione di un dito passato come parametro. Esso controlla che la posizione del dito non sia irreperibile e in quel caso la ritorna, altrimenti la approssima. My3dPoint* MyHand::getPoint(handEnum h){ switch( h ){ case HAND: if(!hand->isLost()) return hand; if(!indexFinger->isLost()){hand->setRect(indexFinger, -0.13f, 0.25f, 0.0f);} return hand; break; case THUMB: if(!thumb->isLost())return thumb; if(!hand->isLost()){thumb->setRect(hand, 0.2f, -0.15f, 0.1f);} return thumb; 22
    • break; case INDEXFINGER: if(!indexFinger->isLost())return indexFinger; if(!hand->isLost()){indexFinger->setRect(hand, 0.13f, -0.25f, 0.0f);} return indexFinger; break; case MIDDLEFINGER: if(!middleFinger->isLost())return middleFinger; if(!hand->isLost()){middleFinger->setRect(hand, 0.05f, -0.25f, 0.0f);} return middleFinger; break; case RINGFINGER: if(!ringFinger->isLost())return ringFinger; if(!hand->isLost()){ringFinger->setRect(hand, 0.0f, -0.25f, 0.0f);} return ringFinger; break; case LITTLEFINGER: if(!littleFinger->isLost())return littleFinger; if(!hand->isLost()){littleFinger->setRect(hand, -0.02f, -0.25f, 0.0f);} return littleFinger; break; default: ; } return NULL; } 6.6 Calcolare quanto un dito ´ aperto e Per calcolare quanto un dito ´ aperto si confrontano le dimensioni dell’ellisse e del palmo della mano con le dimensioni delle ellissi delle dita. Nel caso in cui un dito non sia visibile, il software considera il dito come chiuso, l’ellisse del dito avr´ quindi dimensione minima. a La classe che si occupa di calcolare gli openrate delle dita ´ My3dPoint e e il metodo da chiamare ´ getOpenRate passando un dito come parametro. e float My3dPoint::getOpenRate(My3dPoint *point){ CvRect ellipse = frontalPoint->getEllipse(); CvRect handEllipse = point->getFrontalEllipse(); if(handEllipse.height != 0){ float tmp = ( ellipse.height ) / ( handEllipse.height/2 ); if(tmp > 1.0f) tmp = 1.0f; return tmp; }else return -1.0f; } 23