2021-05-25 14:58:20 +02:00
|
|
|
// @ts-nocheck // typescript checks disabled as this is pure javascript
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Human demo for browsers
|
|
|
|
*
|
|
|
|
* Demo for face descriptor analysis and face simmilarity analysis
|
|
|
|
*/
|
2021-04-12 14:29:52 +02:00
|
|
|
|
2021-06-14 14:16:10 +02:00
|
|
|
import Human from '../../dist/human.esm.js';
|
2021-03-12 00:26:04 +01:00
|
|
|
|
|
|
|
const userConfig = {
|
|
|
|
backend: 'wasm',
|
|
|
|
async: false,
|
|
|
|
warmup: 'none',
|
|
|
|
debug: true,
|
2021-06-14 14:16:10 +02:00
|
|
|
modelBasePath: '../../models/',
|
|
|
|
wasmPath: 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-wasm@3.7.0/dist/',
|
2021-03-12 00:26:04 +01:00
|
|
|
face: {
|
|
|
|
enabled: true,
|
2021-03-12 04:04:44 +01:00
|
|
|
detector: { rotation: true, return: true },
|
2021-03-12 00:26:04 +01:00
|
|
|
mesh: { enabled: true },
|
2021-04-13 17:05:52 +02:00
|
|
|
embedding: { enabled: false },
|
2021-03-12 00:26:04 +01:00
|
|
|
iris: { enabled: false },
|
2021-03-21 19:18:51 +01:00
|
|
|
age: { enabled: false },
|
|
|
|
gender: { enabled: false },
|
2021-05-18 17:26:16 +02:00
|
|
|
emotion: { enabled: true },
|
2021-03-21 19:18:51 +01:00
|
|
|
description: { enabled: true },
|
2021-03-12 00:26:04 +01:00
|
|
|
},
|
|
|
|
hand: { enabled: false },
|
|
|
|
gesture: { enabled: false },
|
|
|
|
body: { enabled: false },
|
2021-06-05 23:51:46 +02:00
|
|
|
filter: { enabled: true },
|
|
|
|
segmentation: { enabled: false },
|
2021-03-12 00:26:04 +01:00
|
|
|
};
|
2021-03-12 18:54:08 +01:00
|
|
|
|
|
|
|
const human = new Human(userConfig); // new instance of human
|
|
|
|
|
|
|
|
const all = []; // array that will hold all detected faces
|
2021-03-15 17:14:48 +01:00
|
|
|
let db = []; // array that holds all known faces
|
2021-03-12 00:26:04 +01:00
|
|
|
|
2021-03-24 16:08:49 +01:00
|
|
|
const minScore = 0.6;
|
|
|
|
const minConfidence = 0.8;
|
|
|
|
|
2021-03-12 00:26:04 +01:00
|
|
|
function log(...msg) {
|
|
|
|
const dt = new Date();
|
|
|
|
const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
console.log(ts, ...msg);
|
|
|
|
}
|
|
|
|
|
2021-03-24 16:08:49 +01:00
|
|
|
async function getFaceDB() {
|
|
|
|
// download db with known faces
|
|
|
|
try {
|
2021-06-14 14:16:10 +02:00
|
|
|
let res = await fetch('/demo/facematch/faces.json');
|
|
|
|
if (!res || !res.ok) res = await fetch('/human/demo/facematch/faces.json');
|
2021-03-24 16:08:49 +01:00
|
|
|
db = (res && res.ok) ? await res.json() : [];
|
|
|
|
for (const rec of db) {
|
|
|
|
rec.embedding = rec.embedding.map((a) => parseFloat(a.toFixed(4)));
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
log('Could not load faces database', err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-12 00:26:04 +01:00
|
|
|
async function analyze(face) {
|
2021-03-24 16:08:49 +01:00
|
|
|
// refresh faces database
|
|
|
|
await getFaceDB();
|
|
|
|
|
2021-03-12 18:54:08 +01:00
|
|
|
// if we have face image tensor, enhance it and display it
|
|
|
|
if (face.tensor) {
|
|
|
|
const enhanced = human.enhance(face);
|
2021-03-24 16:08:49 +01:00
|
|
|
const desc = document.getElementById('desc');
|
|
|
|
desc.innerText = `{"name":"unknown", "source":"${face.fileName}", "embedding":[${face.embedding}]},`;
|
|
|
|
const embedding = face.embedding.map((a) => parseFloat(a.toFixed(4)));
|
|
|
|
navigator.clipboard.writeText(`{"name":"unknown", "source":"${face.fileName}", "embedding":[${embedding}]},`);
|
2021-03-12 18:54:08 +01:00
|
|
|
if (enhanced) {
|
|
|
|
const c = document.getElementById('orig');
|
2021-03-21 19:18:51 +01:00
|
|
|
const squeeze = enhanced.squeeze().div(255);
|
|
|
|
await human.tf.browser.toPixels(squeeze, c);
|
2021-03-12 18:54:08 +01:00
|
|
|
enhanced.dispose();
|
|
|
|
squeeze.dispose();
|
2021-03-21 19:18:51 +01:00
|
|
|
const ctx = c.getContext('2d');
|
|
|
|
ctx.font = 'small-caps 0.4rem "Lato"';
|
|
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
2021-03-12 18:54:08 +01:00
|
|
|
}
|
|
|
|
}
|
2021-03-12 04:04:44 +01:00
|
|
|
|
2021-03-12 18:54:08 +01:00
|
|
|
// loop through all canvases that contain faces
|
2021-03-12 00:26:04 +01:00
|
|
|
const canvases = document.getElementsByClassName('face');
|
|
|
|
for (const canvas of canvases) {
|
2021-03-21 19:18:51 +01:00
|
|
|
// calculate similarity from selected face to current one in the loop
|
|
|
|
const current = all[canvas.tag.sample][canvas.tag.face];
|
2021-03-23 20:24:58 +01:00
|
|
|
const similarity = human.similarity(face.embedding, current.embedding, 3);
|
2021-03-15 17:14:48 +01:00
|
|
|
// get best match
|
2021-03-23 20:24:58 +01:00
|
|
|
// draw the canvas
|
2021-03-21 19:18:51 +01:00
|
|
|
canvas.title = similarity;
|
|
|
|
await human.tf.browser.toPixels(current.tensor, canvas);
|
2021-03-12 00:26:04 +01:00
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
ctx.font = 'small-caps 1rem "Lato"';
|
|
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
|
2021-03-21 19:18:51 +01:00
|
|
|
ctx.fillText(`${(100 * similarity).toFixed(1)}%`, 3, 23);
|
2021-03-12 00:26:04 +01:00
|
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
2021-03-21 19:18:51 +01:00
|
|
|
ctx.fillText(`${(100 * similarity).toFixed(1)}%`, 4, 24);
|
|
|
|
ctx.font = 'small-caps 0.8rem "Lato"';
|
2021-06-01 14:59:09 +02:00
|
|
|
ctx.fillText(`${current.age}y ${(100 * (current.genderScore || 0)).toFixed(1)}% ${current.gender}`, 4, canvas.height - 6);
|
2021-03-23 20:24:58 +01:00
|
|
|
// identify person
|
2021-03-24 16:08:49 +01:00
|
|
|
ctx.font = 'small-caps 1rem "Lato"';
|
|
|
|
const person = await human.match(current.embedding, db);
|
|
|
|
if (person.similarity && person.similarity > minScore && current.confidence > minConfidence) ctx.fillText(`${(100 * person.similarity).toFixed(1)}% ${person.name}`, 4, canvas.height - 30);
|
2021-03-12 00:26:04 +01:00
|
|
|
}
|
2021-03-12 18:54:08 +01:00
|
|
|
|
2021-03-21 19:18:51 +01:00
|
|
|
// sort all faces by similarity
|
2021-03-12 00:26:04 +01:00
|
|
|
const sorted = document.getElementById('faces');
|
|
|
|
[...sorted.children]
|
|
|
|
.sort((a, b) => parseFloat(b.title) - parseFloat(a.title))
|
|
|
|
.forEach((canvas) => sorted.appendChild(canvas));
|
|
|
|
}
|
|
|
|
|
2021-03-21 19:18:51 +01:00
|
|
|
async function faces(index, res, fileName) {
|
2021-03-12 00:26:04 +01:00
|
|
|
all[index] = res.face;
|
|
|
|
for (const i in res.face) {
|
2021-03-15 17:14:48 +01:00
|
|
|
all[index][i].fileName = fileName;
|
2021-03-12 00:26:04 +01:00
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
canvas.tag = { sample: index, face: i };
|
|
|
|
canvas.width = 200;
|
|
|
|
canvas.height = 200;
|
|
|
|
canvas.className = 'face';
|
2021-03-12 18:54:08 +01:00
|
|
|
// mouse click on any face canvas triggers analysis
|
2021-03-12 00:26:04 +01:00
|
|
|
canvas.addEventListener('click', (evt) => {
|
2021-03-13 17:26:53 +01:00
|
|
|
log('Select:', 'Image:', evt.target.tag.sample, 'Face:', evt.target.tag.face, all[evt.target.tag.sample][evt.target.tag.face]);
|
2021-03-12 00:26:04 +01:00
|
|
|
analyze(all[evt.target.tag.sample][evt.target.tag.face]);
|
|
|
|
});
|
2021-03-12 18:54:08 +01:00
|
|
|
// if we actually got face image tensor, draw canvas with that face
|
|
|
|
if (res.face[i].tensor) {
|
2021-03-21 19:18:51 +01:00
|
|
|
await human.tf.browser.toPixels(res.face[i].tensor, canvas);
|
2021-03-12 18:54:08 +01:00
|
|
|
document.getElementById('faces').appendChild(canvas);
|
2021-03-21 19:18:51 +01:00
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
ctx.font = 'small-caps 0.8rem "Lato"';
|
|
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
2021-06-01 14:59:09 +02:00
|
|
|
ctx.fillText(`${res.face[i].age}y ${(100 * (res.face[i].genderScore || 0)).toFixed(1)}% ${res.face[i].gender}`, 4, canvas.height - 6);
|
2021-03-24 16:08:49 +01:00
|
|
|
const person = await human.match(res.face[i].embedding, db);
|
|
|
|
ctx.font = 'small-caps 1rem "Lato"';
|
|
|
|
if (person.similarity && person.similarity > minScore && res.face[i].confidence > minConfidence) ctx.fillText(`${(100 * person.similarity).toFixed(1)}% ${person.name}`, 4, canvas.height - 30);
|
2021-03-12 18:54:08 +01:00
|
|
|
}
|
2021-03-12 00:26:04 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-13 17:26:53 +01:00
|
|
|
async function process(index, image) {
|
2021-03-12 00:26:04 +01:00
|
|
|
return new Promise((resolve) => {
|
2021-03-13 17:26:53 +01:00
|
|
|
const img = new Image(128, 128);
|
2021-03-12 18:54:08 +01:00
|
|
|
img.onload = () => { // must wait until image is loaded
|
2021-06-14 14:16:10 +02:00
|
|
|
human.detect(img, userConfig).then(async (res) => {
|
2021-03-21 19:18:51 +01:00
|
|
|
await faces(index, res, image); // then wait until image is analyzed
|
2021-03-13 17:26:53 +01:00
|
|
|
log('Add image:', index + 1, image, 'faces:', res.face.length);
|
|
|
|
document.getElementById('images').appendChild(img); // and finally we can add it
|
|
|
|
resolve(true);
|
|
|
|
});
|
2021-03-12 00:26:04 +01:00
|
|
|
};
|
2021-04-02 14:15:39 +02:00
|
|
|
img.onerror = () => {
|
|
|
|
log('Add image error:', index + 1, image);
|
|
|
|
resolve(false);
|
|
|
|
};
|
2021-03-13 00:24:34 +01:00
|
|
|
img.title = image;
|
|
|
|
img.src = encodeURI(image);
|
2021-03-12 00:26:04 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-03-15 17:14:48 +01:00
|
|
|
async function createDB() {
|
|
|
|
log('Creating Faces DB...');
|
|
|
|
for (const image of all) {
|
|
|
|
for (const face of image) db.push({ name: 'unknown', source: face.fileName, embedding: face.embedding });
|
|
|
|
}
|
|
|
|
log(db);
|
|
|
|
}
|
|
|
|
|
2021-03-12 00:26:04 +01:00
|
|
|
async function main() {
|
2021-04-25 20:30:40 +02:00
|
|
|
window.addEventListener('unhandledrejection', (evt) => {
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
console.error(evt.reason || evt);
|
|
|
|
document.getElementById('list').innerHTML = evt?.reason?.message || evt?.reason || evt;
|
|
|
|
evt.preventDefault();
|
|
|
|
});
|
|
|
|
|
2021-03-15 17:14:48 +01:00
|
|
|
// pre-load human models
|
2021-03-12 00:26:04 +01:00
|
|
|
await human.load();
|
2021-03-15 17:14:48 +01:00
|
|
|
|
2021-03-24 16:08:49 +01:00
|
|
|
let images = [];
|
|
|
|
let dir = [];
|
2021-04-02 14:37:35 +02:00
|
|
|
// load face descriptor database
|
2021-03-24 16:08:49 +01:00
|
|
|
await getFaceDB();
|
2021-04-02 14:37:35 +02:00
|
|
|
|
2021-03-13 00:24:34 +01:00
|
|
|
// enumerate all sample images in /assets
|
2021-06-02 19:35:33 +02:00
|
|
|
const res = await fetch('/samples/groups');
|
2021-03-24 16:08:49 +01:00
|
|
|
dir = (res && res.ok) ? await res.json() : [];
|
|
|
|
images = images.concat(dir.filter((img) => (img.endsWith('.jpg') && img.includes('sample'))));
|
2021-03-13 00:24:34 +01:00
|
|
|
|
2021-04-02 14:05:19 +02:00
|
|
|
// could not dynamically enumerate images so using static list
|
|
|
|
if (images.length === 0) {
|
|
|
|
images = [
|
2021-06-02 19:35:33 +02:00
|
|
|
'groups/group1.jpg',
|
|
|
|
'groups/group2.jpg',
|
|
|
|
'groups/group3.jpg',
|
|
|
|
'groups/group4.jpg',
|
|
|
|
'groups/group5.jpg',
|
|
|
|
'groups/group6.jpg',
|
|
|
|
'groups/group7.jpg',
|
|
|
|
'groups/group8.jpg',
|
|
|
|
'groups/group9.jpg',
|
|
|
|
'groups/group10.jpg',
|
|
|
|
'groups/group11.jpg',
|
|
|
|
'groups/group12.jpg',
|
|
|
|
'groups/group13.jpg',
|
|
|
|
'groups/group14.jpg',
|
2021-04-02 14:05:19 +02:00
|
|
|
];
|
2021-04-02 14:37:35 +02:00
|
|
|
// add prefix for gitpages
|
2021-06-16 21:46:05 +02:00
|
|
|
images = images.map((a) => `/human/samples/${a}`);
|
2021-04-02 14:06:28 +02:00
|
|
|
log('Adding static image list:', images.length, 'images');
|
2021-04-02 14:05:19 +02:00
|
|
|
}
|
|
|
|
|
2021-04-02 14:09:06 +02:00
|
|
|
// download and analyze all images
|
|
|
|
for (let i = 0; i < images.length; i++) await process(i, images[i]);
|
|
|
|
|
2021-03-15 17:14:48 +01:00
|
|
|
// print stats
|
2021-03-13 17:26:53 +01:00
|
|
|
const num = all.reduce((prev, cur) => prev += cur.length, 0);
|
|
|
|
log('Extracted faces:', num, 'from images:', all.length);
|
|
|
|
log(human.tf.engine().memory());
|
2021-03-15 17:14:48 +01:00
|
|
|
|
|
|
|
// if we didn't download db, generate it from current faces
|
|
|
|
if (!db || db.length === 0) await createDB();
|
|
|
|
else log('Loaded Faces DB:', db.length);
|
|
|
|
|
2021-03-12 04:04:44 +01:00
|
|
|
log('Ready');
|
2021-03-12 00:26:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
window.onload = main;
|