I. Caméra et SDK▲
I-A. Téléchargement et installation du SDK▲
La première étape consiste à récupérer le SDK sur le site d'Intel. L'adresse est la suivante : http://software.intel.com/fr-fr/vcsource/tools/perceptual-computing-sdk.
Une fois le fichier d'installation téléchargé, installez-le. Vous pouvez garder le répertoire par défaut (C:\Program Files (x86)\Intel\PCSDK). L'installation peut prendre un peu de temps si vous décidez d'installer les outils de reconnaissance vocale.
I-B. Achat de la caméra▲
Il est possible d'acheter une caméra perceptuelle depuis le site d'Intel. Cette dernière est destinée aux développeurs. Plus récemment, Intel a annoncé au salon Computex que Creative mettrait sur le marché une caméra grand public nommée Senz3D.
I-C. Tester l'installation et la caméra▲
Il est possible de tester l'installation du SDK en exécutant les samples livrés avec ce dernier. Ouvrez votre explorateur et rendez-vous dans le dossier C:\Program Files (x86)\Intel\PCSDK\bin. Choisissez ensuite le dossier correspondant à votre plateforme. Dans mon cas, j'utiliserai les samples du dossier x64.
Lancez par exemple le programme gesture_viewer :
II. Intégration dans Unity*▲
II-A. Présentation de Unity*▲
Unity* est un outil facilitant le développement de jeux vidéo. Il permet d'intégrer des objets 3D dans des scènes en gérant de nombreux formats (.obj, .blend, etc.). Il est aussi possible d'ajouter des comportements tels que la gravité, la gestion de collisions, des effets visuels, etc. Le comportement des différents éléments de la scène se fait par l'utilisation de scripts écrits en C#, en JavaScript* ou en Boo. Le Perceptual Computing SDK est accessible dans Unity* par l'intermédiaire de plugins. Le plugin est en réalité un fichier C# wrappant les interfaces habituellement utilisées par les développeurs de Perceptual Computing en C# et C++.
II-B. Prérequis pour l'utilisation du SDK dans Unity*▲
Pour exécuter des plugins dans Unity*, il est nécessaire de disposer de la version professionnelle. Cette version est payante, mais il est toutefois possible de l'utiliser gratuitement pendant environ 30 jours. Le site d'Unity* vous donnera tous les détails nécessaires (http://unity3d.com/).
Vous devez aussi disposer d'une caméra perceptuelle et avoir installé le SDK. Si ces critères ne sont pas remplis, veuillez vous reporter aux sections précédentes.
II-C. Récupération du projet▲
Afin de faciliter le développement et de se concentrer réellement sur l'aspect Preceptual Computing, vous pouvez récupérer le projet sur lequel nous allons travailler dans le zip suivant (Developpez-lab.zip).
Ce projet contient :
- un terrain représentant une île ;
- de l'eau ;
- un vaisseau ;
- un cube pour la gestion des collisions du vaisseau et de l'eau ;
- une lumière.
Le projet peut être exécuté, mais rien ne se passe. Nous allons commencer par ajouter le plugin. Pour cela, il faut commencer par créer un dossier nommé Plugins dans le dossier Assets du projet Unity.
Il est ensuite nécessaire de copier quelques fichiers dans ce répertoire nouvellement créé. Pour cela, dans votre explorateur de fichier, récupérez les fichiers du dossier : C:\Program Files (x86)\Intel\PCSDK\framework\Unity\hellounity\Assets\Plugins et copiez-les dans le répertoire Plugins du projet Unity.
Votre projet est maintenant configuré. Nous allons pouvoir créer un script permettant de contrôler le vaisseau.
III. Contrôler le vaisseau▲
III-A. Création d'un script pour déplacer le vaisseau▲
Pour pouvoir déplacer le vaisseau, nous allons lui attacher un script C# qui se chargera de récupérer les événements du Perceptual Computing SDK et opérera ensuite sur la position du vaisseau.
Pour créer un script, sélectionnez le vaisseau, puis dans l'inspecteur, choisissez « Add Component ». Créez un script C# et nommez-le SpaceShipMoves.
Ouvrez ce script nouvellement créé en double-cliquant dessus. Unity crée automatiquement deux méthodes :
- void Start() ;
- void Update().
La méthode Start() est appelée au lancement de l'application. La méthode Update() est appelée à chaque frame.
Nous allons ajouter à la fin de la méthode Update(), une ligne permettant de faire avancer le vaisseau. Ajoutez le code suivant à la méthode Update() .
void
Update()
{%
transform.position =
transform.position +
transform.forward *
SPEED_FACTOR;
}
Lorsqu'un script est rattaché à un objet, il possède une variable transform permettant d'accéder aux données relatives à sa position, son orientation, etc. Ainsi, la position peut être récupérée en utilisant transform.position et le vecteur directeur de notre objet se trouve dans la variable transform.forward. Nous utilisons une constante nommée SPEED_FACTOR dont la valeur est initialisée à 3f.
public
float
SPEED_FACTOR =
3
f;
Si vous relancez le jeu maintenant, vous constaterez que le vaisseau avance tout seul.
III-B. Les mouvements▲
Nous allons implémenter un contrôle sur trois axes :
- Pitch : faire monter ou descendre le nez de l'appareil ;
- Roll : orienter les ailes vers le haut ou le bas ;
- Yaw : orienter le nez de l'appareil vers la droite ou la gauche.
Pour jouer sur le pitch, nous utiliserons la distance de la main la plus proche de la caméra (axe Y). Il est nécessaire d'utiliser un point de référence. Lorsque les mains se trouvent entre l'écran et le point de référence, le vaisseau s'orientera vers le bas, lorsque l'utilisateur aura les mains entre son corps et le point de référence, le vaisseau s'orientera vers le haut. Voici une image montrant comment jouer sur le pitch.
Pour agir sur le roll, nous utiliserons la différence de hauteur (axe Z) entre les deux mains. On peut rapprocher le mouvement du roll au mouvement qu'un conducteur réalise pour faire tourner sa voiture. L'image suivante montre un utilisateur agissant sur le roll.
Enfin, pour agir sur le yaw, l'utilisateur devra tirer sur la main vers lequel l'appareil doit s'orienter. Ainsi, si nous voulons tourner vers la droite, il faudra tirer sur la main droite. La figure suivante illustre ce procédé.
III-C. Utilisation du SDK▲
La prochaine étape consiste à lire les données du SDK puis à les utiliser dans le jeu. Au lancement de l'application, il est nécessaire de paramétrer quelques variables. Nous allons tout d'abord déclarer quatre nouveaux attributs pour notre classe :
private
PXCUPipeline.Mode mode =
PXCUPipeline.Mode.GESTURE;
private
PXCUPipeline pp;
private
bool
calibrated =
false
;
private
float
calibrationY;
void
Start()
{
pp =
new
PXCUPipeline();
pp.Init(mode);
}
Nous en profitons pour donner le code de la méthode start qui initialise certaines variables du SDK.
L'attribut mode est en fait une constante que nous utiliserons pour paramétrer le SDK. L'attribut pp est quant à lui notre lien avec les interfaces du SDK. C'est par cette variable que passeront toutes nos lectures de données.
calibrated nous permettra de savoir si notre point de référence (nécessaire au calcul du pitch) est fiable. Enfin, calibrationY mémorisera la distance en Y de notre point de référence.
Complétons à présent la méthode Update() pour récupérer les données du SDK.
void
Update()
{
//Création de deux références pour récupérer les données
//provenant de la main droite et de la main gauche.
PXCMGesture.GeoNode mainHand;
PXCMGesture.GeoNode secondaryHand;
//La méthode AcquireFrame() est bloquante lorsqu'elle est utilisée avec
//son paramètre à true. Laisser cette méthode bloquante permet de synchroniser
//le frame rate du jeu avec celui de la caméra.
if
(!
pp.AcquireFrame(true
)) return
;
//Nous utilisons la méthode QueryNode pour lire la position des mains.
//Ceci se fait en deux temps, la première fois pour la main principale,
//la seconde pour la main secondaire.
if
(pp.QueryGeoNode(PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_LEFT
|
PXCMGesture.GeoNode.Label.LABEL_HAND_MIDDLE, out mainHand) &&
pp.QueryGeoNode(PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_RIGHT
|
PXCMGesture.GeoNode.Label.LABEL_HAND_MIDDLE, out secondaryHand))
{
//On fera des choses ici
}
else
{
calibrated =
false
; }
//On relache le lock
pp.ReleaseFrame();
//On pourra utiliser les données ici !
transform.position =
transform.position +
transform.forward *
SPEED_FACTOR;
}
La méthode AcquireFrame permet de s'assurer qu'une frame est réellement disponible. En effet, il ne faudrait pas lire une frame alors que le SDK tente toujours d'écrire sur la même ressource. Cette fonction sert donc à protéger l'accès aux données. Lorsque le paramètre passé à AcquireFrame est à true, cette fonction est bloquante.
Nous utilisons ensuite la méthode QueryNode pour « remplir » le contenu des variables mainHand et secondaryHand. La méthode QueryNode doit posséder les arguments suivants :
- PXCMGesture.GeoNode.Label body : correspondant à la partie du corps nous intéressant. Nous construisons alors un label spécifique à l'aide de l'opérateur | pour indiquer la main puis la partie de la main qui nous intéresse. Ainsi, dans l'exemple PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_LEFT | PXCMGesture.GeoNode.Label.LABEL_HAND_MIDDLE, nous nous intéressons à la partie centrale de la main gauche ;
- PXCMGesture.GeoNode geonode : la référence permettant de récupérer les données.
Si l'un des appels à QueryNode échoue, nous devons indiquer à notre jeu que les données ne sont plus fiables. Nous changeons alors la valeur de calibrated pour la passer à false.
Il ne faut surtout pas oublier d'appeler la méthode ReleaseFrame() pour relâcher le lock.
Cependant, ce template ne suffit pas. Si vous exécutez le jeu une nouvelle fois, vous verrez que le comportement n'a pas changé. En réalité, nous n'utilisons aucune donnée pour le moment.
Avant d'implémenter le roll, le pitch et le yaw, nous devons vérifier que les mains sont correctement mappées. À l'heure où ces lignes sont écrites, le SDK est encore jeune et certaines fonctionnalités ne sont pas fiables. Pour vérifier que les mains sont correctement mappées (main droite à droite et main gauche à gauche), nous lisons les données sur l'axe X de chaque main et vérifions que la coordonnée en x de la main secondaire est supérieure à celle de la main primaire.
void
checkHands(ref PXCMGesture.GeoNode mainHand, ref PXCMGesture.GeoNode secondaryHand){
//Si la position en x de la main principale est plus grande que celle de la
//main secondaire, nous inversons les références.
if
(mainHand.positionWorld.x >
secondaryHand.positionWorld.x){
PXCMGesture.GeoNode temp =
mainHand;
mainHand =
secondaryHand;
secondaryHand =
temp;
}
}
Cette vérification est arbitraire, nous aurions pu inverser le sens des mains. Ce qui est important, c'est de toujours faire la même chose et d'appliquer le bon signe aux calculs que nous réaliserons pour calculer le pitch, le roll et le yaw.
Cette fonction est donc appelée si la lecture des données réussit (comme nos mouvements en dépendent, il est intéressant de l'appeler juste après la lecture des positions).
Encore une fois, nous modifions la fonction Update() :
void
Update()
{
//Création de deux références pour récupérer les données
//provenant de la main droite et de la main gauche.
PXCMGesture.GeoNode mainHand;
PXCMGesture.GeoNode secondaryHand;
//La méthode AcquireFrame() est bloquante lorsqu'elle est utilisée avec
//son paramètre à true. Laisser cette méthode bloquante permet de synchroniser
//le frame rate du jeu avec celui de la caméra.
if
(!
pp.AcquireFrame(true
)) return
;
//Nous utilisons la méthode QueryNode pour lire la position des mains.
//Ceci se fait en deux temps, la première fois pour la main principale,
//la seconde pour la main secondaire.
if
(pp.QueryGeoNode(PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_LEFT
|
PXCMGesture.GeoNode.Label.LABEL_HAND_MIDDLE, out mainHand) &&
pp.QueryGeoNode(PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_RIGHT
|
PXCMGesture.GeoNode.Label.LABEL_HAND_MIDDLE, out secondaryHand))
{
checkHands(ref mainHand, ref secondaryHand);
//Calibrate or recalibrate the game if needed
calibrate(ref mainHand);
}
else
{
calibrated =
false
; }
//On relache le lock
pp.ReleaseFrame();
if
(calibrated ==
false
) return
;
//On pourra utiliser les données ici !
transform.position =
transform.position +
transform.forward *
SPEED_FACTOR;
}
Les lignes en gras sont celles que nous avons rajoutées.
Nous utilisons la fonction calibrate() pour calibrer ou recalibrer le jeu si besoin. Son code est le suivant :
void
calibrate(ref PXCMGesture.GeoNode mainHand){
PXCMGesture.Gesture dataMain;
PXCMGesture.Gesture dataSecondary;
//Nous essayons de récupérer un événement "pouce levé" sur la main principale
if
(pp.QueryGesture(PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_PRIMARY
, out dataMain)){
//Si on trouve cet événement, on enregistre la position de la main
if
(dataMain.label ==
PXCMGesture.Gesture.Label.LABEL_POSE_THUMB_UP){
calibrated =
true
;
calibrationY =
mainHand.positionWorld.y;
}
}
//Nous essayons de récupérer un événement "pouce levé" sur la main secondaire
else
if
(pp.QueryGesture(PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_PRIMARY,
out dataSecondary)){
//Si on trouve cet événement, on enregistre la position de la main
if
(dataSecondary.label ==
PXCMGesture.Gesture.Label.LABEL_POSE_THUMB_UP){
calibrated =
true
;
calibrationY =
mainHand.positionWorld.y;
}
}
}
Si vous essayez de faire tourner le jeu, vous verrez que le comportement change. En effet, le vaisseau n'avance plus. Pour le faire avancer, vous devez placer vos mains en face de la caméra et mettre un pouce vers le haut avec l'une ou l'autre de vos mains (les deux si vous le souhaitez). Comme vous pouvez le voir, nous utilisons la variable positionWorld pour récupérer la position de la main. Cette variable est un vecteur à trois dimensions. Dans notre exemple, nous utilisons sa composante Y pour récupérer la distance de la main depuis la caméra.
Une fois le jeu calibré, tant que vos mains restent en vue de la caméra, le vaisseau continuera d'avancer. Il ne reste plus qu'à intégrer les rotations sur le vaisseau et le tour sera joué !
III-D. Implémentation des contrôles▲
III-D-1. Précalcul des données utiles▲
Nos variables mainHand et secondaryHand contiennent beaucoup de données ne nous intéressant pas. Afin de simplifier les calculs, nous allons les récupérer avant de les envoyer aux fonctions qui se chargeront d'appliquer les rotations.
Nous utilisons encore une fois l'attribut positionWorld de nos nœuds.
float
mainHandY =
mainHand.positionWorld.y;
float
mainHandZ =
mainHand.positionWorld.z;
float
secondaryHandY =
secondaryHand.positionWorld.y;
float
secondaryHandZ =
secondaryHand.positionWorld.z;
Il ne reste plus qu'à appliquer les rotations en appelant les fonctions créées à cet effet.
controlRoll(mainHandZ, secondaryHandZ);
controlYaw(mainHandY, secondaryHandY);
controlPitch(mainHandY, secondaryHandY);
Le code de la méthode Update() est maintenant complet et ressemble à :
void
Update()
{
//Création de deux références pour récupérer les données
//provenant de la main droite et de la main gauche.
PXCMGesture.GeoNode mainHand;
PXCMGesture.GeoNode secondaryHand;
//La méthode AcquireFrame() est bloquante lorsqu'elle est utilisée avec
//son paramètre à true. Laisser cette méthode bloquante permet de synchroniser
//le frame rate du jeu avec celui de la caméra.
if
(!
pp.AcquireFrame(true
)) return
;
//Nous utilisons la méthode QueryNode pour lire la position des mains.
//Ceci se fait en deux temps, la première fois pour la main principale,
//la seconde pour la main secondaire.
if
(pp.QueryGeoNode(PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_LEFT
|
PXCMGesture.GeoNode.Label.LABEL_HAND_MIDDLE, out mainHand) &&
pp.QueryGeoNode(PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_RIGHT
|
PXCMGesture.GeoNode.Label.LABEL_HAND_MIDDLE, out secondaryHand))
{
checkHands(ref mainHand, ref secondaryHand);
//Calibrate or recalibrate the game if needed
calibrate(ref mainHand);
}
else
{
calibrated =
false
; }
//On relache le lock
pp.ReleaseFrame();
if
(calibrated ==
false
) return
;
//On récupère les données intéressantes
float
mainHandY =
mainHand.positionWorld.y;
float
mainHandZ =
mainHand.positionWorld.z;
float
secondaryHandY =
secondaryHand.positionWorld.y;
float
secondaryHandZ =
secondaryHand.positionWorld.z;
//On applique les rotations
controlRoll(mainHandZ, secondaryHandZ);
controlYaw(mainHandY, secondaryHandY);
controlPitch(mainHandY, secondaryHandY);
transform.position =
transform.position +
transform.forward *
SPEED_FACTOR;
}
Nous allons détailler les fonctions controlRoll, controlYaw et controlPitch par la suite.
III-D-2. Implémentation du pitch▲
Le pitch est en réalité la commande la plus difficile à implémenter. Elle demande en effet d'utiliser un point de référence. La fonction est en réalité très simple.
void
controlPitch(float
mainHandY, float
secondaryHandY){
//Nous utilisons la main la plus proche de la caméra
float
positionY =
(mainHandY<
secondaryHandY) ? mainHandY : secondaryHandY;
float
pitch =
calibrationY -
positionY;
//La fonction carré permet de donner un rendu souple aux mouvements
pitch *=
Mathf.Abs (pitch);
pitch *=
sensibilityFactor;
transform.RotateAroundLocal(transform.right, pitch);
}
Nous commençons par récupérer la position en Y de la main la plus proche de la camera (ceci afin d'avoir un comportement stable lorsque l'on utilise le yaw). Le pitch est alors la différence entre le point de référence et la position de la main sélectionnée. Pour amortir la valeur et aboutir à un contrôle souple, nous mettons cette valeur au carré. Nous multiplions ensuite par un facteur que vous paramétrerez.
La dernière ligne applique la rotation. Souvenez-vous, nous avions utilisé la référence transform.position pour faire avancer le vaisseau. Nous utilisons maintenant la fonction RotateAroundLocal sur l'axe X pour incliner le vaisseau vers le haut ou le bas.
Mettre la valeur au carré apporte un réel avantage. Ceci permet d'augmenter l'amplitude sur de petits mouvements. On arrive ainsi à avoir des contrôles très précis, tout en permettant d'avoir une rotation importante à partir d'une certaine amplitude. Les explications détaillées de ce procédé font l'objet d'un white paper disponible sur le Net.
III-D-3. Implémentation du roll▲
L'implémentation du roll est très similaire à celle du pitch et ne sera donc pas détaillée.
void
controlRoll(float
mainHandZ, float
secondaryHandZ){
float
roll =
mainHandZ -
secondaryHandZ;
roll *=
Mathf.Abs (roll);
roll *=
sensibilityFactor;
transform.RotateAroundLocal(transform.forward, roll);
}
III-D-4. Implémentation du yaw▲
Tout comme l'implémentation du roll, les détails ont été expliqués précédemment.
void
controlYaw(float
mainHandY, float
secondaryHandY){
float
yaw =
mainHandY -
secondaryHandY;
yaw *=
Mathf.Abs (yaw);
yaw *=
sensibilityFactor;
transform.RotateAroundLocal(transform.up, yaw);
}
IV. Conclusion▲
Nous avons vu comment utiliser le Perceptual Computing SDK dans Unity* et comment implémenter un contrôleur de jeu en quelques lignes de code. Notre jeu ne gère pas les collisions, il n'a aucun but et reste très primaire. Libre à vous d'en faire ce que vous voulez !
Une version plus complète est disponible sur le site : www.intel-software-academic-program.com.
PS: Les personnes souhaitant développer une application pour la caméra peuvent se la procurer ici ou alors si vous avez une idée, des prêts de 2 semaines peuvent être organisés.