Skip to content

Commit 3f1826a

Browse files
added examples for mtcnn face detection + added getters to FaceDetection
1 parent 2b794bc commit 3f1826a

File tree

6 files changed

+195
-44
lines changed

6 files changed

+195
-44
lines changed

examples/public/commons.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ function renderNavBar(navbarId, exampleUri) {
122122
uri: 'mtcnn_face_detection_video',
123123
name: 'MTCNN Face Detection Video'
124124
},
125+
{
126+
uri: 'mtcnn_face_detection_webcam',
127+
name: 'MTCNN Face Detection Webcam'
128+
},
125129
{
126130
uri: 'batch_face_landmarks',
127131
name: 'Batch Face Landmarks'
@@ -159,7 +163,7 @@ function renderNavBar(navbarId, exampleUri) {
159163
githubLink.id = 'github-link'
160164
githubLink.href = 'https://github.com/justadudewhohacks/face-api.js'
161165
const h5 = document.createElement('h5')
162-
h5.innerHTML = 'face-api.js'//'If you like this project, feel free to leave a star on github :)'
166+
h5.innerHTML = 'face-api.js'
163167
githubLink.appendChild(h5)
164168
const githubLinkIcon = document.createElement('img')
165169
githubLinkIcon.src = 'github_link_icon.png'
@@ -176,7 +180,10 @@ function renderNavBar(navbarId, exampleUri) {
176180
const a = document.createElement('a')
177181
a.classList.add('waves-effect', 'waves-light')
178182
a.href = ex.uri
179-
a.innerHTML = ex.name
183+
const span = document.createElement('span')
184+
span.innerHTML = ex.name
185+
span.style.whiteSpace = 'nowrap'
186+
a.appendChild(span)
180187
li.appendChild(a)
181188
menuContent.appendChild(li)
182189
})

examples/server.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ app.get('/face_alignment', (req, res) => res.sendFile(path.join(viewsDir, 'faceA
2626
app.get('/detect_and_recognize_faces', (req, res) => res.sendFile(path.join(viewsDir, 'detectAndRecognizeFaces.html')))
2727
app.get('/mtcnn_face_detection', (req, res) => res.sendFile(path.join(viewsDir, 'mtcnnFaceDetection.html')))
2828
app.get('/mtcnn_face_detection_video', (req, res) => res.sendFile(path.join(viewsDir, 'mtcnnFaceDetectionVideo.html')))
29+
app.get('/mtcnn_face_detection_webcam', (req, res) => res.sendFile(path.join(viewsDir, 'mtcnnFaceDetectionWebcam.html')))
2930
app.get('/batch_face_landmarks', (req, res) => res.sendFile(path.join(viewsDir, 'batchFaceLandmarks.html')))
3031
app.get('/batch_face_recognition', (req, res) => res.sendFile(path.join(viewsDir, 'batchFaceRecognition.html')))
3132

examples/views/mtcnnFaceDetection.html

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@
5252
</div>
5353

5454
<script>
55-
let minFaceSize = 50 //20 //
56-
let scaleFactor = 0.709 // 0.2
57-
let maxNumScales = 10 // 0.2
58-
let stage1Threshold = 0.6
55+
let minFaceSize = 50
56+
let scaleFactor = 0.709
57+
let maxNumScales = 10
58+
let stage1Threshold = 0.7
5959
let stage2Threshold = 0.7
6060
let stage3Threshold = 0.7
6161
let mtcnn
@@ -85,13 +85,15 @@
8585
canvas.width = width
8686
canvas.height = height
8787

88-
const scoreThresholds = [stage1Threshold, stage2Threshold, stage3Threshold]
89-
const { results, stats } = await mtcnn.forwardWithStats(inputImgEl, minFaceSize, scaleFactor, maxNumScales, scoreThresholds)
90-
91-
console.log(stats)
88+
const mtcnnParams = {
89+
minFaceSize,
90+
scaleFactor,
91+
maxNumScales,
92+
scoreThresholds: [stage1Threshold, stage2Threshold, stage3Threshold]
93+
}
94+
const results = await faceapi.mtcnn(inputImgEl, mtcnnParams)
9295

9396
if (results) {
94-
const { faceDetection, faceLandmarks } = results
9597
results.forEach(({ faceDetection, faceLandmarks }) => {
9698
faceapi.drawDetection('overlay', faceDetection.forSize(width, height))
9799
faceapi.drawLandmarks('overlay', faceLandmarks.forSize(width, height), { lineWidth: 4, color: 'red' })
@@ -106,8 +108,7 @@
106108
}
107109

108110
async function run() {
109-
const weightsBuffer = await (await fetch('/uncompressed/mtcnn_model.weights')).arrayBuffer()
110-
mtcnn = faceapi.mtcnn(new Float32Array(weightsBuffer))
111+
await faceapi.loadMtcnnModel('/')
111112
$('#loader').hide()
112113
onSelectionChanged($('#selectList select').val())
113114
}

examples/views/mtcnnFaceDetectionVideo.html

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,18 @@
2020
</div>
2121
<div class="row side-by-side">
2222
<div class="row">
23-
<label for="minConfidence">Min Confidence:</label>
24-
<input disabled value="0.7" id="minConfidence" type="text" class="bold">
23+
<label for="minFaceSize">Minimum Face Size:</label>
24+
<input disabled value="80" id="minFaceSize" type="text" class="bold">
2525
</div>
2626
<button
2727
class="waves-effect waves-light btn"
28-
onclick="onDecreaseThreshold()"
28+
onclick="onDecreaseMinFaceSize()"
2929
>
3030
<i class="material-icons left">-</i>
3131
</button>
3232
<button
3333
class="waves-effect waves-light btn"
34-
onclick="onIncreaseThreshold()"
34+
onclick="onIncreaseMinFaceSize()"
3535
>
3636
<i class="material-icons left">+</i>
3737
</button>
@@ -49,29 +49,26 @@
4949
</div>
5050

5151
<script>
52+
let modelLoaded = false
5253
let minFaceSize = 80
53-
let scaleFactor = 0.709
54-
let maxNumScales = 10
5554
let minConfidence = 0.9
56-
let stage1Threshold = 0.7
57-
let stage2Threshold = 0.7
58-
let stage3Threshold = 0.7
59-
let modelLoaded = false
60-
let result
55+
let forwardTimes = []
6156

62-
function onIncreaseThreshold() {
63-
minConfidence = Math.min(faceapi.round(minConfidence + 0.1), 1.0)
64-
$('#minConfidence').val(minConfidence)
57+
function onIncreaseMinFaceSize() {
58+
minFaceSize = Math.min(faceapi.round(minFaceSize + 20), 200)
59+
$('#minFaceSize').val(minFaceSize)
6560
}
6661

67-
function onDecreaseThreshold() {
68-
minConfidence = Math.max(faceapi.round(minConfidence - 0.1), 0.1)
69-
$('#minConfidence').val(minConfidence)
62+
function onDecreaseMinFaceSize() {
63+
minFaceSize = Math.max(faceapi.round(minFaceSize - 20), 20)
64+
$('#minFaceSize').val(minFaceSize)
7065
}
7166

72-
function displayTimeStats(timeInMs) {
73-
$('#time').val(`${timeInMs} ms`)
74-
$('#fps').val(`${faceapi.round(1000 / timeInMs)}`)
67+
function updateTimeStats(timeInMs) {
68+
forwardTimes = [timeInMs].concat(forwardTimes).slice(0, 30)
69+
const avgTimeInMs = forwardTimes.reduce((total, t) => total + t) / forwardTimes.length
70+
$('#time').val(`${Math.round(avgTimeInMs)} ms`)
71+
$('#fps').val(`${faceapi.round(1000 / avgTimeInMs)}`)
7572
}
7673

7774
async function onPlay(videoEl) {
@@ -83,13 +80,15 @@
8380
canvas.width = width
8481
canvas.height = height
8582

86-
const scoreThresholds = [stage1Threshold, stage2Threshold, stage3Threshold]
83+
const mtcnnParams = {
84+
minFaceSize
85+
}
86+
8787
const ts = Date.now()
88-
const { results, stats } = await mtcnn.forwardWithStats(videoEl, minFaceSize, scaleFactor, maxNumScales, scoreThresholds)
89-
displayTimeStats(Date.now() - ts)
88+
const results = await faceapi.mtcnn(videoEl, mtcnnParams)
89+
updateTimeStats(Date.now() - ts)
9090

9191
if (results) {
92-
const { faceDetection, faceLandmarks } = results
9392
results.forEach(({ faceDetection, faceLandmarks }) => {
9493
if (faceDetection.score < minConfidence) {
9594
return
@@ -102,8 +101,7 @@
102101
}
103102

104103
async function run() {
105-
const weightsBuffer = await (await fetch('/uncompressed/mtcnn_model.weights')).arrayBuffer()
106-
mtcnn = faceapi.mtcnn(new Float32Array(weightsBuffer))
104+
await faceapi.loadMtcnnModel('/')
107105
modelLoaded = true
108106
onPlay($('#inputVideo').get(0))
109107
$('#loader').hide()
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="face-api.js"></script>
5+
<script src="commons.js"></script>
6+
<link rel="stylesheet" href="styles.css">
7+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
8+
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
9+
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
10+
</head>
11+
<body>
12+
<div id="navbar"></div>
13+
<div class="center-content page-container">
14+
<div class="progress" id="loader">
15+
<div class="indeterminate"></div>
16+
</div>
17+
<div style="position: relative" class="margin">
18+
<video onplay="onPlay(this)" id="inputVideo" autoplay muted></video>
19+
<canvas id="overlay" />
20+
</div>
21+
<div class="row side-by-side">
22+
<div class="row">
23+
<label for="minFaceSize">Minimum Face Size:</label>
24+
<input disabled value="200" id="minFaceSize" type="text" class="bold">
25+
</div>
26+
<button
27+
class="waves-effect waves-light btn"
28+
onclick="onDecreaseMinFaceSize()"
29+
>
30+
<i class="material-icons left">-</i>
31+
</button>
32+
<button
33+
class="waves-effect waves-light btn"
34+
onclick="onIncreaseMinFaceSize()"
35+
>
36+
<i class="material-icons left">+</i>
37+
</button>
38+
</div>
39+
<div class="row side-by-side">
40+
<div class="row">
41+
<label for="time">Time:</label>
42+
<input disabled value="-" id="time" type="text" class="bold">
43+
</div>
44+
<div class="row">
45+
<label for="fps">Estimated Fps:</label>
46+
<input disabled value="-" id="fps" type="text" class="bold">
47+
</div>
48+
</div>
49+
</div>
50+
51+
<script>
52+
let modelLoaded = false
53+
let minFaceSize = 200
54+
let minConfidence = 0.9
55+
let forwardTimes = []
56+
57+
function onIncreaseMinFaceSize() {
58+
minFaceSize = Math.min(faceapi.round(minFaceSize + 50), 300)
59+
$('#minFaceSize').val(minFaceSize)
60+
}
61+
62+
function onDecreaseMinFaceSize() {
63+
minFaceSize = Math.max(faceapi.round(minFaceSize - 50), 50)
64+
$('#minFaceSize').val(minFaceSize)
65+
}
66+
67+
function updateTimeStats(timeInMs) {
68+
forwardTimes = [timeInMs].concat(forwardTimes).slice(0, 30)
69+
const avgTimeInMs = forwardTimes.reduce((total, t) => total + t) / forwardTimes.length
70+
$('#time').val(`${Math.round(avgTimeInMs)} ms`)
71+
$('#fps').val(`${faceapi.round(1000 / avgTimeInMs)}`)
72+
}
73+
74+
async function onPlay(videoEl) {
75+
if(videoEl.paused || videoEl.ended || !modelLoaded)
76+
return false
77+
78+
const { width, height } = faceapi.getMediaDimensions(videoEl)
79+
const canvas = $('#overlay').get(0)
80+
canvas.width = width
81+
canvas.height = height
82+
83+
const mtcnnParams = {
84+
minFaceSize
85+
}
86+
87+
const c = faceapi.createCanvas({ width: width , height: height })
88+
c.getContext('2d').drawImage(videoEl, 0, 0)
89+
90+
const { results, stats } = await faceapi.nets.mtcnn.forwardWithStats(c, mtcnnParams)
91+
updateTimeStats(stats.total)
92+
93+
if (results) {
94+
results.forEach(({ faceDetection, faceLandmarks }) => {
95+
if (faceDetection.score < minConfidence) {
96+
return
97+
}
98+
faceapi.drawDetection('overlay', faceDetection.forSize(width, height))
99+
faceapi.drawLandmarks('overlay', faceLandmarks.forSize(width, height), { lineWidth: 4, color: 'red' })
100+
})
101+
}
102+
setTimeout(() => onPlay(videoEl))
103+
}
104+
105+
async function run() {
106+
await faceapi.loadMtcnnModel('/')
107+
modelLoaded = true
108+
109+
const videoEl = $('#inputVideo').get(0)
110+
navigator.getUserMedia(
111+
{ video: {} },
112+
stream => videoEl.srcObject = stream,
113+
err => console.error(err)
114+
)
115+
$('#loader').hide()
116+
}
117+
118+
$(document).ready(function() {
119+
renderNavBar('#navbar', 'mtcnn_face_detection_webcam')
120+
run()
121+
})
122+
</script>
123+
</body>
124+
</html>

src/FaceDetection.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,23 @@ export class FaceDetection {
2424
)
2525
}
2626

27-
public getScore() {
27+
public get score(): number {
2828
return this._score
2929
}
3030

31-
public getBox() {
31+
public get box(): Rect {
3232
return this._box
3333
}
3434

35-
public getImageWidth() {
35+
public get imageWidth(): number {
3636
return this._imageWidth
3737
}
3838

39-
public getImageHeight() {
39+
public get imageHeight(): number {
4040
return this._imageHeight
4141
}
4242

43-
public getRelativeBox() {
43+
public get relativeBox(): Rect {
4444
return new Rect(
4545
this._box.x / this._imageWidth,
4646
this._box.y / this._imageHeight,
@@ -49,6 +49,26 @@ export class FaceDetection {
4949
)
5050
}
5151

52+
public getScore() {
53+
return this.score
54+
}
55+
56+
public getBox() {
57+
return this.box
58+
}
59+
60+
public getImageWidth() {
61+
return this.imageWidth
62+
}
63+
64+
public getImageHeight() {
65+
return this.imageHeight
66+
}
67+
68+
public getRelativeBox() {
69+
return this.relativeBox
70+
}
71+
5272
public forSize(width: number, height: number): FaceDetection {
5373
return new FaceDetection(
5474
this._score,

0 commit comments

Comments
 (0)