Skip to content

Commit 2b794bc

Browse files
added mtcnn e2e tests + check in final model weights
1 parent 1d133c5 commit 2b794bc

10 files changed

+183
-41
lines changed

src/mtcnn/Mtcnn.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class Mtcnn extends NeuralNetwork<NetParams> {
7272
scaleSteps
7373
} = Object.assign({}, getDefaultMtcnnForwardParams(), forwardParams)
7474

75-
const scales = scaleSteps || pyramidDown(minFaceSize, scaleFactor, [height, width])
75+
const scales = (scaleSteps || pyramidDown(minFaceSize, scaleFactor, [height, width]))
7676
.filter(scale => {
7777
const sizes = getSizesForScale(scale, [height, width])
7878
return Math.min(sizes.width, sizes.height) > CELL_SIZE

test/tests/NetInput.test.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
import * as tf from '@tensorflow/tfjs-core';
2-
31
import { NetInput } from '../../src/NetInput';
4-
import { bufferToImage, createCanvasFromMedia } from '../../src/utils';
2+
import { bufferToImage } from '../../src/utils';
53
import { expectAllTensorsReleased, tensor3D } from '../utils';
64

7-
85
describe('NetInput', () => {
96

107
let imgEl: HTMLImageElement

test/tests/e2e/faceDetectionNet.test.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
11
import * as faceapi from '../../../src';
2-
import { FaceDetection } from '../../../src/faceDetectionNet/FaceDetection';
3-
import { IRect } from '../../../src/Rect';
4-
import { describeWithNets, expectAllTensorsReleased, expectMaxDelta } from '../../utils';
5-
6-
function expectRectClose(
7-
result: IRect,
8-
expectedBox: IRect,
9-
maxDelta: number
10-
) {
11-
const { x, y, width, height } = result
12-
expectMaxDelta(x, expectedBox.x, maxDelta)
13-
expectMaxDelta(y, expectedBox.y, maxDelta)
14-
expectMaxDelta(width, expectedBox.width, maxDelta)
15-
expectMaxDelta(height, expectedBox.height, maxDelta)
16-
}
2+
import { describeWithNets, expectAllTensorsReleased, expectRectClose } from '../../utils';
173

184
const expectedBoxes = [
195
{ x: 48, y: 253, width: 104, height: 129 },
@@ -39,7 +25,7 @@ describe('faceDetectionNet', () => {
3925
const maxBoxDelta = 1
4026

4127
it('scores > 0.8', async () => {
42-
const detections = await faceDetectionNet.locateFaces(imgEl) as FaceDetection[]
28+
const detections = await faceDetectionNet.locateFaces(imgEl) as faceapi.FaceDetection[]
4329

4430
expect(detections.length).toEqual(3)
4531
detections.forEach((det, i) => {
@@ -51,7 +37,7 @@ describe('faceDetectionNet', () => {
5137
})
5238

5339
it('scores > 0.5', async () => {
54-
const detections = await faceDetectionNet.locateFaces(imgEl, 0.5) as FaceDetection[]
40+
const detections = await faceDetectionNet.locateFaces(imgEl, 0.5) as faceapi.FaceDetection[]
5541

5642
expect(detections.length).toEqual(6)
5743
detections.forEach((det, i) => {
@@ -70,7 +56,7 @@ describe('faceDetectionNet', () => {
7056
const maxBoxDelta = 5
7157

7258
it('scores > 0.8', async () => {
73-
const detections = await faceDetectionNet.locateFaces(imgEl) as FaceDetection[]
59+
const detections = await faceDetectionNet.locateFaces(imgEl) as faceapi.FaceDetection[]
7460

7561
expect(detections.length).toEqual(4)
7662
detections.forEach((det, i) => {
@@ -82,7 +68,7 @@ describe('faceDetectionNet', () => {
8268
})
8369

8470
it('scores > 0.5', async () => {
85-
const detections = await faceDetectionNet.locateFaces(imgEl, 0.5) as FaceDetection[]
71+
const detections = await faceDetectionNet.locateFaces(imgEl, 0.5) as faceapi.FaceDetection[]
8672

8773
expect(detections.length).toEqual(6)
8874
detections.forEach((det, i) => {
@@ -103,7 +89,7 @@ describe('faceDetectionNet', () => {
10389
await expectAllTensorsReleased(async () => {
10490
const res = await fetch('base/weights/uncompressed/face_detection_model.weights')
10591
const weights = new Float32Array(await res.arrayBuffer())
106-
const net = faceapi.faceDetectionNet(weights)
92+
const net = faceapi.createFaceDetectionNet(weights)
10793
net.dispose()
10894
})
10995
})

test/tests/e2e/faceLandmarkNet.test.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as tf from '@tensorflow/tfjs-core';
22

33
import * as faceapi from '../../../src';
44
import { isTensor3D } from '../../../src/commons/isTensor';
5-
import { FaceLandmarks } from '../../../src/faceLandmarkNet/FaceLandmarks';
65
import { Point } from '../../../src/Point';
76
import { Dimensions, TMediaElement } from '../../../src/types';
87
import { expectMaxDelta, expectAllTensorsReleased, tensor3D, describeWithNets } from '../../utils';
@@ -43,7 +42,7 @@ describe('faceLandmarkNet', () => {
4342
it('computes face landmarks for squared input', async () => {
4443
const { width, height } = imgEl1
4544

46-
const result = await faceLandmarkNet.detectLandmarks(imgEl1) as FaceLandmarks
45+
const result = await faceLandmarkNet.detectLandmarks(imgEl1) as faceapi.FaceLandmarks68
4746
expect(result.getImageWidth()).toEqual(width)
4847
expect(result.getImageHeight()).toEqual(height)
4948
expect(result.getShift().x).toEqual(0)
@@ -57,7 +56,7 @@ describe('faceLandmarkNet', () => {
5756
it('computes face landmarks for rectangular input', async () => {
5857
const { width, height } = imgElRect
5958

60-
const result = await faceLandmarkNet.detectLandmarks(imgElRect) as FaceLandmarks
59+
const result = await faceLandmarkNet.detectLandmarks(imgElRect) as faceapi.FaceLandmarks68
6160
expect(result.getImageWidth()).toEqual(width)
6261
expect(result.getImageHeight()).toEqual(height)
6362
expect(result.getShift().x).toEqual(0)
@@ -75,7 +74,7 @@ describe('faceLandmarkNet', () => {
7574
it('computes face landmarks for squared input', async () => {
7675
const { width, height } = imgEl1
7776

78-
const result = await faceLandmarkNet.detectLandmarks(imgEl1) as FaceLandmarks
77+
const result = await faceLandmarkNet.detectLandmarks(imgEl1) as faceapi.FaceLandmarks68
7978
expect(result.getImageWidth()).toEqual(width)
8079
expect(result.getImageHeight()).toEqual(height)
8180
expect(result.getShift().x).toEqual(0)
@@ -89,7 +88,7 @@ describe('faceLandmarkNet', () => {
8988
it('computes face landmarks for rectangular input', async () => {
9089
const { width, height } = imgElRect
9190

92-
const result = await faceLandmarkNet.detectLandmarks(imgElRect) as FaceLandmarks
91+
const result = await faceLandmarkNet.detectLandmarks(imgElRect) as faceapi.FaceLandmarks68
9392
expect(result.getImageWidth()).toEqual(width)
9493
expect(result.getImageHeight()).toEqual(height)
9594
expect(result.getShift().x).toEqual(0)
@@ -113,7 +112,7 @@ describe('faceLandmarkNet', () => {
113112
faceLandmarkPositionsRect
114113
]
115114

116-
const results = await faceLandmarkNet.detectLandmarks(inputs) as FaceLandmarks[]
115+
const results = await faceLandmarkNet.detectLandmarks(inputs) as faceapi.FaceLandmarks68[]
117116
expect(Array.isArray(results)).toBe(true)
118117
expect(results.length).toEqual(3)
119118
results.forEach((result, batchIdx) => {
@@ -138,7 +137,7 @@ describe('faceLandmarkNet', () => {
138137
faceLandmarkPositionsRect
139138
]
140139

141-
const results = await faceLandmarkNet.detectLandmarks(inputs) as FaceLandmarks[]
140+
const results = await faceLandmarkNet.detectLandmarks(inputs) as faceapi.FaceLandmarks68[]
142141
expect(Array.isArray(results)).toBe(true)
143142
expect(results.length).toEqual(3)
144143
results.forEach((result, batchIdx) => {
@@ -163,7 +162,7 @@ describe('faceLandmarkNet', () => {
163162
faceLandmarkPositionsRect
164163
]
165164

166-
const results = await faceLandmarkNet.detectLandmarks(tf.stack(inputs) as tf.Tensor4D) as FaceLandmarks[]
165+
const results = await faceLandmarkNet.detectLandmarks(tf.stack(inputs) as tf.Tensor4D) as faceapi.FaceLandmarks68[]
167166
expect(Array.isArray(results)).toBe(true)
168167
expect(results.length).toEqual(2)
169168
results.forEach((result, batchIdx) => {
@@ -188,7 +187,7 @@ describe('faceLandmarkNet', () => {
188187
faceLandmarkPositionsRect
189188
]
190189

191-
const results = await faceLandmarkNet.detectLandmarks(inputs) as FaceLandmarks[]
190+
const results = await faceLandmarkNet.detectLandmarks(inputs) as faceapi.FaceLandmarks68[]
192191
expect(Array.isArray(results)).toBe(true)
193192
expect(results.length).toEqual(3)
194193
results.forEach((result, batchIdx) => {
@@ -214,7 +213,7 @@ describe('faceLandmarkNet', () => {
214213
await expectAllTensorsReleased(async () => {
215214
const res = await fetch('base/weights/uncompressed/face_landmark_68_model.weights')
216215
const weights = new Float32Array(await res.arrayBuffer())
217-
const net = faceapi.faceLandmarkNet(weights)
216+
const net = faceapi.createFaceLandmarkNet(weights)
218217
net.dispose()
219218
})
220219
})

test/tests/e2e/faceRecognitionNet.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ describe('faceRecognitionNet', () => {
141141
await expectAllTensorsReleased(async () => {
142142
const res = await fetch('base/weights/uncompressed/face_recognition_model.weights')
143143
const weights = new Float32Array(await res.arrayBuffer())
144-
const net = faceapi.faceRecognitionNet(weights)
144+
const net = faceapi.createFaceRecognitionNet(weights)
145145
net.dispose()
146146
})
147147
})

test/tests/e2e/mtcnn.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import * as faceapi from '../../../src';
2+
import { MtcnnResult } from '../../../src/mtcnn/types';
3+
import { Point } from '../../../src/Point';
4+
import { describeWithNets, expectAllTensorsReleased, expectPointClose, expectRectClose, expectMaxDelta } from '../../utils';
5+
6+
const expectedBoxes = [
7+
{ x: 70, y: 21, width: 112, height: 112 },
8+
{ x: 36, y: 250, width: 133, height: 132 },
9+
{ x: 221, y: 43, width: 112, height: 111 },
10+
{ x: 247, y: 231, width: 106, height: 107 },
11+
{ x: 566, y: 67, width: 104, height: 104 },
12+
{ x: 451, y: 176, width: 122, height: 122 }
13+
]
14+
15+
const expectedFaceLandmarks = [
16+
[new Point(117, 58), new Point(156, 63), new Point(141, 86), new Point(109, 98), new Point(147, 104)],
17+
[new Point(82, 292), new Point(134, 304), new Point(104, 330), new Point(72, 342), new Point(120, 353)],
18+
[new Point(261, 82), new Point(306, 83), new Point(282, 113), new Point(257, 124), new Point(306, 126)],
19+
[new Point(277, 273), new Point(318, 273), new Point(295, 300), new Point(279, 311), new Point(316, 313)],
20+
[new Point(607, 110), new Point(645, 115), new Point(626, 138), new Point(601, 144), new Point(639, 150)],
21+
[new Point(489, 224), new Point(534, 223), new Point(507, 250), new Point(493, 271), new Point(530, 270)]
22+
]
23+
24+
describe('mtcnn', () => {
25+
26+
let imgEl: HTMLImageElement
27+
28+
beforeAll(async () => {
29+
const img = await (await fetch('base/test/images/faces.jpg')).blob()
30+
imgEl = await faceapi.bufferToImage(img)
31+
})
32+
33+
describeWithNets('uncompressed weights', { withMtcnn: { quantized: false } }, ({ mtcnn }) => {
34+
35+
function expectResults(
36+
results: MtcnnResult[],
37+
boxOrder: number[],
38+
maxBoxDelta: number,
39+
maxLandmarkPointsDelta: number
40+
) {
41+
results.forEach((result, i) => {
42+
const { faceDetection, faceLandmarks } = result
43+
expect(faceDetection instanceof faceapi.FaceDetection).toBe(true)
44+
expect(faceLandmarks instanceof faceapi.FaceLandmarks5).toBe(true)
45+
expectRectClose(faceDetection.getBox(), expectedBoxes[boxOrder[i]], maxBoxDelta)
46+
faceLandmarks.getPositions().forEach((pt, j) => expectPointClose(pt, expectedFaceLandmarks[boxOrder[i]][j], maxLandmarkPointsDelta))
47+
expectMaxDelta(faceDetection.getScore(), 0.99, 0.01)
48+
})
49+
}
50+
51+
it('minFaceSize = 20, finds all faces', async () => {
52+
const forwardParams = {
53+
minFaceSize: 20
54+
}
55+
56+
const results = await mtcnn.forward(imgEl, forwardParams)
57+
expect(results.length).toEqual(6)
58+
expectResults(results, [0, 1, 2, 3, 4, 5], 1, 1)
59+
})
60+
61+
it('minFaceSize = 80, finds all faces', async () => {
62+
const forwardParams = {
63+
minFaceSize: 80
64+
}
65+
66+
const results = await mtcnn.forward(imgEl, forwardParams)
67+
68+
expect(results.length).toEqual(6)
69+
expectResults(results, [0, 5, 3, 1, 2, 4], 12, 12)
70+
})
71+
72+
it('all optional params passed, finds all faces', async () => {
73+
const forwardParams = {
74+
maxNumScales: 10,
75+
scaleFactor: 0.8,
76+
scoreThresholds: [0.8, 0.8, 0.9],
77+
minFaceSize: 20
78+
}
79+
80+
const results = await mtcnn.forward(imgEl, forwardParams)
81+
expect(results.length).toEqual(6)
82+
expectResults(results, [5, 1, 4, 3, 2, 0], 6, 10)
83+
})
84+
85+
it('scale steps passed, finds all faces', async () => {
86+
const forwardParams = {
87+
scaleSteps: [0.6, 0.4, 0.2, 0.15, 0.1, 0.08, 0.02]
88+
}
89+
90+
const results = await mtcnn.forward(imgEl, forwardParams)
91+
expect(results.length).toEqual(6)
92+
expectResults(results, [5, 1, 3, 0, 2, 4], 7, 15)
93+
})
94+
95+
})
96+
97+
describe('no memory leaks', () => {
98+
99+
describe('NeuralNetwork, uncompressed model', () => {
100+
101+
it('disposes all param tensors', async () => {
102+
await expectAllTensorsReleased(async () => {
103+
const res = await fetch('base/weights/uncompressed/mtcnn_model.weights')
104+
const weights = new Float32Array(await res.arrayBuffer())
105+
const net = faceapi.createMtcnn(weights)
106+
net.dispose()
107+
})
108+
})
109+
110+
})
111+
112+
describe('NeuralNetwork, quantized model', () => {
113+
114+
it('disposes all param tensors', async () => {
115+
await expectAllTensorsReleased(async () => {
116+
const net = new faceapi.Mtcnn()
117+
await net.load('base/weights')
118+
net.dispose()
119+
})
120+
})
121+
122+
})
123+
124+
})
125+
126+
})

test/tests/toNetInput.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { tf } from '../../src';
1+
import * as tf from '@tensorflow/tfjs-core';
2+
23
import { NetInput } from '../../src/NetInput';
34
import { toNetInput } from '../../src/toNetInput';
45
import { bufferToImage, createCanvasFromMedia } from '../../src/utils';

0 commit comments

Comments
 (0)