human/src/gesture/gesture.ts

144 lines
6.8 KiB
TypeScript
Raw Normal View History

2021-05-25 14:58:20 +02:00
/**
* Gesture detection algorithm
2021-05-25 14:58:20 +02:00
*/
2021-09-13 19:28:35 +02:00
import type { GestureResult } from '../result';
import * as fingerPose from '../fingerpose/fingerpose';
2021-05-22 18:33:19 +02:00
2021-07-29 17:01:50 +02:00
/**
* @typedef FaceGesture
*/
export type FaceGesture =
`facing ${'left' | 'center' | 'right'}`
| `blink ${'left' | 'right'} eye`
| `mouth ${number}% open`
| `head ${'up' | 'down'}`;
/**
* @typedef IrisGesture
*/
export type IrisGesture =
'facing center'
| `looking ${'left' | 'right' | 'up' | 'down'}`
| 'looking center';
/**
* @typedef BodyGesture
*/
export type BodyGesture =
`leaning ${'left' | 'right'}`
| `raise ${'left' | 'right'} hand`
| 'i give up';
/**
* @typedef BodyGesture
*/
export type HandGesture =
`${'thumb' | 'index' | 'middle' | 'ring' | 'pinky'} forward`
| `${'thumb' | 'index' | 'middle' | 'ring' | 'pinky'} up`
| 'victory'
| 'thumbs up';
2021-07-29 17:01:50 +02:00
2021-09-12 05:54:35 +02:00
export const body = (res): GestureResult[] => {
2020-11-04 16:18:22 +01:00
if (!res) return [];
2021-07-29 17:01:50 +02:00
const gestures: Array<{ body: number, gesture: BodyGesture }> = [];
2020-11-26 16:37:04 +01:00
for (let i = 0; i < res.length; i++) {
2020-11-04 16:18:22 +01:00
// raising hands
2020-11-24 05:36:04 +01:00
const leftWrist = res[i].keypoints.find((a) => (a.part === 'leftWrist'));
const rightWrist = res[i].keypoints.find((a) => (a.part === 'rightWrist'));
const nose = res[i].keypoints.find((a) => (a.part === 'nose'));
if (nose && leftWrist && rightWrist && (leftWrist.position.y < nose.position.y) && (rightWrist.position.y < nose.position.y)) gestures.push({ body: i, gesture: 'i give up' });
else if (nose && leftWrist && (leftWrist.position.y < nose.position.y)) gestures.push({ body: i, gesture: 'raise left hand' });
else if (nose && rightWrist && (rightWrist.position.y < nose.position.y)) gestures.push({ body: i, gesture: 'raise right hand' });
2020-11-04 16:18:22 +01:00
// leaning
2020-11-24 05:36:04 +01:00
const leftShoulder = res[i].keypoints.find((a) => (a.part === 'leftShoulder'));
const rightShoulder = res[i].keypoints.find((a) => (a.part === 'rightShoulder'));
if (leftShoulder && rightShoulder) gestures.push({ body: i, gesture: `leaning ${(leftShoulder.position.y > rightShoulder.position.y) ? 'left' : 'right'}` });
2020-11-04 16:18:22 +01:00
}
return gestures;
};
2021-09-12 05:54:35 +02:00
export const face = (res): GestureResult[] => {
2020-11-04 16:18:22 +01:00
if (!res) return [];
2021-07-29 17:01:50 +02:00
const gestures: Array<{ face: number, gesture: FaceGesture }> = [];
2020-11-26 16:37:04 +01:00
for (let i = 0; i < res.length; i++) {
2020-11-24 05:36:04 +01:00
if (res[i].mesh && res[i].mesh.length > 0) {
2021-03-06 23:22:47 +01:00
const eyeFacing = res[i].mesh[33][2] - res[i].mesh[263][2];
if (Math.abs(eyeFacing) < 10) gestures.push({ face: i, gesture: 'facing center' });
2021-04-19 22:02:47 +02:00
else gestures.push({ face: i, gesture: `facing ${eyeFacing < 0 ? 'left' : 'right'}` });
2020-11-24 05:36:04 +01:00
const openLeft = Math.abs(res[i].mesh[374][1] - res[i].mesh[386][1]) / Math.abs(res[i].mesh[443][1] - res[i].mesh[450][1]); // center of eye inner lid y coord div center of wider eye border y coord
if (openLeft < 0.2) gestures.push({ face: i, gesture: 'blink left eye' });
const openRight = Math.abs(res[i].mesh[145][1] - res[i].mesh[159][1]) / Math.abs(res[i].mesh[223][1] - res[i].mesh[230][1]); // center of eye inner lid y coord div center of wider eye border y coord
if (openRight < 0.2) gestures.push({ face: i, gesture: 'blink right eye' });
const mouthOpen = Math.min(100, 500 * Math.abs(res[i].mesh[13][1] - res[i].mesh[14][1]) / Math.abs(res[i].mesh[10][1] - res[i].mesh[152][1]));
if (mouthOpen > 10) gestures.push({ face: i, gesture: `mouth ${Math.trunc(mouthOpen)}% open` });
const chinDepth = res[i].mesh[152][2];
if (Math.abs(chinDepth) > 10) gestures.push({ face: i, gesture: `head ${chinDepth < 0 ? 'up' : 'down'}` });
2020-11-09 20:26:10 +01:00
}
2020-11-04 16:18:22 +01:00
}
return gestures;
};
2021-09-12 05:54:35 +02:00
export const iris = (res): GestureResult[] => {
2021-01-11 15:02:02 +01:00
if (!res) return [];
2021-07-29 17:01:50 +02:00
const gestures: Array<{ iris: number, gesture: IrisGesture }> = [];
2021-01-11 15:02:02 +01:00
for (let i = 0; i < res.length; i++) {
if (!res[i].annotations || !res[i].annotations.leftEyeIris || !res[i].annotations.rightEyeIris) continue;
const sizeXLeft = res[i].annotations.leftEyeIris[3][0] - res[i].annotations.leftEyeIris[1][0];
const sizeYLeft = res[i].annotations.leftEyeIris[4][1] - res[i].annotations.leftEyeIris[2][1];
const areaLeft = Math.abs(sizeXLeft * sizeYLeft);
const sizeXRight = res[i].annotations.rightEyeIris[3][0] - res[i].annotations.rightEyeIris[1][0];
const sizeYRight = res[i].annotations.rightEyeIris[4][1] - res[i].annotations.rightEyeIris[2][1];
const areaRight = Math.abs(sizeXRight * sizeYRight);
2021-04-19 15:30:04 +02:00
let center = false;
2021-01-11 15:02:02 +01:00
const difference = Math.abs(areaLeft - areaRight) / Math.max(areaLeft, areaRight);
2021-04-19 15:30:04 +02:00
if (difference < 0.25) {
center = true;
gestures.push({ iris: i, gesture: 'facing center' });
}
2021-05-28 16:43:48 +02:00
const rightIrisCenterX = Math.abs(res[i].mesh[33][0] - res[i].annotations.rightEyeIris[0][0]) / res[i].box[2];
const leftIrisCenterX = Math.abs(res[i].mesh[263][0] - res[i].annotations.leftEyeIris[0][0]) / res[i].box[2];
if (leftIrisCenterX > 0.06 || rightIrisCenterX > 0.06) center = false;
if (leftIrisCenterX > 0.06) gestures.push({ iris: i, gesture: 'looking right' });
if (rightIrisCenterX > 0.06) gestures.push({ iris: i, gesture: 'looking left' });
2021-04-19 15:30:04 +02:00
2021-05-28 16:43:48 +02:00
const rightIrisCenterY = Math.abs(res[i].mesh[145][1] - res[i].annotations.rightEyeIris[0][1]) / res[i].box[3];
const leftIrisCenterY = Math.abs(res[i].mesh[374][1] - res[i].annotations.leftEyeIris[0][1]) / res[i].box[3];
2021-05-28 21:53:51 +02:00
if (leftIrisCenterY < 0.01 || rightIrisCenterY < 0.01 || leftIrisCenterY > 0.022 || rightIrisCenterY > 0.022) center = false;
2021-05-28 16:43:48 +02:00
if (leftIrisCenterY < 0.01 || rightIrisCenterY < 0.01) gestures.push({ iris: i, gesture: 'looking down' });
2021-05-28 21:53:51 +02:00
if (leftIrisCenterY > 0.022 || rightIrisCenterY > 0.022) gestures.push({ iris: i, gesture: 'looking up' });
2021-04-19 15:30:04 +02:00
// still center;
if (center) gestures.push({ iris: i, gesture: 'looking center' });
2021-01-11 15:02:02 +01:00
}
return gestures;
};
2021-09-12 05:54:35 +02:00
export const hand = (res): GestureResult[] => {
2020-11-04 16:18:22 +01:00
if (!res) return [];
2021-07-29 17:01:50 +02:00
const gestures: Array<{ hand: number, gesture: HandGesture }> = [];
2020-11-26 16:37:04 +01:00
for (let i = 0; i < res.length; i++) {
2021-02-13 15:16:41 +01:00
const fingers: Array<{ name: string, position: number }> = [];
2021-09-21 22:48:16 +02:00
if (res[i]['annotations']) {
for (const [finger, pos] of Object.entries(res[i]['annotations'])) {
if (finger !== 'palmBase' && Array.isArray(pos) && pos[0]) fingers.push({ name: finger.toLowerCase(), position: pos[0] }); // get tip of each finger
}
2020-11-04 16:18:22 +01:00
}
2020-11-04 20:59:30 +01:00
if (fingers && fingers.length > 0) {
const closest = fingers.reduce((best, a) => (best.position[2] < a.position[2] ? best : a));
2021-07-29 17:01:50 +02:00
gestures.push({ hand: i, gesture: `${closest.name} forward` as HandGesture });
2020-11-04 20:59:30 +01:00
const highest = fingers.reduce((best, a) => (best.position[1] < a.position[1] ? best : a));
2021-07-29 17:01:50 +02:00
gestures.push({ hand: i, gesture: `${highest.name} up` as HandGesture });
2020-11-04 20:59:30 +01:00
}
2021-09-21 22:48:16 +02:00
if (res[i]['keypoints']) {
const poses = fingerPose.match(res[i]['keypoints']);
for (const pose of poses) gestures.push({ hand: i, gesture: pose.name as HandGesture });
}
2020-11-04 16:18:22 +01:00
}
return gestures;
};