Skip to content
This repository was archived by the owner on Apr 4, 2023. It is now read-only.

Commit 5973326

Browse files
Expose more configuration and result properties to ML Kit's face detection #704
1 parent c142492 commit 5973326

File tree

10 files changed

+153
-80
lines changed

10 files changed

+153
-80
lines changed

demo-ng/app/tabs/mlkit/facedetection/facedetection.component.html

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
width="100%"
99
height="100%"
1010
processEveryNthFrame="30"
11+
enableFaceTracking="true"
12+
minimumFaceSize="0.2"
13+
modeType="accurate"
1114
(scanResult)="onFaceDetectionResult($event)">
1215
</MLKitFaceDetection>
1316

@@ -37,20 +40,22 @@
3740
<Label row="0" col="1" class="text-center c-white" textWrap="true" color="white" verticalAlignment="center" text="The scanner has been configured to detect faces every 30th frame (default is 10). You can tweak this in your usage of the plugin."></Label>
3841
<StackLayout row="2" col="0" colSpan="3">
3942
<Label [text]="mlKitAllOK" textWrap="true" class="m-t-5 c-purple"></Label>
40-
<GridLayout row="auto" columns="*, *, *" class="m-t-5">
41-
<Label row="0" col="0" class="mlkit-result font-weight-bold" textWrap="true" text="Smiling"></Label>
42-
<Label row="0" col="1" class="mlkit-result font-weight-bold" textWrap="true" text="Left 👁 open"></Label>
43-
<Label row="0" col="2" class="mlkit-result font-weight-bold" textWrap="true" text="Right 👁 open"></Label>
43+
<GridLayout row="auto" columns="60, *, *, *" class="m-t-5">
44+
<Label row="0" col="0" class="mlkit-result font-weight-bold" textWrap="true" text="ID"></Label>
45+
<Label row="0" col="1" class="mlkit-result font-weight-bold" textWrap="true" text="Smiling"></Label>
46+
<Label row="0" col="2" class="mlkit-result font-weight-bold" textWrap="true" text="Left 👁 open"></Label>
47+
<Label row="0" col="3" class="mlkit-result font-weight-bold" textWrap="true" text="Right 👁 open"></Label>
4448
</GridLayout>
4549
</StackLayout>
4650
</GridLayout>
4751
</GridLayout>
4852
<ListView backgroundColor="black" row="1" [items]="faces">
4953
<ng-template let-item="item">
50-
<GridLayout columns="*, *, *">
51-
<Label col="0" class="mlkit-result c-white" [class.c-purple]="item.smilingProbability > 0.7" [text]="item.smilingProbability | number" textWrap="true"></Label>
52-
<Label col="1" class="mlkit-result c-white" [class.c-purple]="item.leftEyeOpenProbability > 0.7" [text]="item.leftEyeOpenProbability | number" textWrap="true"></Label>
53-
<Label col="2" class="mlkit-result c-white" [class.c-purple]="item.rightEyeOpenProbability > 0.7" [text]="item.rightEyeOpenProbability | number" textWrap="true"></Label>
54+
<GridLayout columns="50, *, *, *">
55+
<Label col="0" class="mlkit-result c-white" [text]="item.trackingId" textWrap="true"></Label>
56+
<Label col="1" class="mlkit-result c-white" [class.c-purple]="item.smilingProbability > 0.7" [text]="item.smilingProbability | number" textWrap="true"></Label>
57+
<Label col="2" class="mlkit-result c-white" [class.c-purple]="item.leftEyeOpenProbability > 0.7" [text]="item.leftEyeOpenProbability | number" textWrap="true"></Label>
58+
<Label col="3" class="mlkit-result c-white" [class.c-purple]="item.rightEyeOpenProbability > 0.7" [text]="item.rightEyeOpenProbability | number" textWrap="true"></Label>
5459
</GridLayout>
5560
</ng-template>
5661
</ListView>

demo-ng/app/tabs/mlkit/mlkit.component.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,10 @@ export class MLKitComponent {
236236

237237
private detectFacesOnDevice(imageSource: ImageSource): void {
238238
firebase.mlkit.facedetection.detectFacesOnDevice({
239-
image: imageSource
239+
image: imageSource,
240+
detectionMode: "accurate",
241+
enableFaceTracking: false,
242+
minimumFaceSize: 0.25
240243
}).then(
241244
(result: MLKitDetectFacesOnDeviceResult) => {
242245
alert({

docs/ML_KIT.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ import { MLKitDetectFacesOnDeviceResult } from "nativescript-plugin-firebase/mlk
171171
const firebase = require("nativescript-plugin-firebase");
172172
173173
firebase.mlkit.facedetection.detectFacesOnDevice({
174-
image: imageSource // a NativeScript Image or ImageSource, see the demo for examples
174+
image: imageSource, // a NativeScript Image or ImageSource, see the demo for examples
175+
detectionMode: "accurate", // default "fast"
176+
enableFaceTracking: true, // default false
177+
minimumFaceSize: 0.25 // default 0.1 (which means the face must be at least 10% of the image)
175178
})
176179
.then((result: MLKitDetectFacesOnDeviceResult) => console.log(JSON.stringify(result.faces)))
177180
.catch(errorMessage => console.log("ML Kit error: " + errorMessage));
@@ -189,6 +192,9 @@ registerElement("MLKitFaceDetection", () => require("nativescript-plugin-firebas
189192
<MLKitFaceDetection
190193
width="260"
191194
height="380"
195+
detectionMode="accurate"
196+
enableFaceTracking="true"
197+
minimumFaceSize="0.2"
192198
(scanResult)="onFaceDetectionResult($event)">
193199
</MLKitFaceDetection>
194200
```
Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,46 @@
1-
import { booleanConverter } from "tns-core-modules/ui/core/view-base";
1+
import { booleanConverter, makeParser, makeValidator } from "tns-core-modules/ui/core/view-base";
22
import { Property } from "tns-core-modules/ui/core/properties";
33
import { MLKitCameraView } from "../mlkit-cameraview";
4+
import { MLKitFaceDetectionMode } from "./";
45

5-
export const preferFrontCameraProperty = new Property<MLKitFaceDetection, boolean>({
6-
name: "preferFrontCamera",
6+
export const minimumFaceSizeProperty = new Property<MLKitFaceDetection, number>({
7+
name: "minimumFaceSize",
8+
defaultValue: 0.1
9+
});
10+
11+
export const enableFaceTrackingProperty = new Property<MLKitFaceDetection, boolean>({
12+
name: "enableFaceTracking",
713
defaultValue: false,
814
valueConverter: booleanConverter
915
});
1016

17+
const detectionModeConverter = makeParser<MLKitFaceDetectionMode>(makeValidator<MLKitFaceDetectionMode>("accurate", "fast"));
18+
export const detectionModeProperty = new Property<MLKitFaceDetection, MLKitFaceDetectionMode>({
19+
name: "detectionMode",
20+
defaultValue: "fast",
21+
valueConverter: detectionModeConverter
22+
});
23+
1124
export abstract class MLKitFaceDetection extends MLKitCameraView {
1225
static scanResultEvent: string = "scanResult";
1326

14-
protected formats: string;
15-
protected preferFrontCamera: boolean;
27+
protected enableFaceTracking: boolean;
28+
protected detectionMode: MLKitFaceDetectionMode;
29+
protected minimumFaceSize: number;
30+
31+
[minimumFaceSizeProperty.setNative](value: number) {
32+
this.minimumFaceSize = value;
33+
}
34+
35+
[enableFaceTrackingProperty.setNative](value: boolean) {
36+
this.enableFaceTracking = value;
37+
}
1638

17-
[preferFrontCameraProperty.setNative](value: boolean) {
18-
this.preferFrontCamera = value;
39+
[detectionModeProperty.setNative](value: MLKitFaceDetectionMode) {
40+
this.detectionMode = value;
1941
}
2042
}
2143

22-
preferFrontCameraProperty.register(MLKitFaceDetection);
44+
minimumFaceSizeProperty.register(MLKitFaceDetection);
45+
enableFaceTrackingProperty.register(MLKitFaceDetection);
46+
detectionModeProperty.register(MLKitFaceDetection);

src/mlkit/facedetection/index.android.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ declare const com: any;
88
export class MLKitFaceDetection extends MLKitFaceDetectionBase {
99

1010
protected createDetector(): any {
11-
return getFaceDetector();
11+
return getFaceDetector({
12+
detectionMode: this.detectionMode,
13+
enableFaceTracking: this.enableFaceTracking,
14+
minimumFaceSize: this.minimumFaceSize
15+
});
1216
}
1317

1418
protected createSuccessListener(): any {
@@ -31,7 +35,8 @@ export class MLKitFaceDetection extends MLKitFaceDetectionBase {
3135
result.faces.push({
3236
smilingProbability: face.getSmilingProbability() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.UNCOMPUTED_PROBABILITY ? face.getSmilingProbability() : undefined,
3337
leftEyeOpenProbability: face.getLeftEyeOpenProbability() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.UNCOMPUTED_PROBABILITY ? face.getLeftEyeOpenProbability() : undefined,
34-
rightEyeOpenProbability: face.getRightEyeOpenProbability() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.UNCOMPUTED_PROBABILITY ? face.getRightEyeOpenProbability() : undefined
38+
rightEyeOpenProbability: face.getRightEyeOpenProbability() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.UNCOMPUTED_PROBABILITY ? face.getRightEyeOpenProbability() : undefined,
39+
trackingId: face.getTrackingId() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.INVALID_ID ? face.getTrackingId() : undefined
3540
});
3641
}
3742

@@ -45,14 +50,14 @@ export class MLKitFaceDetection extends MLKitFaceDetectionBase {
4550
}
4651
}
4752

48-
function getFaceDetector(): any {
53+
function getFaceDetector(options: MLKitDetectFacesOnDeviceOptions): any {
4954
const faceDetectorOptions =
5055
new com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions.Builder()
51-
.setModeType(com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions.ACCURATE_MODE)
52-
.setLandmarkType(com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS)
53-
.setClassificationType(com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
54-
.setMinFaceSize(0.15)
55-
.setTrackingEnabled(false)
56+
.setModeType(options.detectionMode === "accurate" ? com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions.ACCURATE_MODE : com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions.FAST_MODE)
57+
// .setLandmarkType(com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS) // TODO
58+
// .setClassificationType(com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS) // TODO
59+
.setMinFaceSize(options.minimumFaceSize)
60+
.setTrackingEnabled(options.enableFaceTracking === true)
5661
.build();
5762

5863
return com.google.firebase.ml.vision.FirebaseVision.getInstance().getVisionFaceDetector(faceDetectorOptions);
@@ -61,7 +66,7 @@ function getFaceDetector(): any {
6166
export function detectFacesOnDevice(options: MLKitDetectFacesOnDeviceOptions): Promise<MLKitDetectFacesOnDeviceResult> {
6267
return new Promise((resolve, reject) => {
6368
try {
64-
const firebaseVisionFaceDetector = getFaceDetector();
69+
const firebaseVisionFaceDetector = getFaceDetector(options);
6570

6671
const onSuccessListener = new com.google.android.gms.tasks.OnSuccessListener({
6772
onSuccess: (faces) => {
@@ -76,7 +81,8 @@ export function detectFacesOnDevice(options: MLKitDetectFacesOnDeviceOptions): P
7681
result.faces.push({
7782
smilingProbability: face.getSmilingProbability() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.UNCOMPUTED_PROBABILITY ? face.getSmilingProbability() : undefined,
7883
leftEyeOpenProbability: face.getLeftEyeOpenProbability() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.UNCOMPUTED_PROBABILITY ? face.getLeftEyeOpenProbability() : undefined,
79-
rightEyeOpenProbability: face.getRightEyeOpenProbability() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.UNCOMPUTED_PROBABILITY ? face.getRightEyeOpenProbability() : undefined
84+
rightEyeOpenProbability: face.getRightEyeOpenProbability() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.UNCOMPUTED_PROBABILITY ? face.getRightEyeOpenProbability() : undefined,
85+
trackingId: face.getTrackingId() !== com.google.firebase.ml.vision.face.FirebaseVisionFace.INVALID_ID ? face.getTrackingId() : undefined
8086
});
8187
}
8288

src/mlkit/facedetection/index.d.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { MLKitOptions } from "../";
22
import { MLKitCameraView, MLKitResult } from "../index";
33

44
export interface MLKitDetectFacesResultFace {
5-
// TODO there are more properties we can return, see https://firebase.google.com/docs/ml-kit/android/detect-faces
65
smilingProbability?: number;
76
leftEyeOpenProbability?: number;
87
rightEyeOpenProbability?: number;
8+
trackingId?: number;
99
ios?: any;
1010
android?: any;
1111
}
@@ -14,8 +14,33 @@ export interface MLKitDetectFacesOnDeviceResult extends MLKitResult {
1414
faces: Array<MLKitDetectFacesResultFace>;
1515
}
1616

17+
export type MLKitFaceDetectionMode = "fast" | "accurate";
18+
1719
export interface MLKitDetectFacesOnDeviceOptions extends MLKitOptions {
18-
// TODO there are a few options here
20+
/**
21+
* Favor speed or accuracy when detecting faces.
22+
* Default "fast".
23+
*/
24+
detectionMode?: MLKitFaceDetectionMode;
25+
26+
/**
27+
* Whether or not to attempt to identify facial "landmarks": eyes, ears, nose, cheeks, mouth.
28+
* Default "none".
29+
*
30+
// detectLandmarks?: "none" | "all";
31+
*/
32+
33+
/**
34+
* The minimum size, relative to the image, of faces to detect.
35+
* Default 0.1.
36+
*/
37+
minimumFaceSize?: number;
38+
39+
/**
40+
* Whether or not to assign faces an ID, which can be used to track faces across images.
41+
* Default false.
42+
*/
43+
enableFaceTracking?: boolean;
1944
}
2045

2146
export declare function detectFacesOnDevice(options: MLKitDetectFacesOnDeviceOptions): Promise<MLKitDetectFacesOnDeviceResult>;

src/mlkit/facedetection/index.ios.ts

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ import { ImageSource } from "tns-core-modules/image-source";
22
import { MLKitDetectFacesOnDeviceOptions, MLKitDetectFacesOnDeviceResult } from "./";
33
import { MLKitOptions } from "../index";
44
import { MLKitFaceDetection as MLKitFaceDetectionBase } from "./facedetection-common";
5+
import { ios as iosUtils } from "tns-core-modules/utils/utils";
56

67
export class MLKitFaceDetection extends MLKitFaceDetectionBase {
78

89
protected createDetector(): any {
9-
return getDetector();
10+
return getDetector({
11+
detectionMode: this.detectionMode,
12+
enableFaceTracking: this.enableFaceTracking,
13+
minimumFaceSize: this.minimumFaceSize
14+
});
1015
}
1116

1217
protected createSuccessListener(): any {
@@ -25,7 +30,8 @@ export class MLKitFaceDetection extends MLKitFaceDetectionBase {
2530
result.faces.push({
2631
smilingProbability: face.hasSmilingProbability ? face.smilingProbability : undefined,
2732
leftEyeOpenProbability: face.hasLeftEyeOpenProbability ? face.leftEyeOpenProbability : undefined,
28-
rightEyeOpenProbability: face.hasRightEyeOpenProbability ? face.rightEyeOpenProbability : undefined
33+
rightEyeOpenProbability: face.hasRightEyeOpenProbability ? face.rightEyeOpenProbability : undefined,
34+
trackingId: face.hasTrackingID ? face.trackingID : undefined
2935
});
3036
}
3137

@@ -42,25 +48,32 @@ export class MLKitFaceDetection extends MLKitFaceDetectionBase {
4248
protected rotateRecording(): boolean {
4349
return false;
4450
}
51+
52+
getVisionOrientation(imageOrientation: UIImageOrientation): FIRVisionDetectorImageOrientation {
53+
if (imageOrientation === UIImageOrientation.Up && !iosUtils.isLandscape()) {
54+
return FIRVisionDetectorImageOrientation.RightTop;
55+
} else {
56+
return super.getVisionOrientation(imageOrientation);
57+
}
58+
}
4559
}
4660

47-
function getDetector(): FIRVisionFaceDetector {
48-
// TODO make configurable (see #704)
61+
function getDetector(options: MLKitDetectFacesOnDeviceOptions): FIRVisionFaceDetector {
4962
const firVision: FIRVision = FIRVision.vision();
50-
const options = FIRVisionFaceDetectorOptions.new();
51-
options.modeType = FIRVisionFaceDetectorMode.Accurate;
52-
options.landmarkType = FIRVisionFaceDetectorLandmark.All;
53-
options.classificationType = FIRVisionFaceDetectorClassification.All;
54-
options.minFaceSize = 0.1;
55-
// options.isTrackingEnabled = true;
56-
return firVision.faceDetectorWithOptions(options);
63+
const firOptions = FIRVisionFaceDetectorOptions.new();
64+
firOptions.modeType = options.detectionMode === "accurate" ? FIRVisionFaceDetectorMode.Accurate : FIRVisionFaceDetectorMode.Fast;
65+
// firOptions.landmarkType = FIRVisionFaceDetectorLandmark.All; // TODO
66+
// firOptions.classificationType = FIRVisionFaceDetectorClassification.All; // TODO
67+
firOptions.minFaceSize = options.minimumFaceSize;
68+
firOptions.isTrackingEnabled = options.enableFaceTracking === true;
69+
return firVision.faceDetectorWithOptions(firOptions);
5770
}
5871

5972
// TODO somehow this function doesn't work.. probably because of the passed image, but I can't find the cause.. the live camera version works great tho
6073
export function detectFacesOnDevice(options: MLKitDetectFacesOnDeviceOptions): Promise<MLKitDetectFacesOnDeviceResult> {
6174
return new Promise((resolve, reject) => {
6275
try {
63-
const faceDetector = getDetector();
76+
const faceDetector = getDetector(options);
6477
faceDetector.detectInImageCompletion(getImage(options), (faces: NSArray<FIRVisionFace>, error: NSError) => {
6578
if (error !== null) {
6679
reject(error.localizedDescription);
@@ -75,7 +88,8 @@ export function detectFacesOnDevice(options: MLKitDetectFacesOnDeviceOptions): P
7588
result.faces.push({
7689
smilingProbability: face.hasSmilingProbability ? face.smilingProbability : undefined,
7790
leftEyeOpenProbability: face.hasLeftEyeOpenProbability ? face.leftEyeOpenProbability : undefined,
78-
rightEyeOpenProbability: face.hasRightEyeOpenProbability ? face.rightEyeOpenProbability : undefined
91+
rightEyeOpenProbability: face.hasRightEyeOpenProbability ? face.rightEyeOpenProbability : undefined,
92+
trackingId: face.hasTrackingID ? face.trackingID : undefined
7993
});
8094
}
8195
resolve(result);
@@ -88,19 +102,7 @@ export function detectFacesOnDevice(options: MLKitDetectFacesOnDeviceOptions): P
88102
});
89103
}
90104

91-
// TODO resize the image (here), like the example app?
92105
function getImage(options: MLKitOptions): FIRVisionImage {
93106
const image: UIImage = options.image instanceof ImageSource ? options.image.ios : options.image.imageSource.ios;
94-
console.log(">> image.imageOrientation: " + image.imageOrientation);
95-
96-
const fIRVisionImageMetadata = FIRVisionImageMetadata.new();
97-
// fIRVisionImageMetadata.orientation = FIRVisionDetectorImageOrientation.TopLeft;
98-
99-
// const randomOrientation = Math.floor(Math.random() * 8) + 1;
100-
// console.log(">>> randomOrientation: " + randomOrientation);
101-
fIRVisionImageMetadata.orientation = 1;
102-
103-
const fIRVisionImage = FIRVisionImage.alloc().initWithImage(image);
104-
fIRVisionImage.metadata = fIRVisionImageMetadata;
105-
return fIRVisionImage;
107+
return FIRVisionImage.alloc().initWithImage(image);
106108
}

src/mlkit/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ImageSource } from "tns-core-modules/image-source";
88
import { Image } from "tns-core-modules/ui/image";
99

1010
export interface MLKitOptions {
11-
image: Image | ImageSource;
11+
image?: Image | ImageSource;
1212
}
1313

1414
export type MLKitCloudModelType = "stable" | "latest";

src/mlkit/mlkit-cameraview.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ export declare abstract class MLKitCameraView extends MLKitCameraViewBase {
66
protected abstract createDetector(): any;
77

88
protected abstract createSuccessListener(): any;
9+
10+
getVisionOrientation(imageOrientation: any): any;
911
}
1012

0 commit comments

Comments
 (0)