Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
F
face
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Иван Кубота
face
Commits
174e0c28
Commit
174e0c28
authored
Oct 14, 2018
by
vincent
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
FaceMatcher
parent
397c05ae
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
146 additions
and
57 deletions
+146
-57
bbt.js
examples/public/js/bbt.js
+10
-25
batchFaceRecognition.html
examples/views/batchFaceRecognition.html
+3
-7
bbtFaceRecognition.html
examples/views/bbtFaceRecognition.html
+4
-7
faceRecognition.html
examples/views/faceRecognition.html
+11
-15
FaceMatch.ts
src/classes/FaceMatch.ts
+19
-0
FullFaceDescription.ts
src/classes/FullFaceDescription.ts
+1
-1
LabeledFaceDescriptors.ts
src/classes/LabeledFaceDescriptors.ts
+21
-0
index.ts
src/classes/index.ts
+3
-0
FaceLandmark68Net.ts
src/faceLandmarkNet/FaceLandmark68Net.ts
+2
-2
FaceMatcher.ts
src/globalApi/FaceMatcher.ts
+71
-0
index.ts
src/globalApi/index.ts
+1
-0
No files found.
examples/public/js/bbt.js
View file @
174e0c28
...
...
@@ -30,40 +30,24 @@ function renderFaceImageSelectList(selectListId, onChange, initialValue) {
}
// fetch first image of each class and compute their descriptors
async
function
initBbtFaceDescriptors
(
net
,
numImagesForTraining
=
1
)
{
async
function
createBbtFaceMatcher
(
numImagesForTraining
=
1
)
{
const
maxAvailableImagesPerClass
=
5
numImagesForTraining
=
Math
.
min
(
numImagesForTraining
,
maxAvailableImagesPerClass
)
return
Promise
.
all
(
classes
.
map
(
const
labeledFaceDescriptors
=
await
Promise
.
all
(
classes
.
map
(
async
className
=>
{
const
descriptors
=
[]
for
(
let
i
=
1
;
i
<
(
numImagesForTraining
+
1
);
i
++
)
{
const
img
=
await
faceapi
.
fetchImage
(
getFaceImageUri
(
className
,
i
))
descriptors
.
push
(
await
faceapi
.
computeFaceDescriptor
(
img
))
}
return
{
descriptors
,
className
}
}
))
}
function
getBestMatch
(
descriptorsByClass
,
queryDescriptor
)
{
function
computeMeanDistance
(
descriptorsOfClass
)
{
return
faceapi
.
round
(
descriptorsOfClass
.
map
(
d
=>
faceapi
.
euclideanDistance
(
d
,
queryDescriptor
))
.
reduce
((
d1
,
d2
)
=>
d1
+
d2
,
0
)
/
(
descriptorsOfClass
.
length
||
1
)
return
new
faceapi
.
LabeledFaceDescriptors
(
className
,
descriptors
)
}
return
descriptorsByClass
.
map
(
({
descriptors
,
className
})
=>
({
distance
:
computeMeanDistance
(
descriptors
),
className
})
)
.
reduce
((
best
,
curr
)
=>
best
.
distance
<
curr
.
distance
?
best
:
curr
)
}
))
return
new
faceapi
.
FaceMatcher
(
labeledFaceDescriptors
)
}
\ No newline at end of file
examples/views/batchFaceRecognition.html
View file @
174e0c28
...
...
@@ -48,8 +48,7 @@
<script>
let
images
=
[]
let
referenceDescriptorsByClass
=
[]
let
descriptorsByFace
=
[]
let
faceMatcher
=
null
let
numImages
=
16
let
maxDistance
=
0.6
...
...
@@ -68,15 +67,12 @@
const
canvas
=
faceapi
.
createCanvasFromMedia
(
img
)
$
(
'#faceContainer'
).
append
(
canvas
)
const
bestMatch
=
getBestMatch
(
referenceDescriptorsByClass
,
descriptor
)
const
text
=
`
${
bestMatch
.
distance
<
maxDistance
?
bestMatch
.
className
:
'unkown'
}
(
${
bestMatch
.
distance
}
)`
const
x
=
20
,
y
=
canvas
.
height
-
20
faceapi
.
drawText
(
canvas
.
getContext
(
'2d'
),
x
,
y
,
text
,
faceMatcher
.
findBestMatch
(
descriptor
).
toString
()
,
Object
.
assign
(
faceapi
.
getDefaultDrawOptions
(),
{
color
:
'red'
,
fontSize
:
16
})
)
}
...
...
@@ -106,7 +102,7 @@
async
function
run
()
{
await
faceapi
.
loadFaceRecognitionModel
(
'/'
)
referenceDescriptorsByClass
=
await
initBbtFaceDescriptors
(
faceapi
.
recognitionNet
,
1
)
faceMatcher
=
await
createBbtFaceMatcher
(
1
)
$
(
'#loader'
).
hide
()
const
imgUris
=
classes
...
...
examples/views/bbtFaceRecognition.html
View file @
174e0c28
...
...
@@ -63,13 +63,10 @@
</div>
<script>
// for 150 x 150 sized face images 0.6 is a good threshold to
// judge whether two face descriptors are similar or not
const
threshold
=
0.6
let
interval
=
2000
let
isStop
=
false
let
referenceDescriptorsByClass
=
[]
let
faceMatcher
=
null
let
currImageIdx
=
2
,
currClassIdx
=
0
let
to
=
null
...
...
@@ -116,8 +113,8 @@
const
descriptor
=
await
faceapi
.
computeFaceDescriptor
(
input
)
displayTimeStats
(
Date
.
now
()
-
ts
)
const
bestMatch
=
getBestMatch
(
referenceDescriptorsByClass
,
descriptor
)
$
(
'#prediction'
).
val
(
`
${
bestMatch
.
distance
<
threshold
?
bestMatch
.
className
:
'unknown'
}
(
${
bestMatch
.
distance
}
)`
)
const
bestMatch
=
faceMatcher
.
findBestMatch
(
descriptor
)
$
(
'#prediction'
).
val
(
bestMatch
.
toString
()
)
currImageIdx
=
currClassIdx
===
(
classes
.
length
-
1
)
?
currImageIdx
+
1
...
...
@@ -138,7 +135,7 @@
setStatusText
(
'computing initial descriptors...'
)
referenceDescriptorsByClass
=
await
initBbtFaceDescriptors
(
faceapi
.
recognitionNet
)
faceMatcher
=
await
createBbtFaceMatcher
(
1
)
$
(
'#loader'
).
hide
()
runFaceRecognition
()
...
...
examples/views/faceRecognition.html
View file @
174e0c28
...
...
@@ -139,8 +139,7 @@
</body>
<script>
const
maxDescriptorDistance
=
0.6
let
referenceDescriptorsByClass
=
[]
let
faceMatcher
=
null
async
function
updateResults
()
{
if
(
!
isFaceDetectionModelLoaded
())
{
...
...
@@ -159,20 +158,17 @@
}
function
drawFaceRecognitionResults
(
results
)
{
const
{
width
,
height
}
=
$
(
'#inputImg'
).
get
(
0
)
const
canvas
=
$
(
'#overlay'
).
get
(
0
)
canvas
.
width
=
width
canvas
.
height
=
height
// resize detection and landmarks in case displayed image is smaller than
// original size
results
=
results
.
map
(
res
=>
res
.
forSize
(
width
,
height
))
const
boxesWithText
=
results
.
map
(({
detection
,
descriptor
})
=>
{
const
bestMatch
=
getBestMatch
(
referenceDescriptorsByClass
,
descriptor
)
const
text
=
`
${
bestMatch
.
distance
<
maxDescriptorDistance
?
bestMatch
.
className
:
'unknown'
}
(
${
bestMatch
.
distance
}
)`
return
new
faceapi
.
BoxWithText
(
detection
.
box
,
text
)
})
resizedResults
=
resizeCanvasAndResults
(
$
(
'#inputImg'
).
get
(
0
),
canvas
,
results
)
const
boxesWithText
=
resizedResults
.
map
(({
detection
,
descriptor
})
=>
new
faceapi
.
BoxWithText
(
detection
.
box
,
faceMatcher
.
findBestMatch
(
descriptor
).
toString
()
)
)
faceapi
.
drawDetection
(
canvas
,
boxesWithText
)
}
...
...
@@ -182,8 +178,8 @@
await
faceapi
.
loadFaceLandmarkModel
(
'/'
)
await
faceapi
.
loadFaceRecognitionModel
(
'/'
)
// initialize
reference descriptors (1 per bbt character)
referenceDescriptorsByClass
=
await
initBbtFaceDescriptors
(
faceapi
.
recognitionNet
,
1
)
// initialize
face matcher with 1 reference descriptor per bbt character
faceMatcher
=
await
createBbtFaceMatcher
(
1
)
// start processing image
updateResults
()
...
...
src/classes/FaceMatch.ts
0 → 100644
View file @
174e0c28
import
{
round
}
from
'tfjs-image-recognition-base'
;
export
class
FaceMatch
{
private
_label
:
string
private
_distance
:
number
constructor
(
label
:
string
,
distance
:
number
)
{
this
.
_label
=
label
this
.
_distance
=
distance
}
public
get
label
():
string
{
return
this
.
_label
}
public
get
distance
():
number
{
return
this
.
_distance
}
public
toString
(
withDistance
:
boolean
=
true
):
string
{
return
`
${
this
.
label
}${
withDistance
?
` (
${
round
(
this
.
distance
)}
)`
:
''
}
`
}
}
\ No newline at end of file
src/classes/FullFaceDescription.ts
View file @
174e0c28
...
...
@@ -4,7 +4,7 @@ import { FaceLandmarks } from './FaceLandmarks';
import
{
FaceLandmarks68
}
from
'./FaceLandmarks68'
;
export
interface
IFullFaceDescription
<
TFaceLandmarks
extends
FaceLandmarks
=
FaceLandmarks68
>
extends
IFaceDetectionWithLandmarks
<
TFaceLandmarks
>
{
extends
IFaceDetectionWithLandmarks
<
TFaceLandmarks
>
{
detection
:
FaceDetection
,
landmarks
:
TFaceLandmarks
,
...
...
src/classes/LabeledFaceDescriptors.ts
0 → 100644
View file @
174e0c28
export
class
LabeledFaceDescriptors
{
private
_label
:
string
private
_descriptors
:
Float32Array
[]
constructor
(
label
:
string
,
descriptors
:
Float32Array
[])
{
if
(
!
(
typeof
label
===
'string'
))
{
throw
new
Error
(
'LabeledFaceDescriptors - constructor expected label to be a string'
)
}
if
(
!
Array
.
isArray
(
descriptors
)
||
descriptors
.
some
(
desc
=>
!
(
desc
instanceof
Float32Array
)))
{
throw
new
Error
(
'LabeledFaceDescriptors - constructor expected descriptors to be an array of Float32Array'
)
}
this
.
_label
=
label
this
.
_descriptors
=
descriptors
}
public
get
label
():
string
{
return
this
.
_label
}
public
get
descriptors
():
Float32Array
[]
{
return
this
.
_descriptors
}
}
\ No newline at end of file
src/classes/index.ts
View file @
174e0c28
...
...
@@ -2,4 +2,6 @@ export * from './FaceDetection';
export
*
from
'./FaceLandmarks'
;
export
*
from
'./FaceLandmarks5'
;
export
*
from
'./FaceLandmarks68'
;
export
*
from
'./FaceMatch'
;
export
*
from
'./FullFaceDescription'
;
export
*
from
'./LabeledFaceDescriptors'
;
\ No newline at end of file
src/faceLandmarkNet/FaceLandmark68Net.ts
View file @
174e0c28
...
...
@@ -38,7 +38,7 @@ function denseBlock(
export
class
FaceLandmark68Net
extends
FaceLandmark68NetBase
<
NetParams
>
{
constructor
()
{
super
(
'FaceLandmark68
Large
Net'
)
super
(
'FaceLandmark68Net'
)
}
public
runNet
(
input
:
NetInput
):
tf
.
Tensor2D
{
...
...
@@ -46,7 +46,7 @@ export class FaceLandmark68Net extends FaceLandmark68NetBase<NetParams> {
const
{
params
}
=
this
if
(
!
params
)
{
throw
new
Error
(
'FaceLandmark68
Large
Net - load model before inference'
)
throw
new
Error
(
'FaceLandmark68Net - load model before inference'
)
}
return
tf
.
tidy
(()
=>
{
...
...
src/globalApi/FaceMatcher.ts
0 → 100644
View file @
174e0c28
import
{
FaceMatch
}
from
'../classes/FaceMatch'
;
import
{
FullFaceDescription
}
from
'../classes/FullFaceDescription'
;
import
{
LabeledFaceDescriptors
}
from
'../classes/LabeledFaceDescriptors'
;
import
{
euclideanDistance
}
from
'../euclideanDistance'
;
export
class
FaceMatcher
{
private
_labeledDescriptors
:
LabeledFaceDescriptors
[]
private
_distanceThreshold
:
number
constructor
(
inputs
:
LabeledFaceDescriptors
|
FullFaceDescription
|
Float32Array
|
Array
<
LabeledFaceDescriptors
|
FullFaceDescription
|
Float32Array
>
,
distanceThreshold
:
number
=
0.6
)
{
this
.
_distanceThreshold
=
distanceThreshold
const
inputArray
=
Array
.
isArray
(
inputs
)
?
inputs
:
[
inputs
]
if
(
!
inputArray
.
length
)
{
throw
new
Error
(
`FaceRecognizer.constructor - expected atleast one input`
)
}
let
count
=
1
const
createUniqueLabel
=
()
=>
`person
${
count
++
}
`
this
.
_labeledDescriptors
=
inputArray
.
map
((
desc
)
=>
{
if
(
desc
instanceof
LabeledFaceDescriptors
)
{
return
desc
}
if
(
desc
instanceof
FullFaceDescription
)
{
return
new
LabeledFaceDescriptors
(
createUniqueLabel
(),
[
desc
.
descriptor
])
}
if
(
desc
instanceof
Float32Array
)
{
return
new
LabeledFaceDescriptors
(
createUniqueLabel
(),
[
desc
])
}
throw
new
Error
(
`FaceRecognizer.constructor - expected inputs to be of type LabeledFaceDescriptors | FullFaceDescription | Float32Array | Array<LabeledFaceDescriptors | FullFaceDescription | Float32Array>`
)
})
}
public
get
labeledDescriptors
():
LabeledFaceDescriptors
[]
{
return
this
.
_labeledDescriptors
}
public
get
distanceThreshold
():
number
{
return
this
.
_distanceThreshold
}
public
computeMeanDistance
(
queryDescriptor
:
Float32Array
,
descriptors
:
Float32Array
[]):
number
{
return
descriptors
.
map
(
d
=>
euclideanDistance
(
d
,
queryDescriptor
))
.
reduce
((
d1
,
d2
)
=>
d1
+
d2
,
0
)
/
(
descriptors
.
length
||
1
)
}
public
matchDescriptor
(
queryDescriptor
:
Float32Array
):
FaceMatch
{
return
this
.
labeledDescriptors
.
map
(({
descriptors
,
label
})
=>
new
FaceMatch
(
label
,
this
.
computeMeanDistance
(
queryDescriptor
,
descriptors
)
))
.
reduce
((
best
,
curr
)
=>
best
.
distance
<
curr
.
distance
?
best
:
curr
)
}
public
findBestMatch
(
queryDescriptor
:
Float32Array
):
FaceMatch
{
const
bestMatch
=
this
.
matchDescriptor
(
queryDescriptor
)
return
bestMatch
.
distance
<
this
.
distanceThreshold
?
bestMatch
:
new
FaceMatch
(
'unknown'
,
bestMatch
.
distance
)
}
}
\ No newline at end of file
src/globalApi/index.ts
View file @
174e0c28
...
...
@@ -3,6 +3,7 @@ export * from './ComposableTask'
export
*
from
'./ComputeFaceDescriptorsTasks'
export
*
from
'./DetectFacesTasks'
export
*
from
'./DetectFaceLandmarksTasks'
export
*
from
'./FaceMatcher'
export
*
from
'./nets'
export
*
from
'./types'
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment