update face angle algorithm

pull/46/head
Vladimir Mandic 2021-03-08 08:55:51 -05:00
parent c0d2eda2d7
commit 77fb56eb1a
21 changed files with 61 additions and 53 deletions

View File

@ -525,6 +525,9 @@ export type WithFaceLandmarks<TSource> = TSource & {
unshiftedLandmarks: FaceLandmarks
landmarks: FaceLandmarks
alignedRect: FaceDetection
angle: { roll: number, yaw: number, pitch: number }
// for angle all values are in radians in range of -pi/2 to pi/2 which is -90 to +90 degrees
// value of 0 means center
}
```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1221,7 +1221,7 @@
]
},
"src/factories/WithFaceLandmarks.ts": {
"bytes": 3288,
"bytes": 3592,
"imports": [
{
"path": "src/classes/FaceDetection.ts",
@ -2591,7 +2591,7 @@
"imports": [],
"exports": [],
"inputs": {},
"bytes": 315561
"bytes": 315931
},
"dist/face-api.esm-nobundle.js": {
"imports": [],
@ -2981,7 +2981,7 @@
"bytesInOutput": 420
},
"src/factories/WithFaceLandmarks.ts": {
"bytesInOutput": 784
"bytesInOutput": 829
},
"src/draw/DrawFaceLandmarks.ts": {
"bytesInOutput": 1225
@ -3188,7 +3188,7 @@
"bytesInOutput": 443
}
},
"bytes": 82590
"bytes": 82635
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1221,7 +1221,7 @@
]
},
"src/factories/WithFaceLandmarks.ts": {
"bytes": 3288,
"bytes": 3592,
"imports": [
{
"path": "src/classes/FaceDetection.ts",
@ -2591,7 +2591,7 @@
"imports": [],
"exports": [],
"inputs": {},
"bytes": 1466296
"bytes": 1466666
},
"dist/face-api.esm.js": {
"imports": [],
@ -2978,7 +2978,7 @@
"bytesInOutput": 422
},
"src/factories/WithFaceLandmarks.ts": {
"bytesInOutput": 790
"bytesInOutput": 835
},
"src/draw/DrawFaceLandmarks.ts": {
"bytesInOutput": 1228
@ -3188,7 +3188,7 @@
"bytesInOutput": 446
}
},
"bytes": 1127159
"bytes": 1127204
}
}
}

2
dist/face-api.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

8
dist/face-api.json vendored
View File

@ -1221,7 +1221,7 @@
]
},
"src/factories/WithFaceLandmarks.ts": {
"bytes": 3288,
"bytes": 3592,
"imports": [
{
"path": "src/classes/FaceDetection.ts",
@ -2591,7 +2591,7 @@
"imports": [],
"exports": [],
"inputs": {},
"bytes": 1466303
"bytes": 1466673
},
"dist/face-api.js": {
"imports": [],
@ -2860,7 +2860,7 @@
"bytesInOutput": 422
},
"src/factories/WithFaceLandmarks.ts": {
"bytesInOutput": 790
"bytesInOutput": 835
},
"src/draw/DrawFaceLandmarks.ts": {
"bytesInOutput": 1228
@ -3067,7 +3067,7 @@
"bytesInOutput": 446
}
},
"bytes": 1127322
"bytes": 1127367
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1221,7 +1221,7 @@
]
},
"src/factories/WithFaceLandmarks.ts": {
"bytes": 3288,
"bytes": 3592,
"imports": [
{
"path": "src/classes/FaceDetection.ts",
@ -2591,7 +2591,7 @@
"imports": [],
"exports": [],
"inputs": {},
"bytes": 315437
"bytes": 315807
},
"dist/face-api.node-cpu.js": {
"imports": [],
@ -2860,7 +2860,7 @@
"bytesInOutput": 420
},
"src/factories/WithFaceLandmarks.ts": {
"bytesInOutput": 784
"bytesInOutput": 829
},
"src/draw/DrawFaceLandmarks.ts": {
"bytesInOutput": 1225
@ -3067,7 +3067,7 @@
"bytesInOutput": 443
}
},
"bytes": 83302
"bytes": 83347
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1221,7 +1221,7 @@
]
},
"src/factories/WithFaceLandmarks.ts": {
"bytes": 3288,
"bytes": 3592,
"imports": [
{
"path": "src/classes/FaceDetection.ts",
@ -2591,7 +2591,7 @@
"imports": [],
"exports": [],
"inputs": {},
"bytes": 315446
"bytes": 315816
},
"dist/face-api.node-gpu.js": {
"imports": [],
@ -2860,7 +2860,7 @@
"bytesInOutput": 420
},
"src/factories/WithFaceLandmarks.ts": {
"bytesInOutput": 784
"bytesInOutput": 829
},
"src/draw/DrawFaceLandmarks.ts": {
"bytesInOutput": 1225
@ -3067,7 +3067,7 @@
"bytesInOutput": 443
}
},
"bytes": 83311
"bytes": 83356
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1221,7 +1221,7 @@
]
},
"src/factories/WithFaceLandmarks.ts": {
"bytes": 3288,
"bytes": 3592,
"imports": [
{
"path": "src/classes/FaceDetection.ts",
@ -2591,7 +2591,7 @@
"imports": [],
"exports": [],
"inputs": {},
"bytes": 315438
"bytes": 315808
},
"dist/face-api.node.js": {
"imports": [],
@ -2860,7 +2860,7 @@
"bytesInOutput": 420
},
"src/factories/WithFaceLandmarks.ts": {
"bytesInOutput": 784
"bytesInOutput": 829
},
"src/draw/DrawFaceLandmarks.ts": {
"bytesInOutput": 1225
@ -3067,7 +3067,7 @@
"bytesInOutput": 443
}
},
"bytes": 83303
"bytes": 83348
}
}
}

View File

@ -46,7 +46,7 @@ function drawFaces(canvas, data, fps) {
ctx.fillText(`gender ${Math.round(100 * person.genderProbability)}% ${person.gender}`, person.detection.box.x, person.detection.box.y - 60);
ctx.fillText(`expression ${Math.round(100 * expression[0][1])}% ${expression[0][0]}`, person.detection.box.x, person.detection.box.y - 42);
ctx.fillText(`age ${Math.round(person.age)} years`, person.detection.box.x, person.detection.box.y - 24);
ctx.fillText(`roll:${Math.trunc(1000 * person.angle.roll) / 1000} pitch:${Math.trunc(1000 * person.angle.pitch) / 1000} yaw:${Math.trunc(1000 * person.angle.yaw) / 1000}`, person.detection.box.x, person.detection.box.y - 6);
ctx.fillText(`roll:${person.angle.roll.toFixed(3)} pitch:${person.angle.pitch.toFixed(3)} yaw:${person.angle.yaw.toFixed(3)}`, person.detection.box.x, person.detection.box.y - 6);
// draw face points for each face
ctx.fillStyle = 'lightblue';
ctx.globalAlpha = 0.5;

View File

@ -23,31 +23,36 @@ export function isWithFaceLandmarks(obj: any): obj is WithFaceLandmarks<WithFace
}
function calculateFaceAngle(mesh) {
const radians = (a1, a2, b1, b2) => Math.atan2(b2 - a2, b1 - a1);
// returns the angle in the plane (in radians) between the positive x-axis and the ray from (0,0) to the point (x,y)
const radians = (a1, a2, b1, b2) => (Math.atan2(b2 - a2, b1 - a1) % Math.PI);
// convert radians to degrees
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const degrees = (theta) => (theta * 180) / Math.PI;
const angle = { roll: <number | undefined>undefined, pitch: <number | undefined>undefined, yaw: <number | undefined>undefined };
if (!mesh || !mesh._positions || mesh._positions.length !== 68) return angle;
const pt = mesh._positions;
// roll is face lean left/right
// values are in radians in range of -pi/2 to pi/2 which is -90 to +90 degrees
// value of 0 means center
// roll is face lean from left to right
// comparing x,y of outside corners of leftEye and rightEye
angle.roll = radians(pt[36]._x, pt[36]._y, pt[45]._x, pt[45]._y);
angle.roll = -radians(pt[36]._x, pt[36]._y, pt[45]._x, pt[45]._y);
// yaw is face turn left/right
// comparing x distance of bottom of nose to left and right edge of face
// and y distance of top of nose to left and right edge of face
// pitch is face turn from left right
// comparing x distance of top of nose to left and right edge of face
// precision is lacking since coordinates are not precise enough
angle.pitch = radians(pt[30]._x - pt[0]._x, pt[27]._y - pt[0]._y, pt[16]._x - pt[30]._x, pt[27]._y - pt[16]._y);
angle.pitch = radians(0, Math.abs(pt[0]._x - pt[30]._x) / pt[30]._x, Math.PI, Math.abs(pt[16]._x - pt[30]._x) / pt[30]._x);
// pitch is face move up/down
// yaw is face move from up to down
// comparing size of the box around the face with top and bottom of detected landmarks
// silly hack, but this gives us face compression on y-axis
// e.g., tilting head up hides the forehead that doesn't have any landmarks so ratio drops
// value is normalized to range, but is not in actual radians
const bottom = pt.reduce((prev, cur) => (prev < cur._y ? prev : cur._y), +Infinity);
const top = pt.reduce((prev, cur) => (prev > cur._y ? prev : cur._y), -Infinity);
angle.yaw = 10 * (mesh._imgDims._height / (top - bottom) / 1.45 - 1);
angle.yaw = Math.PI * (mesh._imgDims._height / (top - bottom) / 1.40 - 1);
return angle;
}