Skip to content

Commit 5061313

Browse files
testcases for face expression recognition
1 parent 622a49a commit 5061313

File tree

10 files changed

+493
-84
lines changed

10 files changed

+493
-84
lines changed

src/faceExpressionNet/FaceExpressionNet.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ export class FaceExpressionNet extends FaceProcessor<FaceFeatureExtractorParams>
4242
public async predictExpressions(input: TNetInput) {
4343
const netInput = await toNetInput(input)
4444
const out = await this.forwardInput(netInput)
45-
const probabilitesByBatch = await Promise.all(tf.unstack(out).map(t => t.data()))
45+
const probabilitesByBatch = await Promise.all(tf.unstack(out).map(async t => {
46+
const data = await t.data()
47+
t.dispose()
48+
return data
49+
}))
4650
out.dispose()
4751

4852
const predictionsByBatch = probabilitesByBatch
@@ -53,11 +57,6 @@ export class FaceExpressionNet extends FaceProcessor<FaceFeatureExtractorParams>
5357
: predictionsByBatch[0]
5458
}
5559

56-
public dispose(throwOnRedispose: boolean = true) {
57-
this.faceFeatureExtractor.dispose(throwOnRedispose)
58-
super.dispose(throwOnRedispose)
59-
}
60-
6160
protected getDefaultModelName(): string {
6261
return 'face_expression_model'
6362
}

src/globalApi/DetectFaceLandmarksTasks.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@ export class DetectAllFaceLandmarksTask<
5252
)
5353
}
5454

55-
withFaceExpressions(): PredictAllFaceExpressionsTask<WithFaceLandmarks<TSource>> {
56-
return new PredictAllFaceExpressionsTask<WithFaceLandmarks<TSource>>(this, this.input)
57-
}
58-
5955
withFaceDescriptors(): ComputeAllFaceDescriptorsTask<WithFaceLandmarks<TSource>> {
6056
return new ComputeAllFaceDescriptorsTask<WithFaceLandmarks<TSource>>(this, this.input)
6157
}
@@ -84,10 +80,6 @@ export class DetectSingleFaceLandmarksTask<
8480
return extendWithFaceLandmarks<TSource>(parentResult, landmarks)
8581
}
8682

87-
withFaceExpression(): PredictSingleFaceExpressionTask<WithFaceLandmarks<TSource>> {
88-
return new PredictSingleFaceExpressionTask<WithFaceLandmarks<TSource>>(this, this.input)
89-
}
90-
9183
withFaceDescriptor(): ComputeSingleFaceDescriptorTask<WithFaceLandmarks<TSource>> {
9284
return new ComputeSingleFaceDescriptorTask<WithFaceLandmarks<TSource>>(this, this.input)
9385
}

src/globalApi/DetectFacesTasks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export class DetectSingleFaceTask extends DetectFacesTaskBase<FaceDetection | un
102102
)
103103
}
104104

105-
withFaceExpression(): PredictSingleFaceExpressionTask<WithFaceDetection<{}>> {
105+
withFaceExpressions(): PredictSingleFaceExpressionTask<WithFaceDetection<{}>> {
106106
return new PredictSingleFaceExpressionTask<WithFaceDetection<{}>>(
107107
this.runAndExtendWithFaceDetection(),
108108
this.input

test/images/angry.jpg

7.5 KB
Loading

test/images/angry_cropped.jpg

13 KB
Loading

test/images/surprised.jpg

37.6 KB
Loading

test/images/surprised_cropped.jpg

28.9 KB
Loading
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import * as tf from '@tensorflow/tfjs-core';
2+
3+
import { createCanvasFromMedia, NetInput, toNetInput } from '../../../src';
4+
import { FaceExpressionPrediction } from '../../../src/faceExpressionNet/types';
5+
import { loadImage } from '../../env';
6+
import { describeWithNets, expectAllTensorsReleased } from '../../utils';
7+
8+
describe('faceExpressionNet', () => {
9+
10+
let imgElAngry: HTMLImageElement
11+
let imgElSurprised: HTMLImageElement
12+
13+
beforeAll(async () => {
14+
imgElAngry = await loadImage('test/images/angry_cropped.jpg')
15+
imgElSurprised = await loadImage('test/images/surprised_cropped.jpg')
16+
})
17+
18+
describeWithNets('quantized weights', { withFaceExpressionNet: { quantized: true } }, ({ faceExpressionNet }) => {
19+
20+
it('recognizes facial expressions', async () => {
21+
const result = await faceExpressionNet.predictExpressions(imgElAngry) as FaceExpressionPrediction[]
22+
23+
expect(Array.isArray(result)).toBe(true)
24+
expect(result.length).toEqual(7)
25+
26+
const angry = result.find(res => res.expression === 'angry') as FaceExpressionPrediction
27+
28+
expect(angry).not.toBeUndefined()
29+
expect(angry.probability).toBeGreaterThan(0.95)
30+
})
31+
32+
})
33+
34+
describeWithNets('batch inputs', { withFaceExpressionNet: { quantized: true } }, ({ faceExpressionNet }) => {
35+
36+
it('recognizes facial expressions for batch of image elements', async () => {
37+
const inputs = [imgElAngry, imgElSurprised]
38+
39+
const results = await faceExpressionNet.predictExpressions(inputs) as FaceExpressionPrediction[][]
40+
expect(Array.isArray(results)).toBe(true)
41+
expect(results.length).toEqual(2)
42+
43+
const [resultAngry, resultSurprised] = results
44+
45+
expect(Array.isArray(resultAngry)).toBe(true)
46+
expect(resultAngry.length).toEqual(7)
47+
expect(Array.isArray(resultSurprised)).toBe(true)
48+
expect(resultSurprised.length).toEqual(7)
49+
50+
const angry = resultAngry.find(res => res.expression === 'angry') as FaceExpressionPrediction
51+
const surprised = resultSurprised.find(res => res.expression === 'surprised') as FaceExpressionPrediction
52+
53+
expect(angry).not.toBeUndefined()
54+
expect(angry.probability).toBeGreaterThan(0.95)
55+
expect(surprised).not.toBeUndefined()
56+
expect(surprised.probability).toBeGreaterThan(0.95)
57+
})
58+
59+
it('computes face landmarks for batch of tf.Tensor3D', async () => {
60+
const inputs = [imgElAngry, imgElSurprised].map(el => tf.fromPixels(createCanvasFromMedia(el)))
61+
62+
const results = await faceExpressionNet.predictExpressions(inputs) as FaceExpressionPrediction[][]
63+
expect(Array.isArray(results)).toBe(true)
64+
expect(results.length).toEqual(2)
65+
66+
const [resultAngry, resultSurprised] = results
67+
68+
expect(Array.isArray(resultAngry)).toBe(true)
69+
expect(resultAngry.length).toEqual(7)
70+
expect(Array.isArray(resultSurprised)).toBe(true)
71+
expect(resultSurprised.length).toEqual(7)
72+
73+
const angry = resultAngry.find(res => res.expression === 'angry') as FaceExpressionPrediction
74+
const surprised = resultSurprised.find(res => res.expression === 'surprised') as FaceExpressionPrediction
75+
76+
expect(angry).not.toBeUndefined()
77+
expect(angry.probability).toBeGreaterThan(0.95)
78+
expect(surprised).not.toBeUndefined()
79+
expect(surprised.probability).toBeGreaterThan(0.95)
80+
})
81+
82+
it('computes face landmarks for batch of mixed inputs', async () => {
83+
const inputs = [imgElAngry, tf.fromPixels(createCanvasFromMedia(imgElSurprised))]
84+
85+
const results = await faceExpressionNet.predictExpressions(inputs) as FaceExpressionPrediction[][]
86+
expect(Array.isArray(results)).toBe(true)
87+
expect(results.length).toEqual(2)
88+
89+
const [resultAngry, resultSurprised] = results
90+
91+
expect(Array.isArray(resultAngry)).toBe(true)
92+
expect(resultAngry.length).toEqual(7)
93+
expect(Array.isArray(resultSurprised)).toBe(true)
94+
expect(resultSurprised.length).toEqual(7)
95+
96+
const angry = resultAngry.find(res => res.expression === 'angry') as FaceExpressionPrediction
97+
const surprised = resultSurprised.find(res => res.expression === 'surprised') as FaceExpressionPrediction
98+
99+
expect(angry).not.toBeUndefined()
100+
expect(angry.probability).toBeGreaterThan(0.95)
101+
expect(surprised).not.toBeUndefined()
102+
expect(surprised.probability).toBeGreaterThan(0.95)
103+
})
104+
105+
})
106+
107+
describeWithNets('no memory leaks', { withFaceExpressionNet: { quantized: true } }, ({ faceExpressionNet }) => {
108+
109+
describe('forwardInput', () => {
110+
111+
it('single image element', async () => {
112+
await expectAllTensorsReleased(async () => {
113+
const netInput = new NetInput([imgElAngry])
114+
const outTensor = await faceExpressionNet.forwardInput(netInput)
115+
outTensor.dispose()
116+
})
117+
})
118+
119+
it('multiple image elements', async () => {
120+
await expectAllTensorsReleased(async () => {
121+
const netInput = new NetInput([imgElAngry, imgElAngry])
122+
const outTensor = await faceExpressionNet.forwardInput(netInput)
123+
outTensor.dispose()
124+
})
125+
})
126+
127+
it('single tf.Tensor3D', async () => {
128+
const tensor = tf.fromPixels(createCanvasFromMedia(imgElAngry))
129+
130+
await expectAllTensorsReleased(async () => {
131+
const outTensor = await faceExpressionNet.forwardInput(await toNetInput(tensor))
132+
outTensor.dispose()
133+
})
134+
135+
tensor.dispose()
136+
})
137+
138+
it('multiple tf.Tensor3Ds', async () => {
139+
const tensors = [imgElAngry, imgElAngry, imgElAngry].map(el => tf.fromPixels(createCanvasFromMedia(el)))
140+
141+
await expectAllTensorsReleased(async () => {
142+
const outTensor = await faceExpressionNet.forwardInput(await toNetInput(tensors))
143+
outTensor.dispose()
144+
})
145+
146+
tensors.forEach(t => t.dispose())
147+
})
148+
149+
it('single batch size 1 tf.Tensor4Ds', async () => {
150+
const tensor = tf.tidy(() => tf.fromPixels(createCanvasFromMedia(imgElAngry)).expandDims()) as tf.Tensor4D
151+
152+
await expectAllTensorsReleased(async () => {
153+
const outTensor = await faceExpressionNet.forwardInput(await toNetInput(tensor))
154+
outTensor.dispose()
155+
})
156+
157+
tensor.dispose()
158+
})
159+
160+
it('multiple batch size 1 tf.Tensor4Ds', async () => {
161+
const tensors = [imgElAngry, imgElAngry, imgElAngry]
162+
.map(el => tf.tidy(() => tf.fromPixels(createCanvasFromMedia(el)).expandDims())) as tf.Tensor4D[]
163+
164+
await expectAllTensorsReleased(async () => {
165+
const outTensor = await faceExpressionNet.forwardInput(await toNetInput(tensors))
166+
outTensor.dispose()
167+
})
168+
169+
tensors.forEach(t => t.dispose())
170+
})
171+
172+
})
173+
174+
describe('predictExpressions', () => {
175+
176+
it('single image element', async () => {
177+
await expectAllTensorsReleased(async () => {
178+
await faceExpressionNet.predictExpressions(imgElAngry)
179+
})
180+
})
181+
182+
it('multiple image elements', async () => {
183+
await expectAllTensorsReleased(async () => {
184+
await faceExpressionNet.predictExpressions([imgElAngry, imgElAngry, imgElAngry])
185+
})
186+
})
187+
188+
it('single tf.Tensor3D', async () => {
189+
const tensor = tf.fromPixels(createCanvasFromMedia(imgElAngry))
190+
191+
await expectAllTensorsReleased(async () => {
192+
await faceExpressionNet.predictExpressions(tensor)
193+
})
194+
195+
tensor.dispose()
196+
})
197+
198+
it('multiple tf.Tensor3Ds', async () => {
199+
const tensors = [imgElAngry, imgElAngry, imgElAngry].map(el => tf.fromPixels(createCanvasFromMedia(el)))
200+
201+
202+
await expectAllTensorsReleased(async () => {
203+
await faceExpressionNet.predictExpressions(tensors)
204+
})
205+
206+
tensors.forEach(t => t.dispose())
207+
})
208+
209+
it('single batch size 1 tf.Tensor4Ds', async () => {
210+
const tensor = tf.tidy(() => tf.fromPixels(createCanvasFromMedia(imgElAngry)).expandDims()) as tf.Tensor4D
211+
212+
await expectAllTensorsReleased(async () => {
213+
await faceExpressionNet.predictExpressions(tensor)
214+
})
215+
216+
tensor.dispose()
217+
})
218+
219+
it('multiple batch size 1 tf.Tensor4Ds', async () => {
220+
const tensors = [imgElAngry, imgElAngry, imgElAngry]
221+
.map(el => tf.tidy(() => tf.fromPixels(createCanvasFromMedia(el)).expandDims())) as tf.Tensor4D[]
222+
223+
await expectAllTensorsReleased(async () => {
224+
await faceExpressionNet.predictExpressions(tensors)
225+
})
226+
227+
tensors.forEach(t => t.dispose())
228+
})
229+
230+
})
231+
})
232+
233+
})
234+

0 commit comments

Comments
 (0)