Skip to content

Commit 68ff853

Browse files
Merge pull request #179 from justadudewhohacks/expressions
face expression recognition
2 parents 6094f36 + a72ec31 commit 68ff853

File tree

114 files changed

+2504
-2658
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+2504
-2658
lines changed

README.md

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Table of Contents:
1616
* **[Face Detection Models](#models-face-detection)**
1717
* **[68 Point Face Landmark Detection Models](#models-face-landmark-detection)**
1818
* **[Face Recognition Model](#models-face-recognition)**
19+
* **[Face Expression Recognition Model](#models-face-expression-recognition)**
1920
* **[Getting Started](#getting-started)**
2021
* **[face-api.js for the Browser](#getting-started-browser)**
2122
* **[face-api.js for Nodejs](#getting-started-nodejs)**
@@ -61,6 +62,10 @@ Check out my face-api.js tutorials:
6162

6263
![preview_face-similarity](https://user-images.githubusercontent.com/31125521/40316573-0a1190c0-5d1f-11e8-8797-f6deaa344523.gif)
6364

65+
## Face Expression Recognition
66+
67+
![preview_face-expression-recognition](https://user-images.githubusercontent.com/31125521/50575270-f501d080-0dfb-11e9-9676-8f419efdade4.png)
68+
6469
## Face Landmark Detection
6570

6671
![face_landmarks_boxes_2](https://user-images.githubusercontent.com/31125521/46063404-00928b00-c16d-11e8-8f29-e9c50afd2bc8.jpg)
@@ -161,6 +166,12 @@ The neural net is equivalent to the **FaceRecognizerNet** used in [face-recognit
161166

162167
The size of the quantized model is roughly 6.2 MB (**face_recognition_model**).
163168

169+
<a name="models-face-expression-recognition"></a>
170+
171+
## Face Expression Recognition Model
172+
173+
The face expression recognition model is lightweight, fast and provides reasonable accuracy. The model has a size of roughly 310kb and it employs depthwise separable convolutions and densely connected blocks. It has been trained on a variety of images from publicly available datasets as well as images scraped from the web. Note, that wearing glasses might decrease the accuracy of the prediction results.
174+
164175
<a name="getting-started"></a>
165176

166177
# Getting Started
@@ -228,6 +239,7 @@ await faceapi.loadSsdMobilenetv1Model('/models')
228239
// await faceapi.loadFaceLandmarkModel('/models')
229240
// await faceapi.loadFaceLandmarkTinyModel('/models')
230241
// await faceapi.loadFaceRecognitionModel('/models')
242+
// await faceapi.loadFaceExpressionModel('/models')
231243
```
232244

233245
All global neural network instances are exported via faceapi.nets:
@@ -319,13 +331,13 @@ You can tune the options of each face detector as shown [here](#usage-face-detec
319331

320332
**After face detection, we can furthermore predict the facial landmarks for each detected face as follows:**
321333

322-
Detect all faces in an image + computes 68 Point Face Landmarks for each detected face. Returns **Array<[FaceDetectionWithLandmarks](#interface-face-detection-with-landmarks)>**:
334+
Detect all faces in an image + computes 68 Point Face Landmarks for each detected face. Returns **Array<[WithFaceLandmarks<WithFaceDetection<{}>>](#usage-utility-classes)>**:
323335

324336
``` javascript
325337
const detectionsWithLandmarks = await faceapi.detectAllFaces(input).withFaceLandmarks()
326338
```
327339

328-
Detect the face with the highest confidence score in an image + computes 68 Point Face Landmarks for that face. Returns **[FaceDetectionWithLandmarks](#interface-face-detection-with-landmarks) | undefined**:
340+
Detect the face with the highest confidence score in an image + computes 68 Point Face Landmarks for that face. Returns **[WithFaceLandmarks<WithFaceDetection<{}>>](#usage-utility-classes) | undefined**:
329341

330342
``` javascript
331343
const detectionWithLandmarks = await faceapi.detectSingleFace(input).withFaceLandmarks()
@@ -342,16 +354,52 @@ const detectionsWithLandmarks = await faceapi.detectAllFaces(input).withFaceLand
342354

343355
**After face detection and facial landmark prediction the face descriptors for each face can be computed as follows:**
344356

345-
Detect all faces in an image + computes 68 Point Face Landmarks for each detected face. Returns **Array<[FullFaceDescription](#interface-full-face-description)>**:
357+
Detect all faces in an image + computes 68 Point Face Landmarks for each detected face. Returns **Array<[WithFaceDescriptor<WithFaceLandmarks<WithFaceDetection<{}>>>](#usage-utility-classes)>**:
358+
359+
``` javascript
360+
const results = await faceapi.detectAllFaces(input).withFaceLandmarks().withFaceDescriptors()
361+
```
362+
363+
Detect the face with the highest confidence score in an image + computes 68 Point Face Landmarks and face descriptor for that face. Returns **[WithFaceDescriptor<WithFaceLandmarks<WithFaceDetection<{}>>>](#usage-utility-classes) | undefined**:
364+
365+
``` javascript
366+
const result = await faceapi.detectSingleFace(input).withFaceLandmarks().withFaceDescriptor()
367+
```
368+
369+
### Recognizing Face Expressions
370+
371+
**Face expressions recognition can be performed for detected faces as follows:**
372+
373+
Detect all faces in an image + recognize face expressions. Returns **Array<[WithFaceExpressions<WithFaceDetection<{}>>](#usage-utility-classes)>**:
374+
375+
``` javascript
376+
const detectionsWithExpressions = await faceapi.detectAllFaces(input).withFaceExpressions()
377+
```
378+
379+
Detect the face with the highest confidence score in an image + recognize the face expression for that face. Returns **[WithFaceExpressions<WithFaceDetection<{}>>](#usage-utility-classes) | undefined**:
346380

347381
``` javascript
348-
const fullFaceDescriptions = await faceapi.detectAllFaces(input).withFaceLandmarks().withFaceDescriptors()
382+
const detectionWithExpressions = await faceapi.detectSingleFace(input).withFaceExpressions()
349383
```
350384

351-
Detect the face with the highest confidence score in an image + computes 68 Point Face Landmarks and face descriptor for that face. Returns **[FullFaceDescription](#interface-full-face-description) | undefined**:
385+
### Composition of Tasks
386+
387+
**Tasks can be composed as follows:**
352388

353389
``` javascript
354-
const fullFaceDescription = await faceapi.detectSingleFace(input).withFaceLandmarks().withFaceDescriptor()
390+
// all faces
391+
await faceapi.detectAllFaces(input)
392+
await faceapi.detectAllFaces(input).withFaceExpressions()
393+
await faceapi.detectAllFaces(input).withFaceLandmarks()
394+
await faceapi.detectAllFaces(input).withFaceExpressions().withFaceLandmarks()
395+
await faceapi.detectAllFaces(input).withFaceExpressions().withFaceLandmarks().withFaceDescriptors()
396+
397+
// single face
398+
await faceapi.detectSingleFace(input)
399+
await faceapi.detectSingleFace(input).withFaceExpressions()
400+
await faceapi.detectSingleFace(input).withFaceLandmarks()
401+
await faceapi.detectSingleFace(input).withFaceExpressions().withFaceLandmarks()
402+
await faceapi.detectSingleFace(input).withFaceExpressions().withFaceLandmarks().withFaceDescriptor()
355403
```
356404

357405
### Face Recognition by Matching Descriptors
@@ -361,43 +409,43 @@ To perform face recognition, one can use faceapi.FaceMatcher to compare referenc
361409
First, we initialize the FaceMatcher with the reference data, for example we can simply detect faces in a **referenceImage** and match the descriptors of the detected faces to faces of subsquent images:
362410

363411
``` javascript
364-
const fullFaceDescriptions = await faceapi
412+
const results = await faceapi
365413
.detectAllFaces(referenceImage)
366414
.withFaceLandmarks()
367415
.withFaceDescriptors()
368416

369-
if (!fullFaceDescriptions.length) {
417+
if (!results.length) {
370418
return
371419
}
372420

373421
// create FaceMatcher with automatically assigned labels
374422
// from the detection results for the reference image
375-
const faceMatcher = new faceapi.FaceMatcher(fullFaceDescriptions)
423+
const faceMatcher = new faceapi.FaceMatcher(results)
376424
```
377425

378426
Now we can recognize a persons face shown in **queryImage1**:
379427

380428
``` javascript
381-
const singleFullFaceDescription = await faceapi
429+
const singleResult = await faceapi
382430
.detectSingleFace(queryImage1)
383431
.withFaceLandmarks()
384432
.withFaceDescriptor()
385433

386-
if (singleFullFaceDescription) {
387-
const bestMatch = faceMatcher.findBestMatch(singleFullFaceDescription.descriptor)
434+
if (singleResult) {
435+
const bestMatch = faceMatcher.findBestMatch(singleResult.descriptor)
388436
console.log(bestMatch.toString())
389437
}
390438
```
391439

392440
Or we can recognize all faces shown in **queryImage2**:
393441

394442
``` javascript
395-
const fullFaceDescriptions = await faceapi
443+
const results = await faceapi
396444
.detectAllFaces(queryImage2)
397445
.withFaceLandmarks()
398446
.withFaceDescriptors()
399447

400-
fullFaceDescriptions.forEach(fd => {
448+
results.forEach(fd => {
401449
const bestMatch = faceMatcher.findBestMatch(fd.descriptor)
402450
console.log(bestMatch.toString())
403451
})
@@ -430,7 +478,7 @@ Drawing the detected faces into a canvas:
430478
const detections = await faceapi.detectAllFaces(input)
431479

432480
// resize the detected boxes in case your displayed image has a different size then the original
433-
const detectionsForSize = detections.map(det => det.forSize(input.width, input.height))
481+
const detectionsForSize = faceapi.resizeResults(detections, { width: input.width, height: input.height })
434482
// draw them into a canvas
435483
const canvas = document.getElementById('overlay')
436484
canvas.width = input.width
@@ -446,7 +494,7 @@ const detectionsWithLandmarks = await faceapi
446494
.withFaceLandmarks()
447495

448496
// resize the detected boxes and landmarks in case your displayed image has a different size then the original
449-
const detectionsWithLandmarksForSize = detectionsWithLandmarks.map(det => det.forSize(input.width, input.height))
497+
const detectionsWithLandmarksForSize = faceapi.resizeResults(detectionsWithLandmarks, { width: input.width, height: input.height })
450498
// draw them into a canvas
451499
const canvas = document.getElementById('overlay')
452500
canvas.width = input.width
@@ -579,27 +627,55 @@ export interface IFaceLandmarks {
579627
}
580628
```
581629

582-
<a name="interface-face-detection-with-landmarks"></a>
630+
<a name="with-face-detection"></a>
583631

584-
### IFaceDetectionWithLandmarks
632+
### WithFaceDetection
585633

586634
``` javascript
587-
export interface IFaceDetectionWithLandmarks {
635+
export type WithFaceDetection<TSource> TSource & {
588636
detection: FaceDetection
637+
}
638+
```
639+
640+
<a name="with-face-landmarks"></a>
641+
642+
### WithFaceLandmarks
643+
644+
``` javascript
645+
export type WithFaceLandmarks<TSource> TSource & {
646+
unshiftedLandmarks: FaceLandmarks
589647
landmarks: FaceLandmarks
648+
alignedRect: FaceDetection
590649
}
591650
```
592651

593-
<a name="interface-full-face-description"></a>
652+
<a name="with-face-descriptor"></a>
594653

595-
### IFullFaceDescription
654+
### WithFaceDescriptor
596655

597656
``` javascript
598-
export interface IFullFaceDescription extends IFaceDetectionWithLandmarks {
657+
export type WithFaceDescriptor<TSource> TSource & {
599658
descriptor: Float32Array
600659
}
601660
```
602661

662+
<a name="with-face-expressions"></a>
663+
664+
### WithFaceExpressions
665+
666+
``` javascript
667+
export type FaceExpression = 'neutral' | 'happy' | 'sad' | 'angry' | 'fearful' | 'disgusted' | 'surprised'
668+
669+
export type FaceExpressionPrediction = {
670+
expression: FaceExpression,
671+
probability: number
672+
}
673+
674+
export type WithFaceExpressions<TSource> TSource & {
675+
expressions: FaceExpressionPrediction[]
676+
}
677+
```
678+
603679
<a name="other-useful-utility"></a>
604680

605681
## Other Useful Utility

examples/examples-browser/public/js/commons.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,16 @@ async function requestExternalImage(imageUrl) {
2525
function renderNavBar(navbarId, exampleUri) {
2626
const examples = [
2727
{
28-
uri: 'face_and_landmark_detection',
29-
name: 'Face And Landmark Detection'
28+
uri: 'face_detection',
29+
name: 'Face Detection'
30+
},
31+
{
32+
uri: 'face_landmark_detection',
33+
name: 'Face Landmark Detection'
34+
},
35+
{
36+
uri: 'face_expression_recognition',
37+
name: 'Face Expression Recognition'
3038
},
3139
{
3240
uri: 'face_recognition',
@@ -41,8 +49,16 @@ function renderNavBar(navbarId, exampleUri) {
4149
name: 'Video Face Tracking'
4250
},
4351
{
44-
uri: 'webcam_face_tracking',
45-
name: 'Webcam Face Tracking'
52+
uri: 'webcam_face_detection',
53+
name: 'Webcam Face Detection'
54+
},
55+
{
56+
uri: 'webcam_face_landmark_detection',
57+
name: 'Webcam Face Landmark Detection'
58+
},
59+
{
60+
uri: 'webcam_face_expression_recognition',
61+
name: 'Webcam Face Expression Recognition'
4662
},
4763
{
4864
uri: 'bbt_face_landmark_detection',
@@ -112,7 +128,7 @@ function renderNavBar(navbarId, exampleUri) {
112128
li.style.background='#b0b0b0'
113129
}
114130
const a = document.createElement('a')
115-
a.classList.add('waves-effect', 'waves-light')
131+
a.classList.add('waves-effect', 'waves-light', 'pad-sides-sm')
116132
a.href = ex.uri
117133
const span = document.createElement('span')
118134
span.innerHTML = ex.name
@@ -123,7 +139,7 @@ function renderNavBar(navbarId, exampleUri) {
123139
})
124140

125141
$('.button-collapse').sideNav({
126-
menuWidth: 240
142+
menuWidth: 260
127143
})
128144
}
129145

examples/examples-browser/public/js/drawing.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function resizeCanvasAndResults(dimensions, canvas, results) {
77

88
// resize detections (and landmarks) in case displayed image is smaller than
99
// original size
10-
return results.map(res => res.forSize(width, height))
10+
return faceapi.resizeResults(results, { width, height })
1111
}
1212

1313
function drawDetections(dimensions, canvas, detections) {
@@ -29,4 +29,14 @@ function drawLandmarks(dimensions, canvas, results, withBoxes = true) {
2929
color: 'green'
3030
}
3131
faceapi.drawLandmarks(canvas, faceLandmarks, drawLandmarksOptions)
32+
}
33+
34+
function drawExpressions(dimensions, canvas, results, thresh, withBoxes = true) {
35+
const resizedResults = resizeCanvasAndResults(dimensions, canvas, results)
36+
37+
if (withBoxes) {
38+
faceapi.drawDetection(canvas, resizedResults.map(det => det.detection), { withScore: false })
39+
}
40+
41+
faceapi.drawFaceExpressions(canvas, resizedResults.map(({ detection, expressions }) => ({ position: detection.box, expressions })))
3242
}

examples/examples-browser/public/js/imageSelectionControls.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,21 @@ async function loadImageFromUrl(url) {
1010
updateResults()
1111
}
1212

13-
function renderImageSelectList(selectListId, onChange, initialValue) {
14-
const images = [1, 2, 3, 4, 5].map(idx => `bbt${idx}.jpg`)
13+
function renderImageSelectList(selectListId, onChange, initialValue, withFaceExpressionImages) {
14+
let images = [1, 2, 3, 4, 5].map(idx => `bbt${idx}.jpg`)
15+
16+
if (withFaceExpressionImages) {
17+
images = [
18+
'happy.jpg',
19+
'sad.jpg',
20+
'angry.jpg',
21+
'disgusted.jpg',
22+
'surprised.jpg',
23+
'fearful.jpg',
24+
'neutral.jpg'
25+
].concat(images)
26+
}
27+
1528
function renderChildren(select) {
1629
images.forEach(imageName =>
1730
renderOption(
@@ -30,13 +43,14 @@ function renderImageSelectList(selectListId, onChange, initialValue) {
3043
)
3144
}
3245

33-
function initImageSelectionControls() {
46+
function initImageSelectionControls(initialValue = 'bbt1.jpg', withFaceExpressionImages = false) {
3447
renderImageSelectList(
3548
'#selectList',
3649
async (uri) => {
3750
await onSelectedImageChanged(uri)
3851
},
39-
'bbt1.jpg'
52+
initialValue,
53+
withFaceExpressionImages
4054
)
4155
onSelectedImageChanged($('#selectList select').val())
4256
}

examples/examples-browser/public/styles.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
right: 0;
44
margin: auto;
55
margin-top: 20px;
6-
padding-left: 260px;
6+
padding-left: 280px;
77
display: inline-flex !important;
88
}
99

@@ -53,6 +53,10 @@
5353
padding: 0 10px !important;
5454
}
5555

56+
.pad-sides-sm {
57+
padding: 0 8px !important;
58+
}
59+
5660
#github-link {
5761
display: flex !important;
5862
justify-content: center;

0 commit comments

Comments
 (0)