Après avoir effectué la veille et eu le retour positif sur le travail effectué, Olivier m'a demandé de me pencher sur la production d'un POC sur les detections physiques, j'ai tout d'abord commencé par les mains, plus simples et rapide afin de pouvoir estimer la difficulté du second projet, le corps entier.
Ce projet a demandé des compétences clées en mathématiques et en physique (calcul barycentriques, quaternions et vecteurs)
Mais il a aussi permit de développer les compétences BTS ci jointe:
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.
Ci dessous la présentation du projet ne prends pas en compte la gamification effectuées puisqu'il s'agit de la documentation techniques effectuées.
Se faisant, J'ai ajouté des effets sonores à chaque gestes afin de pouvoir "jouer" de la musique en faisant certains gestes.
Ci joint la conversations sur la récupération des différents gestes et de la version gamifiée pour les portes ouvertes Domitys d'Arras. (Conversation Wattsapp utilisées pour les retours lorsque le travail se faisait en Télétravail.) Sinon la plus part des retours se sont fait en présentiel donc par oral.
Puisque la détections des gestes n’est pas intégrée directement, et que nous souhaitons l’utiliser, nous avons donc créé le notre, permettant de recevoir comparant ainsi la position de chaque doigt d’une main à la paume de la main.
Sur les principes mathématiques de vecteur par rapport à un plan et de calcul barycentrique afin de savoir si un point est dans un triangle donné.
Nous avons donc déterminés le plan de la main par les points fournis : Pinky_Finger_MCP, Index_Finger_MCP, Wrist.
Il est à noter que pour utiliser le moteur unity permettant déjà de faire une partie des calculs, il faut convertir les points en Vector3.
La première chose qu’il nous faut est la position de chaque point que l’on peut récupérer simplement (et c’est comme cela que nous formeront nos Vector3 puisqu’il n’est pas possible de directement convertir l’un à l’autre puisque landmark (un point normalisé d’une main) a des données supplémentaires (name, presence, visibility) que le Vector3 n’a pas.
Voici un exemple de récupération et de convertion des points:
for (int handIndex = 0; handIndex < result.handLandmarks.Count; handIndex++)
{
var normalizedLandmarks = result.handLandmarks[handIndex];
if (normalizedLandmarks.landmarks == null)
continue;
_sb.AppendLine($"Main {handIndex}");
var landmarks = normalizedLandmarks.landmarks;
// Points
Vector3 wrist = new Vector3(landmarks[0].x, landmarks[0].y, landmarks[0].z);
Vector3 indexMCP = new Vector3(landmarks[5].x, landmarks[5].y, landmarks[5].z);
Vector3 pinkyMCP = new Vector3(landmarks[17].x, landmarks[17].y, landmarks[17].z);
Ensuite, afin de pouvoir définir un plan sur unity, il nous faut notre vecteur normal, vecteur normal pouvant être définis par deux vecteurs (formant le plan) il faut donc définir ces deux vecteurs.
Rappel l’on peut avoir un vecteur en soustrayant un point à un autre.
Ici l’on prend le point wrist en point de centre et les points index_finger_MCP et pinky_finger_MCP en autre points. L’on obtient alors :
// Plan Vector
Vector3 v1 = indexMCP - wrist;
Vector3 v2 = pinkyMCP - wrist;
Ce qui nous permet ensuite d’obtenir, par calcul avec unity directement le vecteur normal:
// Normal Vector
Vector3 normal = Vector3.Cross(v1, v2).normalized;
Ensuite, nous pouvons avec le point normal et un point du plan (wrist) définir un plan. Ci dessous, la définition de ce dernier
// Palm Plane
Plane palmPlane = new Plane(normal, wrist);
Et voilà, nous avons définis le plan de la paume de la main.
Ensuite, afin de savoir si des points rentre dans la paume de la main, ce que l’on considèrera comme “fermé”, l’on définis tout d’abord les points atteint par ceci:
List<int> ListOfPointToDetect = new List<int> { 8 /*indexFinger*/, 12/*middleFinger*/, 16/*ringFinger*/, 20 /*PinkyFinger*/ };
//necessary if we didn't want all the point and don't repeat x time the code
L’on prend uniquement les TIP, car c’est ce point qui va se trouver dans le plan.
Enfin, pour déterminer qu’un point est dans un plan, nous regardons la fonction “isOnTriangle” qui va récupérer la distance du point à déterminer, le projeter sur le plan, puis envoyer tout cela dans la fonction “PointInTriangle”
bool PointInTriangle(Vector3 p, Vector3 a, Vector3 b, Vector3 c, float eps /*tolerance*/ = 0.05f)
{
float scale = 1.3f;
Vector3 center = (a + b + c) / 3f;
a = center + (a - center) * scale;
b = center + (b - center) * scale;
c = center + (c - center) * scale;
Vector3 v0 = c - a;
Vector3 v1 = b - a;
Vector3 v2 = p - a;
float dot00 = Vector3.Dot(v0, v0);
float dot01 = Vector3.Dot(v0, v1);
float dot02 = Vector3.Dot(v0, v2);
float dot11 = Vector3.Dot(v1, v1);
float dot12 = Vector3.Dot(v1, v2);
float invDenom = 1f / (dot00 * dot11 - dot01 * dot01);
float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
return (u >= -eps) && (v >= -eps) && (u + v <= 1f + eps);
}
Qui effectue le calcul barycentrique avec une légère tolérance (le triangle formant le plan est très restreint.)
Il est important de noter que cela n’est pas suffisant, en effet il peut y avoir quelques faux négatifs, et le pouce n’est pas prit (Il peut être fermé sans être dans la paume de la main.)
Ainsi, nous allons ajouter à celà une autre valeur: L’angle.
Pour le pouce nous allons déterminer si il est fermé ou non par rapport à l’angle avec wrist & Index_Finger_MCP le pouce étant le vecteur Wrist & Thumb_Finger_MCP.
Nous rajoutons au pouce sa distance au plan si jamais il est dans la main.
var angleV1 = Vector3.Angle(thumbTIPWristVector, v1);
var angleV2 = Vector3.Angle(thumbTIPWristVector, v2);
var distFromPlane = palmPlane.GetDistanceToPoint(thumbTIP);
bool IsThumbClosed = false;
if ((angleV1 < 13 || angleV2 < 13) && distFromPlane < 0.3)
{
IsThumbClosed = true;
}
Ensuite, pour la définition des gestes, il suffit de prendre les conditions pour chaque doigt
bool isIndexClosed = listOfTipPoint[0];
bool isMiddleClosed = listOfTipPoint[1];
bool isRingClosed = listOfTipPoint[2];
bool isPinkyClosed = listOfTipPoint[3];
Et de mettre une liste de cas pour montrer le geste qui est le plus probable.
Ainsi l’on doit tout d’abord avoir une détection des gestes, ci joint un extrait de comment cela est fait dans la méthode whatAmIDoingWithMyFingerClosed,
if (thumb && index && middle && ring && pinky) //5 fingers
{
return $"Fist";
}
else {
...}
L’on regarde les doigts fermés (désigné par leurs propres nom, true = “fermé”, false = “ouvert”.
L’on va ensuite prendre dans la liste tout ce qui nous intéresse :
ListOfSignOfHandCount["Fist"] = ListOfSignOfHand.FindAll(
delegate (string sn)
{
return sn == "Fist";
}
).Count;
Puis l’on regarde par la suite lequel est majoritaire pendant x temps (temps donné par howManyFrameYouWant), donnable par le reste, notamment par le code extérieur afin qu’ils puissent récupérer les données.
Et ensuite l’on fait la moyenne, c’est à dire :
max / howManyFrameYouWant * 100
Afin de pouvoir récupérer le pourcentage de confiance.
Ici voici la détection de la main et du geste effectué
Ci joint la récupération des données afin de pouvoir tester les calculs.