Skip to content

Commit 70848f5

Browse files
authored
Add FaceDetection TFJS implementation (#912)
* Add FaceDetection TFJS implementation * Add predictIrises default * Rename classes to clarify use cases * Add TODO for changing face landmarks config
1 parent fe3db69 commit 70848f5

27 files changed

+2271
-637
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.DS_Store
2+
.yalc/
3+
.vscode/
4+
.rpt2_cache/
5+
demos/
6+
scripts/
7+
src/
8+
test_data/
9+
coverage/
10+
node_modules/
11+
karma.conf.js
12+
*.tgz
13+
.travis.yml
14+
.npmignore
15+
tslint.json
16+
yarn.lock
17+
yalc.lock
18+
cloudbuild.yml
19+
dist/*_test.js
20+
dist/*_test.js.map
21+
dist/test_util*

face-landmarks-detection/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ Example output:
5959
height: 246.87222836072945
6060
},
6161
keypoints: [
62-
{x: 406.53152857172876, y: 256.8054528661723, name: "lips"},
63-
{x: 406.544237446397, y: 230.06933367750395},
62+
{x: 406.53152857172876, y: 256.8054528661723, z: 10.2, name: "lips"},
63+
{x: 406.544237446397, y: 230.06933367750395, z: 8},
6464
...
6565
],
6666
}
@@ -69,7 +69,7 @@ Example output:
6969

7070
The `box` represents the bounding box of the face in the image pixel space, with `xMin`, `xMax` denoting the x-bounds, `yMin`, `yMax` denoting the y-bounds, and `width`, `height` are the dimensions of the bounding box.
7171

72-
For the `keypoints`, x and y represent the actual keypoint position in the image pixel space.
72+
For the `keypoints`, x and y represent the actual keypoint position in the image pixel space. z represents the depth with the center of the head being the origin, and the smaller the value the closer the keypoint is to the camera. The magnitude of z uses roughly the same scale as x.
7373

7474
The name provides a label for some keypoint, such as 'lips', 'leftEye', etc. Note that not each keypoint will have a label.
7575

face-landmarks-detection/src/create_detector.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
* =============================================================================
1616
*/
1717

18-
import {FaceDetector} from './face_detector';
19-
import {load as loadMediaPipeFaceMeshMediaPipeDetector} from './mediapipe/detector';
18+
import {FaceLandmarksDetector} from './face_landmarks_detector';
19+
import {load as loadMediaPipeFaceMeshMediaPipeLandmarksDetector} from './mediapipe/detector';
2020
import {MediaPipeFaceMeshMediaPipeModelConfig, MediaPipeFaceMeshModelConfig} from './mediapipe/types';
21+
import {loadMeshModel as loadMediaPipeFaceMeshTfjsLandmarksDetector} from './tfjs/detector';
22+
import {MediaPipeFaceMeshTfjsModelConfig} from './tfjs/types';
2123
import {SupportedModels} from './types';
2224

2325
/**
@@ -28,18 +30,19 @@ import {SupportedModels} from './types';
2830
*/
2931
export async function createDetector(
3032
model: SupportedModels,
31-
modelConfig?: MediaPipeFaceMeshMediaPipeModelConfig):
32-
Promise<FaceDetector> {
33+
modelConfig?: MediaPipeFaceMeshMediaPipeModelConfig|
34+
MediaPipeFaceMeshTfjsModelConfig): Promise<FaceLandmarksDetector> {
3335
switch (model) {
3436
case SupportedModels.MediaPipeFaceMesh:
3537
const config = modelConfig as MediaPipeFaceMeshModelConfig;
3638
let runtime;
3739
if (config != null) {
3840
if (config.runtime === 'tfjs') {
39-
throw new Error('TFJS runtime is not yet supported.');
41+
return loadMediaPipeFaceMeshTfjsLandmarksDetector(
42+
config as MediaPipeFaceMeshTfjsModelConfig);
4043
}
4144
if (config.runtime === 'mediapipe') {
42-
return loadMediaPipeFaceMeshMediaPipeDetector(
45+
return loadMediaPipeFaceMeshMediaPipeLandmarksDetector(
4346
config as MediaPipeFaceMeshMediaPipeModelConfig);
4447
}
4548
runtime = config.runtime;

face-landmarks-detection/src/face_detector.ts renamed to face-landmarks-detection/src/face_landmarks_detector.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
* =============================================================================
1616
*/
1717
import {MediaPipeFaceMeshMediaPipeEstimationConfig} from './mediapipe/types';
18-
import {Face, FaceDetectorInput} from './types';
18+
import {MediaPipeFaceMeshTfjsEstimationConfig} from './tfjs/types';
19+
import {Face, FaceLandmarksDetectorInput} from './types';
1920

2021
/**
2122
* User-facing interface for all face pose detectors.
2223
*/
23-
export interface FaceDetector {
24+
export interface FaceLandmarksDetector {
2425
/**
2526
* Finds faces in the input image.
2627
*
@@ -29,9 +30,9 @@ export interface FaceDetector {
2930
* @param estimationConfig common config for `estimateFaces`.
3031
*/
3132
estimateFaces(
32-
input: FaceDetectorInput,
33-
estimationConfig?: MediaPipeFaceMeshMediaPipeEstimationConfig):
34-
Promise<Face[]>;
33+
input: FaceLandmarksDetectorInput,
34+
estimationConfig?: MediaPipeFaceMeshMediaPipeEstimationConfig|
35+
MediaPipeFaceMeshTfjsEstimationConfig): Promise<Face[]>;
3536

3637
/**
3738
* Dispose the underlying models from memory.

face-landmarks-detection/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616
*/
1717

1818
export {createDetector} from './create_detector';
19-
// FaceDetector class.
20-
export {FaceDetector} from './face_detector';
19+
// FaceLandmarksDetector class.
20+
export {FaceLandmarksDetector} from './face_landmarks_detector';
2121
// Entry point to create a new detector instance.
2222
export {MediaPipeFaceMeshMediaPipeEstimationConfig, MediaPipeFaceMeshMediaPipeModelConfig} from './mediapipe/types';
23+
export {MediaPipeFaceMeshTfjsEstimationConfig, MediaPipeFaceMeshTfjsModelConfig} from './tfjs/types';
2324

2425
// Supported models enum.
2526
export * from './types';

face-landmarks-detection/src/mediapipe/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Pass in `faceLandmarksDetection.SupportedModels.MediaPipeFaceMesh` from the
6363

6464
* *maxFaces*: Defaults to 1. The maximum number of faces that will be detected by the model. The number of returned faces can be less than the maximum (for example when no faces are present in the input). It is highly recommended to set this value to the expected max number of faces, otherwise the model will continue to search for the missing faces which can slow down the performance.
6565

66-
* *predictIrises*: If set to true, refines the landmark coordinates around the eyes and lips, and output additional landmarks around the irises.
66+
* *refineLandmarks*: Defaults to false. If set to true, refines the landmark coordinates around the eyes and lips, and output additional landmarks around the irises.
6767

6868
* *solutionPath*: The path to where the wasm binary and model files are located.
6969

face-landmarks-detection/src/mediapipe/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const DEFAULT_FACE_MESH_MODEL_CONFIG:
2020
MediaPipeFaceMeshMediaPipeModelConfig = {
2121
runtime: 'mediapipe',
2222
maxFaces: 1,
23-
predictIrises: false
23+
refineLandmarks: false
2424
};
2525

2626
export const DEFAULT_FACE_MESH_ESTIMATION_CONFIG:

face-landmarks-detection/src/mediapipe/detector.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,19 @@ import * as faceMesh from '@mediapipe/face_mesh';
1818
import * as tf from '@tensorflow/tfjs-core';
1919

2020
import {MEDIAPIPE_KEYPOINTS} from '../constants';
21-
import {FaceDetector} from '../face_detector';
21+
import {FaceLandmarksDetector} from '../face_landmarks_detector';
2222
import {Keypoint} from '../shared/calculators/interfaces/common_interfaces';
2323
import {landmarksToDetection} from '../shared/calculators/landmarks_to_detection';
24-
import {Face, FaceDetectorInput} from '../types';
24+
import {Face, FaceLandmarksDetectorInput} from '../types';
2525

2626
import {validateModelConfig} from './detector_utils';
2727
import {MediaPipeFaceMeshMediaPipeEstimationConfig, MediaPipeFaceMeshMediaPipeModelConfig} from './types';
2828

2929
/**
3030
* MediaPipe detector class.
3131
*/
32-
class MediaPipeFaceMeshMediaPipeDetector implements FaceDetector {
32+
class MediaPipeFaceMeshMediaPipeLandmarksDetector implements
33+
FaceLandmarksDetector {
3334
private readonly faceMeshSolution: faceMesh.FaceMesh;
3435

3536
// This will be filled out by asynchronous calls to onResults. They will be
@@ -52,7 +53,7 @@ class MediaPipeFaceMeshMediaPipeDetector implements FaceDetector {
5253
}
5354
});
5455
this.faceMeshSolution.setOptions({
55-
refineLandmarks: config.predictIrises,
56+
refineLandmarks: config.refineLandmarks,
5657
selfieMode: this.selfieMode,
5758
maxNumFaces: config.maxFaces,
5859
});
@@ -80,6 +81,7 @@ class MediaPipeFaceMeshMediaPipeDetector implements FaceDetector {
8081
const keypoint: Keypoint = {
8182
x: landmark.x * this.width,
8283
y: landmark.y * this.height,
84+
z: landmark.z * this.width,
8385
};
8486

8587
const name = MEDIAPIPE_KEYPOINTS.get(i);
@@ -113,7 +115,7 @@ class MediaPipeFaceMeshMediaPipeDetector implements FaceDetector {
113115
* @return An array of `Face`s.
114116
*/
115117
async estimateFaces(
116-
input: FaceDetectorInput,
118+
input: FaceLandmarksDetectorInput,
117119
estimationConfig?: MediaPipeFaceMeshMediaPipeEstimationConfig):
118120
Promise<Face[]> {
119121
if (estimationConfig && estimationConfig.flipHorizontal &&
@@ -158,9 +160,9 @@ class MediaPipeFaceMeshMediaPipeDetector implements FaceDetector {
158160
* `MediaPipeFaceMeshMediaPipeModelConfig` interface.
159161
*/
160162
export async function load(modelConfig: MediaPipeFaceMeshMediaPipeModelConfig):
161-
Promise<FaceDetector> {
163+
Promise<FaceLandmarksDetector> {
162164
const config = validateModelConfig(modelConfig);
163-
const detector = new MediaPipeFaceMeshMediaPipeDetector(config);
165+
const detector = new MediaPipeFaceMeshMediaPipeLandmarksDetector(config);
164166
await detector.initialize();
165167
return detector;
166168
}

face-landmarks-detection/src/mediapipe/detector_utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ export function validateModelConfig(
3333
config.maxFaces = DEFAULT_FACE_MESH_MODEL_CONFIG.maxFaces;
3434
}
3535

36-
if (config.predictIrises == null) {
37-
config.predictIrises = DEFAULT_FACE_MESH_MODEL_CONFIG.predictIrises;
36+
if (config.refineLandmarks == null) {
37+
config.refineLandmarks = DEFAULT_FACE_MESH_MODEL_CONFIG.refineLandmarks;
3838
}
3939

4040
return config;

face-landmarks-detection/src/mediapipe/mediapipe_test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ const EXPECTED_BOX: BoundingBox = {
9393
};
9494

9595
export async function expectFaceMesh(
96-
detector: faceDetection.FaceDetector, image: HTMLImageElement,
97-
staticImageMode: boolean, predictIrises: boolean, numFrames: number,
96+
detector: faceDetection.FaceLandmarksDetector, image: HTMLImageElement,
97+
staticImageMode: boolean, refineLandmarks: boolean, numFrames: number,
9898
epsilon: number) {
9999
for (let i = 0; i < numFrames; ++i) {
100100
const result = await detector.estimateFaces(image, {staticImageMode});
@@ -112,14 +112,14 @@ export async function expectFaceMesh(
112112
result[0].keypoints.map(keypoint => [keypoint.x, keypoint.y]);
113113
expect(keypoints.length)
114114
.toBe(
115-
predictIrises ? MEDIAPIPE_FACEMESH_NUM_KEYPOINTS_WITH_IRISES :
116-
MEDIAPIPE_FACEMESH_NUM_KEYPOINTS);
115+
refineLandmarks ? MEDIAPIPE_FACEMESH_NUM_KEYPOINTS_WITH_IRISES :
116+
MEDIAPIPE_FACEMESH_NUM_KEYPOINTS);
117117

118118
for (const [eyeIdx, gtLds] of EYE_INDICES_TO_LANDMARKS) {
119119
expectArraysClose(keypoints[eyeIdx], gtLds, epsilon);
120120
}
121121

122-
if (predictIrises) {
122+
if (refineLandmarks) {
123123
for (const [irisIdx, gtLds] of IRIS_INDICES_TO_LANDMARKS) {
124124
expectArraysClose(keypoints[irisIdx], gtLds, epsilon);
125125
}
@@ -142,15 +142,15 @@ describeWithFlags('MediaPipe FaceMesh ', BROWSER_ENVS, () => {
142142
});
143143

144144
async function expectMediaPipeFaceMesh(
145-
image: HTMLImageElement, staticImageMode: boolean, predictIrises: boolean,
146-
numFrames: number) {
145+
image: HTMLImageElement, staticImageMode: boolean,
146+
refineLandmarks: boolean, numFrames: number) {
147147
// Note: this makes a network request for model assets.
148148
const model = faceDetection.SupportedModels.MediaPipeFaceMesh;
149149
const detector = await faceDetection.createDetector(
150-
model, {...MEDIAPIPE_MODEL_CONFIG, predictIrises});
150+
model, {...MEDIAPIPE_MODEL_CONFIG, refineLandmarks});
151151

152152
await expectFaceMesh(
153-
detector, image, staticImageMode, predictIrises, numFrames,
153+
detector, image, staticImageMode, refineLandmarks, numFrames,
154154
EPSILON_IMAGE);
155155
}
156156

0 commit comments

Comments
 (0)