new module: face description

pull/91/head
Vladimir Mandic 2021-03-21 14:18:51 -04:00
parent 343e8c8d93
commit ee05c6a283
34 changed files with 1534 additions and 1026 deletions

View File

@ -1,6 +1,6 @@
# @vladmandic/human
Version: **1.1.10**
Version: **1.2.0**
Description: **Human: AI-powered 3D Face Detection, Face Embedding & Recognition, Body Pose Tracking, Hand & Finger Tracking, Iris Analysis, Age & Gender & Emotion Prediction & Gesture Recognition**
Author: **Vladimir Mandic <mandic00@live.com>**
@ -9,6 +9,10 @@ Repository: **<git+https://github.com/vladmandic/human.git>**
## Changelog
### **1.1.11** 2021/03/21 mandic00@live.com
- refactor face classes
### **1.1.10** 2021/03/18 mandic00@live.com
- cleanup

View File

@ -102,8 +102,8 @@ async function calcSimmilariry(result) {
document.getElementById('compare-canvas').getContext('2d').drawImage(original.canvas, 0, 0, 200, 200);
}
}
const simmilarity = human.simmilarity(original?.face[0]?.embedding, result?.face[0]?.embedding);
document.getElementById('simmilarity').innerText = `simmilarity: ${Math.trunc(1000 * simmilarity) / 10}%`;
const similarity = human.similarity(original?.face[0]?.embedding, result?.face[0]?.embedding);
document.getElementById('similarity').innerText = `similarity: ${Math.trunc(1000 * similarity) / 10}%`;
}
// draws processed results and starts processing of a next frame

View File

@ -27,7 +27,8 @@
<div id="images"></div>
<br>Selected Face (Enhanced):<br>
<canvas id="orig" style="width: 200px; height: 200px;"></canvas>
<br><br>Extracted Faces - click on a face to sort by simmilarity and get a known face match:<br>
<span id="desc" style="visibility: hidden; font-size: 0.4rem;"></span>
<br><br>Extracted Faces - click on a face to sort by similarity and get a known face match:<br>
<div id="faces"></div>
</body>
</html>

View File

@ -12,9 +12,10 @@ const userConfig = {
mesh: { enabled: true },
embedding: { enabled: true },
iris: { enabled: false },
age: { enabled: true },
gender: { enabled: true },
age: { enabled: false },
gender: { enabled: false },
emotion: { enabled: false },
description: { enabled: true },
},
hand: { enabled: false },
gesture: { enabled: false },
@ -40,41 +41,52 @@ async function analyze(face) {
// if we have face image tensor, enhance it and display it
if (face.tensor) {
const enhanced = human.enhance(face);
// const desc = document.getElementById('desc');
// desc.innerText = `{"name":"unknown", "source":"${face.fileName}", "embedding":[${face.embedding}]},`;
navigator.clipboard.writeText(`{"name":"unknown", "source":"${face.fileName}", "embedding":[${face.embedding}]},`);
if (enhanced) {
const c = document.getElementById('orig');
const squeeze = enhanced.squeeze();
human.tf.browser.toPixels(squeeze, c);
const squeeze = enhanced.squeeze().div(255);
await human.tf.browser.toPixels(squeeze, c);
enhanced.dispose();
squeeze.dispose();
const ctx = c.getContext('2d');
ctx.font = 'small-caps 0.4rem "Lato"';
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
}
}
// loop through all canvases that contain faces
const canvases = document.getElementsByClassName('face');
for (const canvas of canvases) {
// calculate simmilarity from selected face to current one in the loop
const simmilarity = human.simmilarity(face.embedding, all[canvas.tag.sample][canvas.tag.face].embedding, 2);
// calculate similarity from selected face to current one in the loop
const current = all[canvas.tag.sample][canvas.tag.face];
const similarity = human.similarity(face.embedding, current.embedding, 2);
// get best match
const person = (simmilarity > 0.99) ? await human.match(face.embedding, db) : { name: '' };
// draw the canvas and simmilarity score
canvas.title = simmilarity;
await human.tf.browser.toPixels(all[canvas.tag.sample][canvas.tag.face].tensor, canvas);
const person = await human.match(current.embedding, db);
// draw the canvas and similarity score
canvas.title = similarity;
await human.tf.browser.toPixels(current.tensor, canvas);
const ctx = canvas.getContext('2d');
ctx.font = 'small-caps 1rem "Lato"';
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillText(`${(100 * simmilarity).toFixed(1)}% ${person.name}`, 3, 23);
ctx.fillText(`${(100 * similarity).toFixed(1)}%`, 3, 23);
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillText(`${(100 * simmilarity).toFixed(1)}% ${person.name}`, 4, 24);
ctx.fillText(`${(100 * similarity).toFixed(1)}%`, 4, 24);
ctx.font = 'small-caps 0.8rem "Lato"';
ctx.fillText(`${current.age}y ${(100 * current.genderConfidence).toFixed(1)}% ${current.gender}`, 4, canvas.height - 6);
ctx.font = 'small-caps 1rem "Lato"';
if (person.similarity) ctx.fillText(`${(100 * person.similarity).toFixed(1)}% ${person.name}`, 4, canvas.height - 30);
}
// sort all faces by simmilarity
// sort all faces by similarity
const sorted = document.getElementById('faces');
[...sorted.children]
.sort((a, b) => parseFloat(b.title) - parseFloat(a.title))
.forEach((canvas) => sorted.appendChild(canvas));
}
function faces(index, res, fileName) {
async function faces(index, res, fileName) {
all[index] = res.face;
for (const i in res.face) {
// log(res.face[i]);
@ -87,13 +99,19 @@ function faces(index, res, fileName) {
// mouse click on any face canvas triggers analysis
canvas.addEventListener('click', (evt) => {
log('Select:', 'Image:', evt.target.tag.sample, 'Face:', evt.target.tag.face, all[evt.target.tag.sample][evt.target.tag.face]);
log('Select:', 'Gender:', all[evt.target.tag.sample][evt.target.tag.face].gender);
analyze(all[evt.target.tag.sample][evt.target.tag.face]);
});
// if we actually got face image tensor, draw canvas with that face
if (res.face[i].tensor) {
human.tf.browser.toPixels(res.face[i].tensor, canvas);
await human.tf.browser.toPixels(res.face[i].tensor, canvas);
document.getElementById('faces').appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.font = 'small-caps 0.8rem "Lato"';
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillText(`${res.face[i].age}y ${(100 * res.face[i].genderConfidence).toFixed(1)}% ${res.face[i].gender}`, 4, canvas.height - 6);
const person = await human.match(res.face[i].embedding, db);
ctx.font = 'small-caps 1rem "Lato"';
if (person.similarity && person.similarity > 0.60) ctx.fillText(`${(100 * person.similarity).toFixed(1)}% ${person.name}`, 4, canvas.height - 30);
}
}
}
@ -102,8 +120,8 @@ async function process(index, image) {
return new Promise((resolve) => {
const img = new Image(128, 128);
img.onload = () => { // must wait until image is loaded
human.detect(img).then((res) => {
faces(index, res, image); // then wait until image is analyzed
human.detect(img).then(async (res) => {
await faces(index, res, image); // then wait until image is analyzed
log('Add image:', index + 1, image, 'faces:', res.face.length);
document.getElementById('images').appendChild(img); // and finally we can add it
resolve(true);
@ -136,10 +154,14 @@ async function main() {
let images = dir.filter((img) => (img.endsWith('.jpg') && img.includes('sample')));
// enumerate additional private test images in /private, not includded in git repository
res = await fetch('/private/err');
res = await fetch('/private/me');
dir = (res && res.ok) ? await res.json() : [];
// images = images.concat(dir.filter((img) => (img.endsWith('.jpg'))));
images = dir.filter((img) => (img.endsWith('.jpg')));
images = images.concat(dir.filter((img) => (img.endsWith('.jpg'))));
// enumerate just possible error images, not includded in git repository
// res = await fetch('/private/err');
// dir = (res && res.ok) ? await res.json() : [];
// images = dir.filter((img) => (img.endsWith('.jpg')));
// download and analyze all images
log('Enumerated:', images.length, 'images');

File diff suppressed because one or more lines are too long

View File

@ -93,7 +93,7 @@
</div>
<div id="compare-container" style="display: none" class="compare-image">
<canvas id="compare-canvas" width="200px" height="200px"></canvas>
<div id="simmilarity"></div>
<div id="similarity"></div>
</div>
<div id="samples-container" class="samples-container"></div>
<div id="log" class="log"></div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

518
dist/human.esm.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

518
dist/human.js vendored

File diff suppressed because one or more lines are too long

6
dist/human.js.map vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16
dist/human.node.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
models/faceres.bin Normal file

Binary file not shown.

315
models/faceres.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "@vladmandic/human",
"version": "1.1.11",
"version": "1.2.0",
"description": "Human: AI-powered 3D Face Detection, Face Embedding & Recognition, Body Pose Tracking, Hand & Finger Tracking, Iris Analysis, Age & Gender & Emotion Prediction & Gesture Recognition",
"sideEffects": false,
"main": "dist/human.node.js",

View File

@ -59,6 +59,11 @@ export interface Config {
enabled: boolean,
modelPath: string,
},
description: {
enabled: boolean,
modelPath: string,
skipFrames: number,
},
age: {
enabled: boolean,
modelPath: string,
@ -212,21 +217,14 @@ const config: Config = {
modelPath: '../models/iris.json',
},
age: {
enabled: true,
modelPath: '../models/age.json',
description: {
enabled: true, // to improve accuracy of face embedding extraction it is
// recommended to enable detector.rotation and mesh.enabled
modelPath: '../models/faceres.json',
skipFrames: 31, // how many frames to go without re-running the detector
// only used for video inputs
},
gender: {
enabled: true,
minConfidence: 0.1, // threshold for discarding a prediction
modelPath: '../models/gender.json',
skipFrames: 32, // how many frames to go without re-running the detector
// only used for video inputs
},
emotion: {
enabled: true,
minConfidence: 0.1, // threshold for discarding a prediction
@ -234,9 +232,23 @@ const config: Config = {
modelPath: '../models/emotion.json',
},
age: {
enabled: false, // obsolete, replaced by description module
modelPath: '../models/age.json',
skipFrames: 31, // how many frames to go without re-running the detector
// only used for video inputs
},
gender: {
enabled: false, // obsolete, replaced by description module
minConfidence: 0.1, // threshold for discarding a prediction
modelPath: '../models/gender.json',
skipFrames: 32, // how many frames to go without re-running the detector
// only used for video inputs
},
embedding: {
enabled: false, // to improve accuracy of face embedding extraction it is
// highly recommended to enable detector.rotation and mesh.enabled
enabled: false, // obsolete, replaced by description module
modelPath: '../models/mobileface.json',
},
},

View File

@ -14,7 +14,7 @@ export async function load(config) {
return model;
}
export function simmilarity(embedding1, embedding2, order = 2): number {
export function similarity(embedding1, embedding2, order = 2): number {
if (!embedding1 || !embedding2) return 0;
if (embedding1?.length === 0 || embedding2?.length === 0) return 0;
if (embedding1?.length !== embedding2?.length) return 0;
@ -28,12 +28,12 @@ export function simmilarity(embedding1, embedding2, order = 2): number {
}
export function match(embedding: Array<number>, db: DB, threshold = 0) {
let best = { simmilarity: 0, name: '', source: '', embedding: [] as number[] };
let best = { similarity: 0, name: '', source: '', embedding: [] as number[] };
if (!embedding || !db || !Array.isArray(embedding) || !Array.isArray(db)) return best;
for (const f of db) {
if (f.embedding && f.name) {
const perc = simmilarity(embedding, f.embedding);
if (perc > threshold && perc > best.simmilarity) best = { ...f, simmilarity: perc };
const perc = similarity(embedding, f.embedding);
if (perc > threshold && perc > best.similarity) best = { ...f, similarity: perc };
}
}
return best;

View File

@ -4,6 +4,7 @@ import * as age from './age/age';
import * as gender from './gender/gender';
import * as emotion from './emotion/emotion';
import * as embedding from './embedding/embedding';
import * as faceres from './faceres/faceres';
type Tensor = typeof tf.Tensor;
@ -33,6 +34,7 @@ export const detectFace = async (parent, input): Promise<any> => {
let genderRes;
let emotionRes;
let embeddingRes;
let descRes;
const faceRes: Array<{
confidence: number,
boxConfidence: number,
@ -111,11 +113,23 @@ export const detectFace = async (parent, input): Promise<any> => {
embeddingRes = parent.config.face.embedding.enabled ? await embedding.predict(face, parent.config) : [];
parent.perf.embedding = Math.trunc(now() - timeStamp);
}
parent.analyze('End Emotion:');
parent.analyze('End Embedding:');
// run emotion, inherits face from blazeface
parent.analyze('Start Description:');
if (parent.config.async) {
descRes = parent.config.face.description.enabled ? faceres.predict(face, parent.config) : [];
} else {
parent.state = 'run:description';
timeStamp = now();
descRes = parent.config.face.description.enabled ? await faceres.predict(face.image, parent.config) : [];
parent.perf.embedding = Math.trunc(now() - timeStamp);
}
parent.analyze('End Description:');
// if async wait for results
if (parent.config.async) {
[ageRes, genderRes, emotionRes, embeddingRes] = await Promise.all([ageRes, genderRes, emotionRes, embeddingRes]);
[ageRes, genderRes, emotionRes, embeddingRes, descRes] = await Promise.all([ageRes, genderRes, emotionRes, embeddingRes, descRes]);
}
parent.analyze('Finish Face:');
@ -134,11 +148,11 @@ export const detectFace = async (parent, input): Promise<any> => {
// combine results
faceRes.push({
...face,
age: ageRes.age,
gender: genderRes.gender,
genderConfidence: genderRes.confidence,
age: descRes.age || ageRes.age,
gender: descRes.gender || genderRes.gender,
genderConfidence: descRes.genderConfidence || genderRes.confidence,
embedding: descRes.descriptor || embeddingRes,
emotion: emotionRes,
embedding: embeddingRes,
iris: (irisSize !== 0) ? Math.trunc(irisSize) / 100 : 0,
angle,
tensor: parent.config.face.detector.return ? face.image?.squeeze() : null,

146
src/faceres/faceres.ts Normal file
View File

@ -0,0 +1,146 @@
import { log } from '../helpers';
import * as tf from '../../dist/tfjs.esm.js';
import * as profile from '../profile';
let model;
let last = { age: 0 };
let skipped = Number.MAX_SAFE_INTEGER;
type Tensor = typeof tf.Tensor;
type DB = Array<{ name: string, source: string, embedding: number[] }>;
export async function load(config) {
if (!model) {
model = await tf.loadGraphModel(config.face.description.modelPath);
if (config.debug) log(`load model: ${config.face.description.modelPath.match(/\/(.*)\./)[1]}`);
}
return model;
}
export function similarity(embedding1, embedding2, order = 2): number {
if (!embedding1 || !embedding2) return 0;
if (embedding1?.length === 0 || embedding2?.length === 0) return 0;
if (embedding1?.length !== embedding2?.length) return 0;
// general minkowski distance, euclidean distance is limited case where order is 2
const distance = 4.0 * embedding1
.map((val, i) => (Math.abs(embedding1[i] - embedding2[i]) ** order)) // distance squared
.reduce((sum, now) => (sum + now), 0) // sum all distances
** (1 / order); // get root of
const res = Math.max(0, 100 - distance) / 100.0;
return res;
}
export function match(embedding: Array<number>, db: DB, threshold = 0) {
let best = { similarity: 0, name: '', source: '', embedding: [] as number[] };
if (!embedding || !db || !Array.isArray(embedding) || !Array.isArray(db)) return best;
for (const f of db) {
if (f.embedding && f.name) {
const perc = similarity(embedding, f.embedding);
if (perc > threshold && perc > best.similarity) best = { ...f, similarity: perc };
}
}
return best;
}
export function enhance(input): Tensor {
const image = tf.tidy(() => {
// input received from detector is already normalized to 0..1
// input is also assumed to be straightened
const tensor = input.image || input.tensor || input;
/*
// do a tight crop of image and resize it to fit the model
const box = [[0.05, 0.15, 0.85, 0.85]]; // empyrical values for top, left, bottom, right
if (!(tensor instanceof tf.Tensor)) return null;
const crop = (tensor.shape.length === 3)
? tf.image.cropAndResize(tf.expandDims(tensor, 0), box, [0], [model.inputs[0].shape[2], model.inputs[0].shape[1]]) // add batch dimension if missing
: tf.image.cropAndResize(tensor, box, [0], [model.inputs[0].shape[2], model.inputs[0].shape[1]]);
*/
const crop = tf.image.resizeBilinear(tensor, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false); // just resize to fit the embedding model
/*
// convert to black&white to avoid colorization impact
const rgb = [0.2989, 0.5870, 0.1140]; // factors for red/green/blue colors when converting to grayscale: https://www.mathworks.com/help/matlab/ref/rgb2gray.html
const [red, green, blue] = tf.split(crop, 3, 3);
const redNorm = tf.mul(red, rgb[0]);
const greenNorm = tf.mul(green, rgb[1]);
const blueNorm = tf.mul(blue, rgb[2]);
const grayscale = tf.addN([redNorm, greenNorm, blueNorm]);
const merge = tf.stack([grayscale, grayscale, grayscale], 3).squeeze(4);
*/
/*
// optional increase image contrast
// or do it per-channel so mean is done on each channel
// or do it based on histogram
const mean = merge.mean();
const factor = 5;
const contrast = merge.sub(mean).mul(factor).add(mean);
*/
/*
// normalize brightness from 0..1
const darken = crop.sub(crop.min());
const lighten = darken.div(darken.max());
*/
const norm = crop.mul(255);
return norm;
});
return image;
}
export async function predict(image, config) {
if (!model) return null;
if ((skipped < config.face.description.skipFrames) && config.videoOptimized && last.age && (last.age > 0)) {
skipped++;
return last;
}
if (config.videoOptimized) skipped = 0;
else skipped = Number.MAX_SAFE_INTEGER;
return new Promise(async (resolve) => {
// const resize = tf.image.resizeBilinear(image, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false);
// const enhanced = tf.mul(resize, [255.0]);
// tf.dispose(resize);
const enhanced = enhance(image);
let resT;
const obj = {
age: <number>0,
gender: <string>'unknown',
genderConfidence: <number>0,
descriptor: <number[]>[] };
if (!config.profile) {
if (config.face.description.enabled) resT = await model.predict(enhanced);
} else {
const profileAge = config.face.description.enabled ? await tf.profile(() => model.predict(enhanced)) : {};
resT = profileAge.result.clone();
profileAge.result.dispose();
profile.run('age', profileAge);
}
tf.dispose(enhanced);
if (resT) {
tf.tidy(() => {
const gender = resT.find((t) => t.shape[1] === 1).dataSync();
const confidence = Math.trunc(200 * Math.abs((gender[0] - 0.5))) / 100;
if (confidence > config.face.gender.minConfidence) {
obj.gender = gender[0] <= 0.5 ? 'female' : 'male';
obj.genderConfidence = Math.min(0.99, confidence);
}
const age = resT.find((t) => t.shape[1] === 100).argMax(1).dataSync()[0];
const all = resT.find((t) => t.shape[1] === 100).dataSync();
obj.age = Math.round(all[age - 1] > all[age + 1] ? 10 * age - 100 * all[age - 1] : 10 * age + 100 * all[age + 1]) / 10;
const desc = resT.find((t) => t.shape[1] === 1024);
// const reshape = desc.reshape([128, 8]);
// const reduce = reshape.logSumExp(1); // reduce 2nd dimension by calculating logSumExp on it
obj.descriptor = [...desc.dataSync()];
});
resT.forEach((t) => tf.dispose(t));
}
last = obj;
resolve(obj);
});
}

View File

@ -59,24 +59,39 @@ export async function predict(image, config) {
enhance.dispose();
if (genderT) {
const data = genderT.dataSync();
if (alternative) {
// returns two values 0..1, bigger one is prediction
if (data[0] > config.face.gender.minConfidence || data[1] > config.face.gender.minConfidence) {
obj.gender = data[0] > data[1] ? 'female' : 'male';
obj.confidence = data[0] > data[1] ? (Math.trunc(100 * data[0]) / 100) : (Math.trunc(100 * data[1]) / 100);
if (!Array.isArray(genderT)) {
const data = genderT.dataSync();
if (alternative) {
// returns two values 0..1, bigger one is prediction
if (data[0] > config.face.gender.minConfidence || data[1] > config.face.gender.minConfidence) {
obj.gender = data[0] > data[1] ? 'female' : 'male';
obj.confidence = data[0] > data[1] ? (Math.trunc(100 * data[0]) / 100) : (Math.trunc(100 * data[1]) / 100);
}
} else {
// returns one value 0..1, .5 is prediction threshold
const confidence = Math.trunc(200 * Math.abs((data[0] - 0.5))) / 100;
if (confidence > config.face.gender.minConfidence) {
obj.gender = data[0] <= 0.5 ? 'female' : 'male';
obj.confidence = Math.min(0.99, confidence);
}
}
genderT.dispose();
} else {
// returns one value 0..1, .5 is prediction threshold
const confidence = Math.trunc(200 * Math.abs((data[0] - 0.5))) / 100;
const gender = genderT[0].dataSync();
const confidence = Math.trunc(200 * Math.abs((gender[0] - 0.5))) / 100;
if (confidence > config.face.gender.minConfidence) {
obj.gender = data[0] <= 0.5 ? 'female' : 'male';
obj.gender = gender[0] <= 0.5 ? 'female' : 'male';
obj.confidence = Math.min(0.99, confidence);
}
/*
let age = genderT[1].argMax(1).dataSync()[0];
const all = genderT[1].dataSync();
age = Math.round(all[age - 1] > all[age + 1] ? 10 * age - 100 * all[age - 1] : 10 * age + 100 * all[age + 1]) / 10;
const descriptor = genderT[1].dataSync();
*/
genderT.forEach((t) => tf.dispose(t));
}
}
genderT.dispose();
last = obj;
resolve(obj);
});

View File

@ -6,6 +6,7 @@ import * as faceall from './faceall';
import * as facemesh from './blazeface/facemesh';
import * as age from './age/age';
import * as gender from './gender/gender';
import * as faceres from './faceres/faceres';
import * as emotion from './emotion/emotion';
import * as embedding from './embedding/embedding';
import * as posenet from './posenet/posenet';
@ -71,6 +72,7 @@ export class Human {
emotion: Model | null,
embedding: Model | null,
nanodet: Model | null,
faceres: Model | null,
};
classes: {
facemesh: typeof facemesh;
@ -80,6 +82,7 @@ export class Human {
body: typeof posenet | typeof blazepose;
hand: typeof handpose;
nanodet: typeof nanodet;
faceres: typeof faceres;
};
sysinfo: { platform: string, agent: string };
perf: any;
@ -112,6 +115,7 @@ export class Human {
emotion: null,
embedding: null,
nanodet: null,
faceres: null,
};
// export access to image processing
// @ts-ignore
@ -122,6 +126,7 @@ export class Human {
age,
gender,
emotion,
faceres,
body: this.config.body.modelPath.includes('posenet') ? posenet : blazepose,
hand: handpose,
nanodet,
@ -160,19 +165,20 @@ export class Human {
return null;
}
simmilarity(embedding1: Array<number>, embedding2: Array<number>): number {
if (this.config.face.embedding.enabled) return embedding.simmilarity(embedding1, embedding2);
similarity(embedding1: Array<number>, embedding2: Array<number>): number {
if (this.config.face.description.enabled) return faceres.similarity(embedding1, embedding2);
if (this.config.face.embedding.enabled) return embedding.similarity(embedding1, embedding2);
return 0;
}
// eslint-disable-next-line class-methods-use-this
enhance(input: Tensor): Tensor | null {
return embedding.enhance(input);
return faceres.enhance(input);
}
// eslint-disable-next-line class-methods-use-this
match(faceEmbedding: Array<number>, db: Array<{ name: string, source: string, embedding: number[] }>, threshold = 0): { name: string, source: string, simmilarity: number, embedding: number[] } {
return embedding.match(faceEmbedding, db, threshold);
match(faceEmbedding: Array<number>, db: Array<{ name: string, source: string, embedding: number[] }>, threshold = 0): { name: string, source: string, similarity: number, embedding: number[] } {
return faceres.match(faceEmbedding, db, threshold);
}
// preload models, not explicitly required as it's done automatically on first use
@ -204,6 +210,7 @@ export class Human {
this.models.posenet,
this.models.blazepose,
this.models.nanodet,
this.models.faceres,
] = await Promise.all([
this.models.face || (this.config.face.enabled ? facemesh.load(this.config) : null),
this.models.age || ((this.config.face.enabled && this.config.face.age.enabled) ? age.load(this.config) : null),
@ -214,6 +221,7 @@ export class Human {
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes('posenet') ? posenet.load(this.config) : null),
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes('blazepose') ? blazepose.load(this.config) : null),
this.models.nanodet || (this.config.object.enabled ? nanodet.load(this.config) : null),
this.models.faceres || ((this.config.face.enabled && this.config.face.description.enabled) ? faceres.load(this.config) : null),
]);
} else {
if (this.config.face.enabled && !this.models.face) this.models.face = await facemesh.load(this.config);
@ -225,6 +233,7 @@ export class Human {
if (this.config.body.enabled && !this.models.posenet && this.config.body.modelPath.includes('posenet')) this.models.posenet = await posenet.load(this.config);
if (this.config.body.enabled && !this.models.blazepose && this.config.body.modelPath.includes('blazepose')) this.models.blazepose = await blazepose.load(this.config);
if (this.config.object.enabled && !this.models.nanodet) this.models.nanodet = await nanodet.load(this.config);
if (this.config.face.enabled && this.config.face.description.enabled && !this.models.faceres) this.models.faceres = await faceres.load(this.config);
}
if (this.#firstRun) {

File diff suppressed because one or more lines are too long

View File

@ -117,7 +117,7 @@
<li class="tsd-kind-method tsd-parent-kind-class"><a href="human.html#load" class="tsd-kind-icon">load</a></li>
<li class="tsd-kind-method tsd-parent-kind-class"><a href="human.html#match" class="tsd-kind-icon">match</a></li>
<li class="tsd-kind-method tsd-parent-kind-class"><a href="human.html#profiledata" class="tsd-kind-icon">profile<wbr>Data</a></li>
<li class="tsd-kind-method tsd-parent-kind-class"><a href="human.html#simmilarity" class="tsd-kind-icon">simmilarity</a></li>
<li class="tsd-kind-method tsd-parent-kind-class"><a href="human.html#similarity" class="tsd-kind-icon">similarity</a></li>
<li class="tsd-kind-method tsd-parent-kind-class"><a href="human.html#warmup" class="tsd-kind-icon">warmup</a></li>
</ul>
</section>
@ -152,7 +152,7 @@
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class">
<a name="classes" class="tsd-anchor"></a>
<h3>classes</h3>
<div class="tsd-signature tsd-kind-icon">classes<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">{ </span>age<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>body<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>emotion<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>facemesh<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>gender<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>hand<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>nanodet<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol"> }</span></div>
<div class="tsd-signature tsd-kind-icon">classes<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">{ </span>age<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>body<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>emotion<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>facemesh<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>faceres<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>gender<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>hand<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol">; </span>nanodet<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span><span class="tsd-signature-symbol"> }</span></div>
<aside class="tsd-sources">
</aside>
<div class="tsd-type-declaration">
@ -170,6 +170,9 @@
<li class="tsd-parameter">
<h5>facemesh<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span></h5>
</li>
<li class="tsd-parameter">
<h5>faceres<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span></h5>
</li>
<li class="tsd-parameter">
<h5>gender<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">__module</span></h5>
</li>
@ -422,7 +425,7 @@
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class">
<a name="models" class="tsd-anchor"></a>
<h3>models</h3>
<div class="tsd-signature tsd-kind-icon">models<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">{ </span>age<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>blazepose<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>embedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>emotion<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>face<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">MediaPipeFaceMesh</span><span class="tsd-signature-symbol">; </span>gender<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>handpose<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">HandPose</span><span class="tsd-signature-symbol">; </span>iris<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>nanodet<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>posenet<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">PoseNet</span><span class="tsd-signature-symbol"> }</span></div>
<div class="tsd-signature tsd-kind-icon">models<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">{ </span>age<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>blazepose<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>embedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>emotion<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>face<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">MediaPipeFaceMesh</span><span class="tsd-signature-symbol">; </span>faceres<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>gender<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>handpose<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">HandPose</span><span class="tsd-signature-symbol">; </span>iris<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>nanodet<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">; </span>posenet<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">PoseNet</span><span class="tsd-signature-symbol"> }</span></div>
<aside class="tsd-sources">
</aside>
<div class="tsd-type-declaration">
@ -443,6 +446,9 @@
<li class="tsd-parameter">
<h5>face<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">MediaPipeFaceMesh</span></h5>
</li>
<li class="tsd-parameter">
<h5>faceres<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span></h5>
</li>
<li class="tsd-parameter">
<h5>gender<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">null</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">Object</span></h5>
</li>
@ -577,7 +583,7 @@
<a name="match" class="tsd-anchor"></a>
<h3>match</h3>
<ul class="tsd-signatures tsd-kind-method tsd-parent-kind-class">
<li class="tsd-signature tsd-kind-icon">match<span class="tsd-signature-symbol">(</span>faceEmbedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span>, db<span class="tsd-signature-symbol">: </span><span class="tsd-signature-symbol">{ </span>embedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">; </span>name<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">; </span>source<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> }</span><span class="tsd-signature-symbol">[]</span>, threshold<span class="tsd-signature-symbol">?: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-symbol">{ </span>embedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">; </span>name<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">; </span>simmilarity<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">; </span>source<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> }</span></li>
<li class="tsd-signature tsd-kind-icon">match<span class="tsd-signature-symbol">(</span>faceEmbedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span>, db<span class="tsd-signature-symbol">: </span><span class="tsd-signature-symbol">{ </span>embedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">; </span>name<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">; </span>source<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> }</span><span class="tsd-signature-symbol">[]</span>, threshold<span class="tsd-signature-symbol">?: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-symbol">{ </span>embedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">; </span>name<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">; </span>similarity<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">; </span>source<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> }</span></li>
</ul>
<ul class="tsd-descriptions">
<li class="tsd-description">
@ -595,7 +601,7 @@
<h5>threshold: <span class="tsd-signature-type">number</span><span class="tsd-signature-symbol"> = 0</span></h5>
</li>
</ul>
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-symbol">{ </span>embedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">; </span>name<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">; </span>simmilarity<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">; </span>source<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> }</span></h4>
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-symbol">{ </span>embedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">; </span>name<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">; </span>similarity<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">; </span>source<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol"> }</span></h4>
<ul class="tsd-parameters">
<li class="tsd-parameter">
<h5>embedding<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span></h5>
@ -604,7 +610,7 @@
<h5>name<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span></h5>
</li>
<li class="tsd-parameter">
<h5>simmilarity<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span></h5>
<h5>similarity<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span></h5>
</li>
<li class="tsd-parameter">
<h5>source<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">string</span></h5>
@ -628,10 +634,10 @@
</ul>
</section>
<section class="tsd-panel tsd-member tsd-kind-method tsd-parent-kind-class">
<a name="simmilarity" class="tsd-anchor"></a>
<h3>simmilarity</h3>
<a name="similarity" class="tsd-anchor"></a>
<h3>similarity</h3>
<ul class="tsd-signatures tsd-kind-method tsd-parent-kind-class">
<li class="tsd-signature tsd-kind-icon">simmilarity<span class="tsd-signature-symbol">(</span>embedding1<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span>, embedding2<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span></li>
<li class="tsd-signature tsd-kind-icon">similarity<span class="tsd-signature-symbol">(</span>embedding1<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span>, embedding2<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span></li>
</ul>
<ul class="tsd-descriptions">
<li class="tsd-description">
@ -739,7 +745,7 @@
<a href="human.html#profiledata" class="tsd-kind-icon">profile<wbr>Data</a>
</li>
<li class=" tsd-kind-method tsd-parent-kind-class">
<a href="human.html#simmilarity" class="tsd-kind-icon">simmilarity</a>
<a href="human.html#similarity" class="tsd-kind-icon">similarity</a>
</li>
<li class=" tsd-kind-method tsd-parent-kind-class">
<a href="human.html#warmup" class="tsd-kind-icon">warmup</a>

File diff suppressed because one or more lines are too long

5
types/config.d.ts vendored
View File

@ -56,6 +56,11 @@ export interface Config {
enabled: boolean;
modelPath: string;
};
description: {
enabled: boolean;
modelPath: string;
skipFrames: number;
};
age: {
enabled: boolean;
modelPath: string;

View File

@ -6,9 +6,9 @@ declare type DB = Array<{
embedding: number[];
}>;
export declare function load(config: any): Promise<any>;
export declare function simmilarity(embedding1: any, embedding2: any, order?: number): number;
export declare function similarity(embedding1: any, embedding2: any, order?: number): number;
export declare function match(embedding: Array<number>, db: DB, threshold?: number): {
simmilarity: number;
similarity: number;
name: string;
source: string;
embedding: number[];

18
types/faceres/faceres.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
import * as tf from '../../dist/tfjs.esm.js';
declare type Tensor = typeof tf.Tensor;
declare type DB = Array<{
name: string;
source: string;
embedding: number[];
}>;
export declare function load(config: any): Promise<any>;
export declare function similarity(embedding1: any, embedding2: any, order?: number): number;
export declare function match(embedding: Array<number>, db: DB, threshold?: number): {
similarity: number;
name: string;
source: string;
embedding: number[];
};
export declare function enhance(input: any): Tensor;
export declare function predict(image: any, config: any): Promise<unknown>;
export {};

7
types/human.d.ts vendored
View File

@ -2,6 +2,7 @@ import * as tf from '../dist/tfjs.esm.js';
import * as facemesh from './blazeface/facemesh';
import * as age from './age/age';
import * as gender from './gender/gender';
import * as faceres from './faceres/faceres';
import * as emotion from './emotion/emotion';
import * as posenet from './posenet/posenet';
import * as handpose from './handpose/handpose';
@ -63,6 +64,7 @@ export declare class Human {
emotion: Model | null;
embedding: Model | null;
nanodet: Model | null;
faceres: Model | null;
};
classes: {
facemesh: typeof facemesh;
@ -72,6 +74,7 @@ export declare class Human {
body: typeof posenet | typeof blazepose;
hand: typeof handpose;
nanodet: typeof nanodet;
faceres: typeof faceres;
};
sysinfo: {
platform: string;
@ -90,7 +93,7 @@ export declare class Human {
} | {};
/** @hidden */
analyze: (...msg: any[]) => void;
simmilarity(embedding1: Array<number>, embedding2: Array<number>): number;
similarity(embedding1: Array<number>, embedding2: Array<number>): number;
enhance(input: Tensor): Tensor | null;
match(faceEmbedding: Array<number>, db: Array<{
name: string;
@ -99,7 +102,7 @@ export declare class Human {
}>, threshold?: number): {
name: string;
source: string;
simmilarity: number;
similarity: number;
embedding: number[];
};
load(userConfig?: Config | Object): Promise<void>;