Commit 5f0b196d by vincent

added batch input example for face landmarks + fixed: return array if batch…

added batch input example for face landmarks + fixed: return array if batch input with array of length 1
parent 27ec01e6
...@@ -113,6 +113,10 @@ function renderNavBar(navbarId, exampleUri) { ...@@ -113,6 +113,10 @@ function renderNavBar(navbarId, exampleUri) {
{ {
uri: 'detect_and_recognize_faces', uri: 'detect_and_recognize_faces',
name: 'Detect and Recognize Faces' name: 'Detect and Recognize Faces'
},
{
uri: 'batch_face_landmarks',
name: 'Batch Face Landmarks'
} }
] ]
...@@ -152,9 +156,11 @@ function renderNavBar(navbarId, exampleUri) { ...@@ -152,9 +156,11 @@ function renderNavBar(navbarId, exampleUri) {
menuContent.appendChild(li) menuContent.appendChild(li)
examples examples
.filter(ex => ex.uri !== exampleUri)
.forEach(ex => { .forEach(ex => {
const li = document.createElement('li') const li = document.createElement('li')
if (ex.uri === exampleUri) {
li.style.background='#b0b0b0'
}
const a = document.createElement('a') const a = document.createElement('a')
a.classList.add('waves-effect', 'waves-light') a.classList.add('waves-effect', 'waves-light')
a.href = ex.uri a.href = ex.uri
......
...@@ -24,6 +24,9 @@ app.get('/detect_and_draw_faces', (req, res) => res.sendFile(path.join(viewsDir, ...@@ -24,6 +24,9 @@ app.get('/detect_and_draw_faces', (req, res) => res.sendFile(path.join(viewsDir,
app.get('/detect_and_draw_landmarks', (req, res) => res.sendFile(path.join(viewsDir, 'detectAndDrawLandmarks.html'))) app.get('/detect_and_draw_landmarks', (req, res) => res.sendFile(path.join(viewsDir, 'detectAndDrawLandmarks.html')))
app.get('/face_alignment', (req, res) => res.sendFile(path.join(viewsDir, 'faceAlignment.html'))) app.get('/face_alignment', (req, res) => res.sendFile(path.join(viewsDir, 'faceAlignment.html')))
app.get('/detect_and_recognize_faces', (req, res) => res.sendFile(path.join(viewsDir, 'detectAndRecognizeFaces.html'))) app.get('/detect_and_recognize_faces', (req, res) => res.sendFile(path.join(viewsDir, 'detectAndRecognizeFaces.html')))
app.get('/batch_face_landmarks', (req, res) => res.sendFile(path.join(viewsDir, 'batchFaceLandmarks.html')))
app.post('/fetch_external_image', async (req, res) => { app.post('/fetch_external_image', async (req, res) => {
const { imageUrl } = req.body const { imageUrl } = req.body
......
<!DOCTYPE html>
<html>
<head>
<script src="face-api.js"></script>
<script src="commons.js"></script>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
</head>
<body>
<div id="navbar"></div>
<div class="center-content page-container">
<div>
<div class="progress" id="loader">
<div class="indeterminate"></div>
</div>
<div class="row side-by-side">
<div class="row">
<label for="timeNoBatch">Time for processing each face seperately:</label>
<input disabled value="-" id="timeNoBatch" type="text" class="bold"/>
</div>
<div class="row">
<label for="timeBatch">Time for processing in Batch:</label>
<input disabled value="-" id="timeBatch" type="text" class="bold"/>
</div>
</div>
<div class="row side-by-side">
<div>
<label for="numImages">Num Images:</label>
<input id="numImages" type="text" class="bold" value="40"/>
</div>
<button
class="waves-effect waves-light btn"
onclick="measureTimingsAndDisplay()"
>
Ok
</button>
</div>
<div class="row side-by-side">
<div class="center-content">
<div id="faceContainer"></div>
</div>
</div>
</div>
</div>
<script>
let images = []
let landmarksByFace = []
let numImages = 40
function onNumImagesChanged(e) {
const val = parseInt(e.target.value) || 40
numImages = Math.min(Math.max(val, 0), 40)
e.target.value = numImages
}
function displayTimeStats(timeNoBatch, timeBatch) {
$('#timeNoBatch').val(`${timeNoBatch} ms`)
$('#timeBatch').val(`${timeBatch} ms`)
}
function drawLandmarkCanvas(img, landmarks) {
const canvas = faceapi.createCanvasFromMedia(img)
$('#faceContainer').append(canvas)
faceapi.drawLandmarks(canvas, landmarks, { lineWidth: 2 , drawLines: true })
}
async function runLandmarkDetection(useBatchInput) {
const ts = Date.now()
landmarksByFace = useBatchInput
? await faceapi.detectLandmarks(images.slice(0, numImages))
: await Promise.all(images.slice(0, numImages).map(img => faceapi.detectLandmarks(img)))
const time = Date.now() - ts
return time
}
async function measureTimings() {
const timeNoBatch = await runLandmarkDetection(false)
const timeBatch = await runLandmarkDetection(true)
return { timeNoBatch, timeBatch }
}
async function measureTimingsAndDisplay() {
const { timeNoBatch, timeBatch } = await measureTimings()
displayTimeStats(timeNoBatch, timeBatch)
$('#faceContainer').empty()
landmarksByFace.forEach((landmarks, i) => drawLandmarkCanvas(images[i], landmarks))
}
async function run() {
await faceapi.loadFaceLandmarkModel('/')
$('#loader').hide()
const allImgUris = classes
.map(clazz => Array.from(Array(5), (_, idx) => getFaceImageUri(clazz, idx + 1)))
.reduce((flat, arr) => flat.concat(arr))
images = await Promise.all(allImgUris.map(
async uri => faceapi.bufferToImage(await fetchImage(uri))
))
// warmup
await measureTimings()
// run
measureTimingsAndDisplay()
}
$(document).ready(function() {
$('#numImages').on('change', onNumImagesChanged)
renderNavBar('#navbar', 'batch_face_landmarks')
run()
})
</script>
</body>
</html>
\ No newline at end of file
...@@ -26,6 +26,11 @@ module.exports = function(config) { ...@@ -26,6 +26,11 @@ module.exports = function(config) {
tsconfig: 'tsconfig.test.json' tsconfig: 'tsconfig.test.json'
}, },
browsers: ['Chrome'], browsers: ['Chrome'],
browserNoActivityTimeout: 60000 browserNoActivityTimeout: 60000,
client: {
jasmine: {
timeoutInterval: 30000
}
}
}) })
} }
...@@ -9,11 +9,15 @@ import { createCanvasFromMedia } from './utils'; ...@@ -9,11 +9,15 @@ import { createCanvasFromMedia } from './utils';
export class NetInput { export class NetInput {
private _inputs: tf.Tensor3D[] = [] private _inputs: tf.Tensor3D[] = []
private _isManaged: boolean = false private _isManaged: boolean = false
private _isBatchInput: boolean = false
private _inputDimensions: number[][] = [] private _inputDimensions: number[][] = []
private _paddings: Point[] = [] private _paddings: Point[] = []
constructor(inputs: tf.Tensor4D | Array<TResolvedNetInput>) { constructor(
inputs: tf.Tensor4D | Array<TResolvedNetInput>,
isBatchInput: boolean = false
) {
if (isTensor4D(inputs)) { if (isTensor4D(inputs)) {
this._inputs = tf.unstack(inputs as tf.Tensor4D) as tf.Tensor3D[] this._inputs = tf.unstack(inputs as tf.Tensor4D) as tf.Tensor3D[]
} }
...@@ -30,6 +34,8 @@ export class NetInput { ...@@ -30,6 +34,8 @@ export class NetInput {
) )
}) })
} }
this._isBatchInput = this.batchSize > 1 || isBatchInput
this._inputDimensions = this._inputs.map(t => t.shape) this._inputDimensions = this._inputs.map(t => t.shape)
} }
...@@ -41,6 +47,10 @@ export class NetInput { ...@@ -41,6 +47,10 @@ export class NetInput {
return this._isManaged return this._isManaged
} }
public get isBatchInput(): boolean {
return this._isBatchInput
}
public get batchSize(): number { public get batchSize(): number {
return this._inputs.length return this._inputs.length
} }
......
...@@ -128,6 +128,8 @@ export class FaceLandmarkNet { ...@@ -128,6 +128,8 @@ export class FaceLandmarkNet {
landmarkTensors.forEach(t => t.dispose()) landmarkTensors.forEach(t => t.dispose())
return landmarksForBatch.length === 1 ? landmarksForBatch[0] : landmarksForBatch return netInput.isBatchInput
? landmarksForBatch
: landmarksForBatch[0]
} }
} }
\ No newline at end of file
...@@ -72,5 +72,5 @@ export async function toNetInput( ...@@ -72,5 +72,5 @@ export async function toNetInput(
inputArray.map(input => isMediaElement(input) && awaitMediaLoaded(input)) inputArray.map(input => isMediaElement(input) && awaitMediaLoaded(input))
) )
return afterCreate(new NetInput(inputArray)) return afterCreate(new NetInput(inputArray, Array.isArray(inputs)))
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment