Skip to content

Commit 5b215ad

Browse files
mtcnn face recognition + face alignment examples
1 parent 930f85b commit 5b215ad

File tree

6 files changed

+407
-14
lines changed

6 files changed

+407
-14
lines changed

examples/public/commons.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ function renderNavBar(navbarId, exampleUri) {
126126
uri: 'mtcnn_face_detection_webcam',
127127
name: 'MTCNN Face Detection Webcam'
128128
},
129+
{
130+
uri: 'mtcnn_face_recognition',
131+
name: 'MTCNN Face Recognition'
132+
},
133+
{
134+
uri: 'mtcnn_face_recognition_webcam',
135+
name: 'MTCNN Face Recognition Webcam'
136+
},
129137
{
130138
uri: 'batch_face_landmarks',
131139
name: 'Batch Face Landmarks'

examples/server.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ app.get('/detect_and_recognize_faces', (req, res) => res.sendFile(path.join(view
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')))
2929
app.get('/mtcnn_face_detection_webcam', (req, res) => res.sendFile(path.join(viewsDir, 'mtcnnFaceDetectionWebcam.html')))
30+
app.get('/mtcnn_face_recognition', (req, res) => res.sendFile(path.join(viewsDir, 'mtcnnFaceRecognition.html')))
31+
app.get('/mtcnn_face_recognition_webcam', (req, res) => res.sendFile(path.join(viewsDir, 'mtcnnFaceRecognitionWebcam.html')))
3032
app.get('/batch_face_landmarks', (req, res) => res.sendFile(path.join(viewsDir, 'batchFaceLandmarks.html')))
3133
app.get('/batch_face_recognition', (req, res) => res.sendFile(path.join(viewsDir, 'batchFaceRecognition.html')))
3234

examples/views/faceAlignment.html

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,17 @@
5050
<i class="material-icons left">+</i>
5151
</button>
5252
</div>
53+
<div class="row">
54+
<p>
55+
<input type="checkbox" id="drawLinesCheckbox" onchange="onChangeUseMtcnn(event)" />
56+
<label for="drawLinesCheckbox">Use Mtcnn</label>
57+
</p>
58+
</div>
5359
</div>
5460

5561
<script>
5662
let minConfidence = 0.7
57-
let drawLines = true
63+
let useMtcnn = false
5864

5965
function onIncreaseMinConfidence() {
6066
minConfidence = Math.min(faceapi.round(minConfidence + 0.1), 1.0)
@@ -68,26 +74,54 @@
6874
updateResults()
6975
}
7076

77+
function onChangeUseMtcnn(e) {
78+
useMtcnn = $(e.target).prop('checked')
79+
updateResults()
80+
}
81+
7182
async function loadImageFromUrl(url) {
7283
const img = await requestExternalImage($('#imgUrlInput').val())
7384
$('#inputImg').get(0).src = img.src
7485
updateResults()
7586
}
7687

77-
async function updateResults() {
78-
const inputImgEl = $('#inputImg').get(0)
79-
const { width, height } = inputImgEl
80-
const canvas = $('#overlay').get(0)
81-
canvas.width = width
82-
canvas.height = height
88+
async function locateAndAlignFacesWithMtcnn(inputImgEl) {
89+
const input = await faceapi.toNetInput(
90+
inputImgEl,
91+
// dispose input manually
92+
false,
93+
// keep canvases (required for mtcnn)
94+
true
95+
)
96+
97+
const results = await faceapi.mtcnn(input, { minFaceSize: 100 })
98+
99+
const unalignedFaceImages = await faceapi.extractFaces(input.inputs[0], results.map(res => res.faceDetection))
100+
101+
const alignedFaceBoxes = results
102+
.filter(res => res.faceDetection.score > minConfidence)
103+
.map(res => res.faceLandmarks.align())
104+
105+
const alignedFaceImages = await faceapi.extractFaces(input.inputs[0], alignedFaceBoxes)
83106

107+
// free memory for input tensors
108+
input.dispose()
109+
110+
return {
111+
unalignedFaceImages,
112+
alignedFaceImages
113+
}
114+
}
115+
116+
async function locateAndAlignFacesWithSSD(inputImgEl) {
84117
const input = await faceapi.toNetInput(inputImgEl)
118+
85119
const locations = await faceapi.locateFaces(input, minConfidence)
86120

87-
const faceImages = await faceapi.extractFaces(input.inputs[0], locations)
121+
const unalignedFaceImages = await faceapi.extractFaces(input.inputs[0], locations)
88122

89123
// detect landmarks and get the aligned face image bounding boxes
90-
const alignedFaceBoxes = await Promise.all(faceImages.map(
124+
const alignedFaceBoxes = await Promise.all(unalignedFaceImages.map(
91125
async (faceCanvas, i) => {
92126
const faceLandmarks = await faceapi.detectLandmarks(faceCanvas)
93127
return faceLandmarks.align(locations[i])
@@ -98,8 +132,28 @@
98132
// free memory for input tensors
99133
input.dispose()
100134

135+
return {
136+
unalignedFaceImages,
137+
alignedFaceImages
138+
}
139+
}
140+
141+
async function updateResults() {
142+
const inputImgEl = $('#inputImg').get(0)
143+
const { width, height } = inputImgEl
144+
const canvas = $('#overlay').get(0)
145+
canvas.width = width
146+
canvas.height = height
147+
148+
const {
149+
unalignedFaceImages,
150+
alignedFaceImages
151+
} = useMtcnn
152+
? await locateAndAlignFacesWithMtcnn(inputImgEl)
153+
: await locateAndAlignFacesWithSSD(inputImgEl)
154+
101155
$('#facesContainer').empty()
102-
faceImages.forEach(async (faceCanvas, i) => {
156+
unalignedFaceImages.forEach(async (faceCanvas, i) => {
103157
$('#facesContainer').append(faceCanvas)
104158
$('#facesContainer').append(alignedFaceImages[i])
105159
})
@@ -114,6 +168,7 @@
114168
async function run() {
115169
await faceapi.loadFaceDetectionModel('/')
116170
await faceapi.loadFaceLandmarkModel('/')
171+
await faceapi.loadMtcnnModel('/')
117172
$('#loader').hide()
118173
onSelectionChanged($('#selectList select').val())
119174
}

examples/views/mtcnnFaceDetectionWebcam.html

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,7 @@
8484
minFaceSize
8585
}
8686

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)
87+
const { results, stats } = await faceapi.nets.mtcnn.forwardWithStats(videoEl, mtcnnParams)
9188
updateTimeStats(stats.total)
9289

9390
if (results) {
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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+
<img id="inputImg" src="" style="max-width: 800px;" />
19+
<canvas id="overlay" />
20+
</div>
21+
<div class="row side-by-side">
22+
<div id="selectList"></div>
23+
<div class="row">
24+
<label for="imgUrlInput">Get image from URL:</label>
25+
<input id="imgUrlInput" type="text" class="bold">
26+
</div>
27+
<button
28+
class="waves-effect waves-light btn"
29+
onclick="loadImageFromUrl()"
30+
>
31+
Ok
32+
</button>
33+
</div>
34+
<div class="row side-by-side">
35+
<div class="row side-by-side">
36+
<div class="row">
37+
<label for="minFaceSize">Minimum Face Size:</label>
38+
<input disabled value="40" id="minFaceSize" type="text" class="bold">
39+
</div>
40+
<button
41+
class="waves-effect waves-light btn"
42+
onclick="onDecreaseMinFaceSize()"
43+
>
44+
<i class="material-icons left">-</i>
45+
</button>
46+
<button
47+
class="waves-effect waves-light btn"
48+
onclick="onIncreaseMinFaceSize()"
49+
>
50+
<i class="material-icons left">+</i>
51+
</button>
52+
</div>
53+
<div class="row">
54+
<label for="minConfidence">Min Confidence:</label>
55+
<input disabled value="0.7" id="minConfidence" type="text" class="bold">
56+
</div>
57+
<button
58+
class="waves-effect waves-light btn button-sm"
59+
onclick="onDecreaseMinConfidence()"
60+
>
61+
<i class="material-icons left">-</i>
62+
</button>
63+
<button
64+
class="waves-effect waves-light btn button-sm"
65+
onclick="onIncreaseMinConfidence()"
66+
>
67+
<i class="material-icons left">+</i>
68+
</button>
69+
<div class="row">
70+
<label for="maxDistance">Max Descriptor Distance:</label>
71+
<input disabled value="0.6" id="maxDistance" type="text" class="bold">
72+
</div>
73+
<button
74+
class="waves-effect waves-light btn button-sm"
75+
onclick="onDecreaseMaxDistance()"
76+
>
77+
<i class="material-icons left">-</i>
78+
</button>
79+
<button
80+
class="waves-effect waves-light btn button-sm"
81+
onclick="onIncreaseMaxDistance()"
82+
>
83+
<i class="material-icons left">+</i>
84+
</button>
85+
</div>
86+
</div>
87+
88+
<script>
89+
let maxDistance = 0.6
90+
let minConfidence = 0.7
91+
let minFaceSize = 40
92+
let trainDescriptorsByClass = []
93+
94+
function onIncreaseMinFaceSize() {
95+
minFaceSize = Math.min(faceapi.round(minFaceSize + 20), 200)
96+
$('#minFaceSize').val(minFaceSize)
97+
}
98+
99+
function onDecreaseMinFaceSize() {
100+
minFaceSize = Math.max(faceapi.round(minFaceSize - 20), 20)
101+
$('#minFaceSize').val(minFaceSize)
102+
}
103+
104+
function onIncreaseMinConfidence() {
105+
minConfidence = Math.min(faceapi.round(minConfidence + 0.1), 1.0)
106+
$('#minConfidence').val(minConfidence)
107+
updateResults()
108+
}
109+
110+
function onDecreaseMinConfidence() {
111+
minConfidence = Math.max(faceapi.round(minConfidence - 0.1), 0.1)
112+
$('#minConfidence').val(minConfidence)
113+
updateResults()
114+
}
115+
116+
function onIncreaseMaxDistance() {
117+
maxDistance = Math.min(faceapi.round(maxDistance + 0.1), 1.0)
118+
$('#maxDistance').val(maxDistance)
119+
updateResults()
120+
}
121+
122+
function onDecreaseMaxDistance() {
123+
maxDistance = Math.max(faceapi.round(maxDistance - 0.1), 0.1)
124+
$('#maxDistance').val(maxDistance)
125+
updateResults()
126+
}
127+
128+
async function loadImageFromUrl(url) {
129+
const img = await requestExternalImage($('#imgUrlInput').val())
130+
$('#inputImg').get(0).src = img.src
131+
updateResults()
132+
}
133+
134+
async function updateResults() {
135+
const inputImgEl = $('#inputImg').get(0)
136+
const { width, height } = inputImgEl
137+
const canvas = $('#overlay').get(0)
138+
canvas.width = width
139+
canvas.height = height
140+
141+
const mtcnnParams = {
142+
minFaceSize
143+
}
144+
145+
const fullFaceDescriptions = (await faceapi.allFacesMtcnn(inputImgEl, mtcnnParams))
146+
.map(fd => fd.forSize(width, height))
147+
148+
fullFaceDescriptions.forEach(({ detection, landmarks, descriptor }) => {
149+
faceapi.drawDetection('overlay', [detection], { withScore: false })
150+
faceapi.drawLandmarks('overlay', landmarks, { lineWidth: 4, color: 'red' })
151+
const bestMatch = getBestMatch(trainDescriptorsByClass, descriptor)
152+
const text = `${bestMatch.distance < maxDistance ? bestMatch.className : 'unkown'} (${bestMatch.distance})`
153+
const { x, y, height: boxHeight } = detection.getBox()
154+
faceapi.drawText(
155+
canvas.getContext('2d'),
156+
x,
157+
y + boxHeight,
158+
text,
159+
Object.assign(faceapi.getDefaultDrawOptions(), { color: 'red', fontSize: 16 })
160+
)
161+
})
162+
}
163+
164+
async function onSelectionChanged(uri) {
165+
const imgBuf = await fetchImage(uri)
166+
$(`#inputImg`).get(0).src = (await faceapi.bufferToImage(imgBuf)).src
167+
updateResults()
168+
}
169+
170+
async function run() {
171+
await faceapi.loadMtcnnModel('/')
172+
await faceapi.loadFaceRecognitionModel('/')
173+
174+
trainDescriptorsByClass = await initTrainDescriptorsByClass(faceapi.recognitionNet, 1)
175+
176+
$('#loader').hide()
177+
onSelectionChanged($('#selectList select').val())
178+
}
179+
180+
$(document).ready(function() {
181+
renderNavBar('#navbar', 'mtcnn_face_recognition')
182+
renderImageSelectList(
183+
'#selectList',
184+
async (uri) => {
185+
await onSelectionChanged(uri)
186+
},
187+
'bbt1.jpg'
188+
)
189+
run()
190+
})
191+
</script>
192+
</body>
193+
</html>

0 commit comments

Comments
 (0)