This is an old revision of the document!
Proiectul presupune crearea pe un ecran LCD a unui stickman care sa imite mișcările unei persoane aflate in fata unui XBOX 360 Kinect.
Consider ca proiectul meu reprezintă un POC pentru o idee care ar putea fi aplicată pe semafoarele din zonele turistice urmând:
Au fost deja implementate astfel de proiecte având efectele precizate anterior. Aveți un exemplu aici.
Am ales acest proiect din următoarele motive:
Pași de funcționare a proiectului:
Proiectul este alcătuit din următoarele componente:
Descriere a asamblării:
De menționat:
Pentru implementarea funcționalității am utilizat următoarele tehnologii (medii de dezvoltate, limbaje de programare și librării):
Deoarece Kinect-ul si plăcuța Arduino sunt conectate prin intermediul laptop-ului, vom avea pe lângă dancing_stickman.ino
, pentru Arduino, și dancing_stickman.pde
, un program scris în Processing, prin intermediul căruia Kinect-ul poate comunica cu plăcuța folosind USART.
Pentru Arduino nu trebuie să facem nimic diferit, în Processing însă va trebui să accesăm portul serial astfel:
import processing.serial.*; Serial port; void setup() { String portName = Serial.list()[SERIAL_PORT_INDEX]; port = new Serial(this, portName, BAUD_RATE); }
SERIAL_PORT_INDEX
trebuie încercate mai multe valori prin trial and error pentru a descoperi valoarea care corespunde port-ului serial. Majoritatea tutorialelor de pe internet folosesc 0, dar în cazul meu am folosit 2.
Dacă setup-ul este corect, apelul funcției port.write()
din Processing ar trebui să trimită octeți către Arduino pe care să îi recepționeze cu funcția Serial.read()
.
Dorim ca Kinect-ul să detecteze scheletul user-ului și gesturile acestuia. Pentru a utiliza aceste funcționalități ale Kinect-ului, vom avea nevoie și de matricea de adâncime (depth map), care calculează distanța de la Kinect la tot ceea ce vede utilizând două camere (RGB și IR) și un proiector de raze infraroșii. Aveți aici o explicație cu reprezentare vizuală.
Exemplu de harta de adâncime. Observați faptul ca brațul stâng apare negru, deși este foarte aproape de cameră, deci ne-am aștepta să apară alb. Acest fenomen este cauzat de felul în care Kinect-ul se folosește de razele infraroșii pentru a calcula distanța până la obiecte. Distanța la care Kinect-ul este cel mai eficient este de obicei între 1.5 și 4 metri.
Kinect-ul este inițializat astfel:
import SimpleOpenNI.*; SimpleOpenNI kinect; void setup() { kinect = new SimpleOpenNI(this); kinect.enableDepth(); // activează matricea de adâncime kinect.enableUser(); // activează recunoașterea utilizatorilor (recunoașterea corpului) kinect.enableHand(); // activează recunoașterea gesturilor kinect.startGesture(SimpleOpenNI.GESTURE_WAVE); // activează recunoașterea gestului WAVE /* se pregătește fereasta în care vom afișa matricea de adâncime */ size(KINECT_WIDTH, KINECT_HEIGHT); // KINECT_WIDTH = 640, KINECT_HEIGHT = 480 fill(255, 0, 0); } void draw() { /* actualizează fereastra cu noua matrice de adâncime */ kinect.update(); image(kinect.depthImage(), 0, 0); }
Activarea acestor funcționalități vor declanșa si apelul funcțiilor de callback onNewUser
, onLostUser
, onCompletedGesture
, care vor fi folosite pentru etapa de calibrare și lansare a recunoașterii corpului.
Pentru detectarea corpului utilizatorului, Kinect-ul recunoaște 15 puncte cu care formeaza scheletul: cap, gât, doi umeri, două coate, două mâini, trunchi, două șolduri, doi genunchi, două tălpi.
Exemplu de recunoaștere a scheletului peste matricea de adâncime.
Obținerea unor coordonate 2D la aceste puncte este trivială. Folosind biblioteca SimpleOpenNI
se utilizează funcția getJointPositionSkeleton
pentru fiecare punct pentru a obtine poziția lui în spațiul 3D, apoi se proiectează la 2D folosind convertRealWorldToProjective
.
PVector getProjectiveJoint(int userID, int jointID) { /* Obține coordonatele 3D a punctului de pe schelet */ PVector joint = new PVector(); float confidence = kinect.getJointPositionSkeleton(userID, jointID, joint); // confidence poate fi folosit pentru verificarea preciziei rezultatelor /* Convertește la spațiul 2D calculând proiecția */ PVector projectiveJoint = new PVector(); kinect.convertRealWorldToProjective(joint, projectiveJoint); return projectiveJoint; }
Partea mai complicată la acest proiect este transmiterea celor 15 puncte de la Kinect la Arduino, prin intermediul USART, din următoarele motive:
Acest fapt înseamnă că este nevoie să fie schițat un protocol de comunicație minimal care să fie folosit de cele două dispozitive. Partea bună este totuși că această comunicație se poate produce doar într-un singur sens, de la Kinect la Arduino.
Soluția la care am ajuns a fost utilizarea funcției map(value, fromLow, fromHigh, toLow, toHigh)
, existentă în ambele limbaje de programare folosite, pentru a codifica coordonatele (x, y) ale punctelor astfel:
Pentru trimitere de pe Kinect:
void sendCoordinateAxis(float value, int lowerBound, int upperBound) { int intVal = floor(between(value, lowerBound, upperBound)); int asByte = (int)map(intVal, lowerBound, upperBound, 0, 255); port.write(asByte); }
Pentru recepție de pe Arduino: <code cpp> int readCoordinateX() {
byte xAsByte = Serial.read(); int x = (int)map(xAsByte, 0, 255, 0, width); Serial.println(x); return x;
}
int readCoordinateY() {
byte yAsByte = Serial.read(); int y = (int)map(yAsByte, 0, 255, 0, height); Serial.println(y); return y;
} </code
Probleme întâmpinate:
Resurse Software:
Resurse Hardware:
Resurse Kinect: