update face angle algorithm
parent
c0d2eda2d7
commit
77fb56eb1a
|
@ -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
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue