Après avoir créé le système de reconnaissances des gestes des mains, je me suis ensuite lancé sur celui du corps entier.
Ce projet m'a aussi demandé de bonne connaissances physiques et mathématiques (vecteur, quaternions)
Mais il ma aussi permis, comme pour le projet de la detection des mains de développer des compétences :
Travailler en mode projet
Répondre aux incidents et aux demandes d'assistances et d'évolutions
Travailler en mode projet
Dans le cadre de ce projet j'ai utilisé à multiple reprise Github afin de pouvoir envoyer mon code et pouvoir versionner ce dernier.
Répondre aux incidents et aux demandes d'assistances et d'évolutions
lors du développement de la finalisation de l'application Olivier a pu montrer l'application au public cible lors d'un salon et a pu, ainsi, recueillir les avis.
Pour se faire Olivier m'a donc demandé de gamifier un peu l'application afin de pouvoir tester le concepts ainsi que le montrer aux nouveaux stagiaires.
Ci dessous la présentation du projet prends en compte le point léger de la gamification, montrant quel sigle vous êtes entrain de faire.
Cependant sur ce projet toutes les conversations se sont fait à l'oral.
Afin de pouvoir reconnaitre les différentes positions, il faut déjà déterminer tout les mouvements du corps que nous souhaitons analyser.
Pour se faire, l’on peut se fier aux articulations principales des membres du corps (cou, hanche, épaule) et leurs inférieur (coude, genoux) ainsi que la position de leur extrémintée.
Nous déterminons que nous parlerons d’angle x pour l’angle horizontal, z pour l’angle vertical et y pour l’angle de la profondeur.
Dessins fais à la main, de reflexions sur les mouvements du corps, ici, de la tête
Pour se faire, en se référant au squelette de découpage de MediaPipe, nous obtenons la possibilité de connaitre par la hauteur du nez (angle horizontal), l’angle haut-bas de la tête, avec une récupération d’angle grâce à un point central entre 7 (oreille gauche) et 8 (oreille droite), et 0 (le nez) au vecteur normal “d’allignement du corps” du plan 12 - 11 - 23 (plan du torse).
Ensuite, pour obtenir l’angle profond la hauteur entre les deux oreilles (penchée du côté du vecteur le plus petit.)
Et enfin pour l’angle vertical, l’on prend la ligne 11 12 et l’on fait deux nouveaux vecteurs entre 11 et 0 et entre 12 et 0 afin de pouvoir avoir un triangle. Si le triangle à les deux côtés (11,0 et 12,0) identiques alors la tête n’est pas tournée, si elle penche vers la gauche, 11 va être plus petit et inversement pour la 12.
Dessins fais à la main, de reflexions sur les mouvements du corps, ici, de la détermination de l’angle vertical de la tête.
Les bras sont une autre paire de manche, joke of the year. Notamment pour l’axe “vertical” (on va le prendre par rapport à un personnage en T-pose à qui l’on ne bouge QUE le bras)
Dessins fais à la main, de reflexions sur les mouvements du corps, ici, l’ébauche de la réflexion sur le mouvement du bras.
Tout d’abord, nous allons nous concentrer sur la première partie du bras (vecteur 11 - 13 (ou 12 -1 4))
Ci joint, les différents dossiers fournis pour le code,
Members est la classe pour chaque membre , contenant leurs axes, leurs angles, et ce qu'ils "font"
Members Name est la liste enum des noms possibles des membres afin d'optimiser un peu le code et d'éviter l'utilisation de strings.
Pose et poselandmarks sont aussi des enums
PoseLandmarkDetectionconfig et PoseLandmarkDetectionconfigwindows sont juste des dossiers de base pour l'interface
PoseLandMarkRunner et ce qui fait tourner l'application, notamment Mediapipe. (C'est dedans que la plus part du code est)
Nous allons commencer par décrire le fichier des membres, contenant alors les différents calculs permettant de connaitre leur propre position par rapport au vecteur UpperMemberVector.
Tout d'abord un membre est constitué de 3 joints importants :
foreJoint, ce qui va prendre l'avant bras et la hanche
MiddleJoint, qui va prendre le coude et le genoux
BackJoint, qui va prendre le poignet et la cheville
Ensuite il faut aussi le vecteur permettant de déterminer la position dénommé "UpperMemberVector" qui prends le vecteur 11 - 12 pour les bras et 23-24 pour les jambes.
Ce dernier va permettre de connaitre la position générale du corps.
private List<Vector3> GetAllTheAxes()
{
GetUpper();
zb = upperMemberVector.normalized;
yb = yt;
xb = Vector3.Cross(yb, zb).normalized;
if (xb.magnitude < 0.01f)
xb = Vector3.Cross(xt, zb).normalized;
yb = Vector3.Cross(zb, xb).normalized;
List<Vector3> axes = new List<Vector3> { xb, yb, zb };
return axes;
}
Le code ci dessus permet de récupérer les différents axes des membres et surtout de les normaliser afin de simplifier les calculs. (On calcul zb avec xb et yb et est normalisé et ensuite on fait de même avec yb entre xb et zb afin que tout soit normal à xb.)
private float BendAngle()
{
return bendAngle = Vector3.Angle(
(middleJoint - backJoint).normalized,
(foreJoint - middleJoint).normalized
);
}
Cette fonction permet de calculer l'angle de plie du membre.
public (axeY, axeX, axeZ, bool) WhatIAmDoing
(
float tol)
{
List<float> angles = getAngles();
float v = angles[4];
float d = angles[3];
axeY verticalSign;
axeX horizontalSign;
axeZ depthSign;
// --- UP / MIDDLE / DOWN ---
if (v > 125) { verticalSign = axeY.up; }
else if (v < 25 ) { verticalSign = axeY.down; }
else { verticalSign = axeY.middle; }
bool isLeg = nameOfMember == MembersName.LeftLeg || nameOfMember == MembersName.RightLeg;
// --- FRONT / BACK / SIDE ---
if (!isLeg)
{
if (d > 120 || d < 45) { depthSign = axeZ.front; }
else { depthSign = axeZ.side; }
}
else
{
if (d > 90 || d < 40 ) { depthSign = axeZ.front; }
else { depthSign = axeZ.side; }
}
float distanceFromMiddToRoot = Vector3.Distance(middleVector, backJoint);
// --- RIGHT / MIDDLE / LEFT ---
if (nameOfMember == MembersName.RightArm || nameOfMember == MembersName.RightLeg)
{
float xPrime = backJoint.x + distanceFromMiddToRoot;
if (xPrime < foreJoint.x)
{
horizontalSign = axeX.right;
}
else if (middleVector.x > foreJoint.x )
{
horizontalSign = axeX.left;
}
else
{
horizontalSign = axeX.middle;
}
}
else
{
float xPrime = backJoint.x - distanceFromMiddToRoot;
if (middleVector.x < foreJoint.x)
{
horizontalSign = axeX.right;
}
else if (xPrime > foreJoint.x )
{
horizontalSign = axeX.left;
}
else
{
horizontalSign = axeX.middle;
}
}
// --- BENT (coude/genou plié) via twist ---
bool isBend = BendAngle() > 35f;
Debug.Log(BendAngle());
return (verticalSign, horizontalSign, depthSign, isBend);
}
Ci dessus le code permettant de savoir ce que le membre "fait" c'est à dire de savoir ce que le membre a comme position, et si il est plié ou non.
Il permet donc de savoir si le membre est en bas, en haut, au milieu (axeY), si le membre est devant, derrière ou centré (axeZ) et si le membre est plus à gauche que la normale, au centre ou plus à droite.
Ces informations sont ensuite récupérées dans le runner afin de pouvoir reconnaitres les gestes.
Il est à noter que j'ai créé une nomenclature spéciale afin d'éviter la "confusion" lors de la création de mouvement et éviter les doublons en prévision de futurs améliorations.
//WARNING
//If you need to add a pose, there is some rules.
//First : CamelCase. Important to read the line
//Second : Right Hand, Left Hand, Left Leg, Right Leg, Torso
//Thrid : Up/Middle/Down, Bend/Straight, Back/Side/Front
//Fourth : Torso has only Straight or Bend, Legs has only Straight/Bend and Down/Middle (Or up if it's a really hard move)
donnant alors une liste comme ci joint (ici une liste réduite)
[SerializeField] private Sprite RHandUpBendSideLHandDownBendSideLLegDownStraightRLegDownStraightTorsoStraight;
[SerializeField] private Sprite RHandMiddleBendFrontLHandUpStraightSideLLegDownStraightRLegDownStraightTorsoStraight;
[SerializeField] private Sprite RHandMiddleBendFrontLHandMiddleBendFrontLLegDownStraightRLegDownStraightTorsoStraight;
[SerializeField] private Sprite RHandDownBendSideLHandDownBendSideLLegDownStraightRLegDownStraightTorsoStraight;
à contrario des mains, ici l'on va changer le gestes en cours dès la détection d'un nouveau (plutôt que de calculer la moyenne sur un temps donné)
Il est bien sûr possible de le changer, ce choix techniques a été fait afin de pouvoir faire un "just dance" en test.
Enfin la méthode "whatAreYouDoing" permet de déterminer (par une suite de if) quel geste est actuellement fait.
Exemple:
if (rightArmAct.Item1.ToString() == "up" && rightArmAct.Item4 == true && (rightArmAct.Item3.ToString() == "side" || rightArmAct.Item3.ToString() == "front") && rightArmAct.Item2.ToString() == "right" &&
leftArmAct.Item1.ToString() == "down" && leftArmAct.Item4 == true && leftArmAct.Item3.ToString() == "side" && leftArmAct.Item2.ToString() == "middle" &&
rightLegAct.Item1.ToString() == "down" &&
leftLegAct.Item1.ToString() == "down" &&
!torsoBendAxeX && !torsoBendAxeZ)
{
signs.Add("RHandUpBendSideLHandDownBendSideLLegDownStraightRLegDownStraightTorsoStraight");
}
if (rightArmAct.Item1.ToString() == "middle" && rightArmAct.Item4 == true && rightArmAct.Item3.ToString() == "front" && rightArmAct.Item2.ToString() == "left" &&
leftArmAct.Item1.ToString() == "up" && leftArmAct.Item4 == false && leftArmAct.Item3.ToString() == "side" && leftArmAct.Item2.ToString() == "left" &&
rightLegAct.Item1.ToString() == "down" &&
leftLegAct.Item1.ToString() == "down" &&
!torsoBendAxeX && !torsoBendAxeZ)
{
signs.Add("RHandMiddleBendFrontLHandUpStraightSideLLegDownStraightRLegDownStraightTorsoStraight");
}