mirror of https://github.com/vladmandic/human
add test for face descriptors
parent
fb10a529ab
commit
ca1bc638f8
|
@ -1,2 +1,2 @@
|
|||
node_modules
|
||||
alternative
|
||||
private
|
||||
|
|
1
TODO.md
1
TODO.md
|
@ -2,6 +2,7 @@
|
|||
|
||||
- Strong typing
|
||||
- Automated testing
|
||||
- Guard against corrupt input
|
||||
- Improve face embedding
|
||||
- Build Face embedding database
|
||||
- Dynamic sample processing
|
||||
|
|
|
@ -85,6 +85,7 @@ export default {
|
|||
scoreThreshold: 0.2, // threshold for deciding when to remove boxes based on score
|
||||
// in non-maximum suppression,
|
||||
// this is applied on detection objects only and before minConfidence
|
||||
return: true, // return extracted face as tensor
|
||||
},
|
||||
|
||||
mesh: {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Human</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type">
|
||||
<meta content="text/html">
|
||||
<meta name="description" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.6, minimum-scale=0.3, maximum-scale=3.0, shrink-to-fit=yes, user-scalable=yes">
|
||||
<meta name="theme-color" content="#000000"/>
|
||||
<meta name="application-name" content="Human">
|
||||
<meta name="msapplication-tooltip" content="Human: AI-powered 3D Human Detection">
|
||||
<link rel="manifest" href="./manifest.webmanifest">
|
||||
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
|
||||
<link rel="apple-touch-icon" href="../assets/icon.png">
|
||||
<script src="./embedding.js" type="module"></script>
|
||||
<style>
|
||||
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../assets/lato-light.woff2') }
|
||||
html { font-family: 'Lato', 'Segoe UI'; font-size: 20px; font-variant: small-caps; }
|
||||
body { margin: 0; background: black; color: white; overflow-x: hidden; scrollbar-width: none; }
|
||||
body::-webkit-scrollbar { display: none; }
|
||||
img { object-fit: contain; }
|
||||
.face { width: 200px; height: 200px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<br>Sample Images:
|
||||
<div id="images"></div>
|
||||
<br>Extracted Faces - click on a face to sort by simmilarity:<br>
|
||||
<div id="faces"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,94 @@
|
|||
import Human from '../dist/human.esm.js';
|
||||
|
||||
const userConfig = {
|
||||
backend: 'wasm',
|
||||
async: false,
|
||||
warmup: 'none',
|
||||
debug: true,
|
||||
filter: false,
|
||||
videoOptimized: false,
|
||||
face: {
|
||||
enabled: true,
|
||||
detector: { rotation: true },
|
||||
mesh: { enabled: true },
|
||||
embedding: { enabled: true, modelPath: '../models/mobilefacenet.json' },
|
||||
iris: { enabled: false },
|
||||
age: { enabled: false },
|
||||
gender: { enabled: false },
|
||||
emotion: { enabled: false },
|
||||
},
|
||||
hand: { enabled: false },
|
||||
gesture: { enabled: false },
|
||||
body: { enabled: false },
|
||||
};
|
||||
const human = new Human(userConfig);
|
||||
const samples = ['../assets/sample-me.jpg', '../assets/sample6.jpg', '../assets/sample1.jpg', '../assets/sample4.jpg', '../assets/sample5.jpg', '../assets/sample3.jpg', '../assets/sample2.jpg',
|
||||
'../private/me (1).jpg', '../private/me (2).jpg', '../private/me (3).jpg', '../private/me (4).jpg', '../private/me (5).jpg', '../private/me (6).jpg', '../private/me (7).jpg', '../private/me (8).jpg',
|
||||
'../private/me (9).jpg', '../private/me (10).jpg', '../private/me (11).jpg', '../private/me (12).jpg', '../private/me (13).jpg'];
|
||||
const all = [];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async function analyze(face) {
|
||||
log('Face:', face);
|
||||
const canvases = document.getElementsByClassName('face');
|
||||
for (const canvas of canvases) {
|
||||
const res = human.simmilarity(face.embedding, all[canvas.tag.sample][canvas.tag.face].embedding);
|
||||
canvas.title = res;
|
||||
await human.tf.browser.toPixels(all[canvas.tag.sample][canvas.tag.face].tensor, canvas);
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.font = 'small-caps 1rem "Lato"';
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
|
||||
ctx.fillText(`${(100 * res).toFixed(1)}%`, 3, 19);
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
||||
ctx.fillText(`${(100 * res).toFixed(1)}%`, 4, 20);
|
||||
}
|
||||
const sorted = document.getElementById('faces');
|
||||
[...sorted.children]
|
||||
.sort((a, b) => parseFloat(b.title) - parseFloat(a.title))
|
||||
.forEach((canvas) => sorted.appendChild(canvas));
|
||||
}
|
||||
|
||||
async function faces(index, res) {
|
||||
all[index] = res.face;
|
||||
for (const i in res.face) {
|
||||
// log(res.face[i]);
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.tag = { sample: index, face: i };
|
||||
canvas.width = 200;
|
||||
canvas.height = 200;
|
||||
canvas.className = 'face';
|
||||
canvas.addEventListener('click', (evt) => {
|
||||
log('Select:', evt.target.tag.sample, evt.target.tag.face);
|
||||
analyze(all[evt.target.tag.sample][evt.target.tag.face]);
|
||||
});
|
||||
human.tf.browser.toPixels(res.face[i].tensor, canvas);
|
||||
document.getElementById('faces').appendChild(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
async function add(index) {
|
||||
log('Add:', samples[index]);
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image(100, 100);
|
||||
img.onload = () => {
|
||||
human.detect(img).then((res) => faces(index, res));
|
||||
document.getElementById('images').appendChild(img);
|
||||
resolve(true);
|
||||
};
|
||||
img.title = samples[index];
|
||||
img.src = samples[index];
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await human.load();
|
||||
for (const i in samples) await add(i);
|
||||
}
|
||||
|
||||
window.onload = main;
|
|
@ -79445,32 +79445,39 @@ function simmilarity(embedding1, embedding22, order = 2) {
|
|||
return 0;
|
||||
if ((embedding1 == null ? void 0 : embedding1.length) !== (embedding22 == null ? void 0 : embedding22.length))
|
||||
return 0;
|
||||
const distance = 50 * embedding1.map((val, i) => val - embedding22[i]).reduce((dist, diff) => dist + diff ** order, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - (isNaN(distance) ? 1 : distance))) / 1e3;
|
||||
console.log(distance, res);
|
||||
const distance = embedding1.map((val, i) => Math.abs(embedding1[i] - embedding22[i]) ** order).reduce((sum6, now3) => sum6 + now3, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - 20 * distance)) / 1e3;
|
||||
return res;
|
||||
}
|
||||
async function predict4(image3, config3) {
|
||||
async function predict4(input2, config3) {
|
||||
if (!model5)
|
||||
return null;
|
||||
return new Promise(async (resolve) => {
|
||||
const resize = image.resizeBilinear(image3, [model5.inputs[0].shape[2], model5.inputs[0].shape[1]], false);
|
||||
const norm2 = resize.sub(0.5);
|
||||
resize.dispose();
|
||||
const image3 = tidy(() => {
|
||||
const data3 = image.resizeBilinear(input2, [model5.inputs[0].shape[2], model5.inputs[0].shape[1]], false);
|
||||
const norm2 = data3.sub(data3.mean());
|
||||
return norm2;
|
||||
});
|
||||
let data2 = [];
|
||||
if (config3.face.embedding.enabled) {
|
||||
if (!config3.profile) {
|
||||
const res = await model5.predict({img_inputs: norm2});
|
||||
data2 = [...res.dataSync()];
|
||||
const res = await model5.predict({img_inputs: image3});
|
||||
const scaled = tidy(() => {
|
||||
const l23 = res.norm("euclidean");
|
||||
const scale2 = res.div(l23);
|
||||
return scale2;
|
||||
});
|
||||
data2 = [...scaled.dataSync()];
|
||||
dispose(scaled);
|
||||
dispose(res);
|
||||
} else {
|
||||
const profileData = await profile(() => model5.predict({img_inputs: norm2}));
|
||||
const profileData = await profile(() => model5.predict({img_inputs: image3}));
|
||||
data2 = [...profileData.result.dataSync()];
|
||||
profileData.result.dispose();
|
||||
run("emotion", profileData);
|
||||
}
|
||||
}
|
||||
norm2.dispose();
|
||||
image3.dispose();
|
||||
resolve(data2);
|
||||
});
|
||||
}
|
||||
|
@ -99022,7 +99029,8 @@ var config_default = {
|
|||
skipInitial: false,
|
||||
minConfidence: 0.2,
|
||||
iouThreshold: 0.1,
|
||||
scoreThreshold: 0.2
|
||||
scoreThreshold: 0.2,
|
||||
return: true
|
||||
},
|
||||
mesh: {
|
||||
enabled: true,
|
||||
|
@ -100505,7 +100513,8 @@ var Human = class {
|
|||
emotion: emotionRes,
|
||||
embedding: embeddingRes,
|
||||
iris: irisSize !== 0 ? Math.trunc(irisSize) / 100 : 0,
|
||||
angle
|
||||
angle,
|
||||
tensor: this.config.face.detector.return ? face4.image.squeeze() : null
|
||||
});
|
||||
(_f = face4.image) == null ? void 0 : _f.dispose();
|
||||
__privateGet(this, _analyze).call(this, "End Face");
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -4357,32 +4357,39 @@ function simmilarity(embedding1, embedding2, order = 2) {
|
|||
return 0;
|
||||
if ((embedding1 == null ? void 0 : embedding1.length) !== (embedding2 == null ? void 0 : embedding2.length))
|
||||
return 0;
|
||||
const distance = 50 * embedding1.map((val, i) => val - embedding2[i]).reduce((dist, diff) => dist + diff ** order, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - (isNaN(distance) ? 1 : distance))) / 1e3;
|
||||
console.log(distance, res);
|
||||
const distance = embedding1.map((val, i) => Math.abs(embedding1[i] - embedding2[i]) ** order).reduce((sum, now2) => sum + now2, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - 20 * distance)) / 1e3;
|
||||
return res;
|
||||
}
|
||||
async function predict4(image13, config3) {
|
||||
async function predict4(input, config3) {
|
||||
if (!model4)
|
||||
return null;
|
||||
return new Promise(async (resolve) => {
|
||||
const resize = tf8.image.resizeBilinear(image13, [model4.inputs[0].shape[2], model4.inputs[0].shape[1]], false);
|
||||
const norm = resize.sub(0.5);
|
||||
resize.dispose();
|
||||
const image13 = tf8.tidy(() => {
|
||||
const data4 = tf8.image.resizeBilinear(input, [model4.inputs[0].shape[2], model4.inputs[0].shape[1]], false);
|
||||
const norm = data4.sub(data4.mean());
|
||||
return norm;
|
||||
});
|
||||
let data3 = [];
|
||||
if (config3.face.embedding.enabled) {
|
||||
if (!config3.profile) {
|
||||
const res = await model4.predict({img_inputs: norm});
|
||||
data3 = [...res.dataSync()];
|
||||
const res = await model4.predict({img_inputs: image13});
|
||||
const scaled = tf8.tidy(() => {
|
||||
const l2 = res.norm("euclidean");
|
||||
const scale = res.div(l2);
|
||||
return scale;
|
||||
});
|
||||
data3 = [...scaled.dataSync()];
|
||||
tf8.dispose(scaled);
|
||||
tf8.dispose(res);
|
||||
} else {
|
||||
const profileData = await tf8.profile(() => model4.predict({img_inputs: norm}));
|
||||
const profileData = await tf8.profile(() => model4.predict({img_inputs: image13}));
|
||||
data3 = [...profileData.result.dataSync()];
|
||||
profileData.result.dispose();
|
||||
run("emotion", profileData);
|
||||
}
|
||||
}
|
||||
norm.dispose();
|
||||
image13.dispose();
|
||||
resolve(data3);
|
||||
});
|
||||
}
|
||||
|
@ -23949,7 +23956,8 @@ var config_default = {
|
|||
skipInitial: false,
|
||||
minConfidence: 0.2,
|
||||
iouThreshold: 0.1,
|
||||
scoreThreshold: 0.2
|
||||
scoreThreshold: 0.2,
|
||||
return: true
|
||||
},
|
||||
mesh: {
|
||||
enabled: true,
|
||||
|
@ -25432,7 +25440,8 @@ var Human = class {
|
|||
emotion: emotionRes,
|
||||
embedding: embeddingRes,
|
||||
iris: irisSize !== 0 ? Math.trunc(irisSize) / 100 : 0,
|
||||
angle
|
||||
angle,
|
||||
tensor: this.config.face.detector.return ? face4.image.squeeze() : null
|
||||
});
|
||||
(_f = face4.image) == null ? void 0 : _f.dispose();
|
||||
__privateGet(this, _analyze).call(this, "End Face");
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -79445,32 +79445,39 @@ function simmilarity(embedding1, embedding22, order = 2) {
|
|||
return 0;
|
||||
if ((embedding1 == null ? void 0 : embedding1.length) !== (embedding22 == null ? void 0 : embedding22.length))
|
||||
return 0;
|
||||
const distance = 50 * embedding1.map((val, i) => val - embedding22[i]).reduce((dist, diff) => dist + diff ** order, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - (isNaN(distance) ? 1 : distance))) / 1e3;
|
||||
console.log(distance, res);
|
||||
const distance = embedding1.map((val, i) => Math.abs(embedding1[i] - embedding22[i]) ** order).reduce((sum6, now3) => sum6 + now3, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - 20 * distance)) / 1e3;
|
||||
return res;
|
||||
}
|
||||
async function predict4(image3, config3) {
|
||||
async function predict4(input2, config3) {
|
||||
if (!model5)
|
||||
return null;
|
||||
return new Promise(async (resolve) => {
|
||||
const resize = image.resizeBilinear(image3, [model5.inputs[0].shape[2], model5.inputs[0].shape[1]], false);
|
||||
const norm2 = resize.sub(0.5);
|
||||
resize.dispose();
|
||||
const image3 = tidy(() => {
|
||||
const data3 = image.resizeBilinear(input2, [model5.inputs[0].shape[2], model5.inputs[0].shape[1]], false);
|
||||
const norm2 = data3.sub(data3.mean());
|
||||
return norm2;
|
||||
});
|
||||
let data2 = [];
|
||||
if (config3.face.embedding.enabled) {
|
||||
if (!config3.profile) {
|
||||
const res = await model5.predict({img_inputs: norm2});
|
||||
data2 = [...res.dataSync()];
|
||||
const res = await model5.predict({img_inputs: image3});
|
||||
const scaled = tidy(() => {
|
||||
const l23 = res.norm("euclidean");
|
||||
const scale2 = res.div(l23);
|
||||
return scale2;
|
||||
});
|
||||
data2 = [...scaled.dataSync()];
|
||||
dispose(scaled);
|
||||
dispose(res);
|
||||
} else {
|
||||
const profileData = await profile(() => model5.predict({img_inputs: norm2}));
|
||||
const profileData = await profile(() => model5.predict({img_inputs: image3}));
|
||||
data2 = [...profileData.result.dataSync()];
|
||||
profileData.result.dispose();
|
||||
run("emotion", profileData);
|
||||
}
|
||||
}
|
||||
norm2.dispose();
|
||||
image3.dispose();
|
||||
resolve(data2);
|
||||
});
|
||||
}
|
||||
|
@ -99022,7 +99029,8 @@ var config_default = {
|
|||
skipInitial: false,
|
||||
minConfidence: 0.2,
|
||||
iouThreshold: 0.1,
|
||||
scoreThreshold: 0.2
|
||||
scoreThreshold: 0.2,
|
||||
return: true
|
||||
},
|
||||
mesh: {
|
||||
enabled: true,
|
||||
|
@ -100505,7 +100513,8 @@ var Human = class {
|
|||
emotion: emotionRes,
|
||||
embedding: embeddingRes,
|
||||
iris: irisSize !== 0 ? Math.trunc(irisSize) / 100 : 0,
|
||||
angle
|
||||
angle,
|
||||
tensor: this.config.face.detector.return ? face4.image.squeeze() : null
|
||||
});
|
||||
(_f = face4.image) == null ? void 0 : _f.dispose();
|
||||
__privateGet(this, _analyze).call(this, "End Face");
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -79452,32 +79452,39 @@ return a / b;`;
|
|||
return 0;
|
||||
if ((embedding1 == null ? void 0 : embedding1.length) !== (embedding22 == null ? void 0 : embedding22.length))
|
||||
return 0;
|
||||
const distance = 50 * embedding1.map((val, i) => val - embedding22[i]).reduce((dist, diff) => dist + diff ** order, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - (isNaN(distance) ? 1 : distance))) / 1e3;
|
||||
console.log(distance, res);
|
||||
const distance = embedding1.map((val, i) => Math.abs(embedding1[i] - embedding22[i]) ** order).reduce((sum6, now3) => sum6 + now3, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - 20 * distance)) / 1e3;
|
||||
return res;
|
||||
}
|
||||
async function predict4(image3, config3) {
|
||||
async function predict4(input2, config3) {
|
||||
if (!model5)
|
||||
return null;
|
||||
return new Promise(async (resolve) => {
|
||||
const resize = image.resizeBilinear(image3, [model5.inputs[0].shape[2], model5.inputs[0].shape[1]], false);
|
||||
const norm2 = resize.sub(0.5);
|
||||
resize.dispose();
|
||||
const image3 = tidy(() => {
|
||||
const data3 = image.resizeBilinear(input2, [model5.inputs[0].shape[2], model5.inputs[0].shape[1]], false);
|
||||
const norm2 = data3.sub(data3.mean());
|
||||
return norm2;
|
||||
});
|
||||
let data2 = [];
|
||||
if (config3.face.embedding.enabled) {
|
||||
if (!config3.profile) {
|
||||
const res = await model5.predict({img_inputs: norm2});
|
||||
data2 = [...res.dataSync()];
|
||||
const res = await model5.predict({img_inputs: image3});
|
||||
const scaled = tidy(() => {
|
||||
const l23 = res.norm("euclidean");
|
||||
const scale2 = res.div(l23);
|
||||
return scale2;
|
||||
});
|
||||
data2 = [...scaled.dataSync()];
|
||||
dispose(scaled);
|
||||
dispose(res);
|
||||
} else {
|
||||
const profileData = await profile(() => model5.predict({img_inputs: norm2}));
|
||||
const profileData = await profile(() => model5.predict({img_inputs: image3}));
|
||||
data2 = [...profileData.result.dataSync()];
|
||||
profileData.result.dispose();
|
||||
run("emotion", profileData);
|
||||
}
|
||||
}
|
||||
norm2.dispose();
|
||||
image3.dispose();
|
||||
resolve(data2);
|
||||
});
|
||||
}
|
||||
|
@ -99029,7 +99036,8 @@ return a / b;`;
|
|||
skipInitial: false,
|
||||
minConfidence: 0.2,
|
||||
iouThreshold: 0.1,
|
||||
scoreThreshold: 0.2
|
||||
scoreThreshold: 0.2,
|
||||
return: true
|
||||
},
|
||||
mesh: {
|
||||
enabled: true,
|
||||
|
@ -100512,7 +100520,8 @@ lBhEMohlFerLlBjEMohMVTEARDKCITsAk2AEgAAAkAAAAAAAAAAAAAAAAAAAAAAAASAAAAAAAAD/
|
|||
emotion: emotionRes,
|
||||
embedding: embeddingRes,
|
||||
iris: irisSize !== 0 ? Math.trunc(irisSize) / 100 : 0,
|
||||
angle
|
||||
angle,
|
||||
tensor: this.config.face.detector.return ? face4.image.squeeze() : null
|
||||
});
|
||||
(_f = face4.image) == null ? void 0 : _f.dispose();
|
||||
__privateGet(this, _analyze).call(this, "End Face");
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -4337,32 +4337,39 @@ function simmilarity(embedding1, embedding2, order = 2) {
|
|||
return 0;
|
||||
if ((embedding1 == null ? void 0 : embedding1.length) !== (embedding2 == null ? void 0 : embedding2.length))
|
||||
return 0;
|
||||
const distance = 50 * embedding1.map((val, i) => val - embedding2[i]).reduce((dist, diff) => dist + diff ** order, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - (isNaN(distance) ? 1 : distance))) / 1e3;
|
||||
console.log(distance, res);
|
||||
const distance = embedding1.map((val, i) => Math.abs(embedding1[i] - embedding2[i]) ** order).reduce((sum, now2) => sum + now2, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - 20 * distance)) / 1e3;
|
||||
return res;
|
||||
}
|
||||
async function predict4(image13, config3) {
|
||||
async function predict4(input, config3) {
|
||||
if (!model4)
|
||||
return null;
|
||||
return new Promise(async (resolve) => {
|
||||
const resize = tf8.image.resizeBilinear(image13, [model4.inputs[0].shape[2], model4.inputs[0].shape[1]], false);
|
||||
const norm = resize.sub(0.5);
|
||||
resize.dispose();
|
||||
const image13 = tf8.tidy(() => {
|
||||
const data3 = tf8.image.resizeBilinear(input, [model4.inputs[0].shape[2], model4.inputs[0].shape[1]], false);
|
||||
const norm = data3.sub(data3.mean());
|
||||
return norm;
|
||||
});
|
||||
let data2 = [];
|
||||
if (config3.face.embedding.enabled) {
|
||||
if (!config3.profile) {
|
||||
const res = await model4.predict({img_inputs: norm});
|
||||
data2 = [...res.dataSync()];
|
||||
const res = await model4.predict({img_inputs: image13});
|
||||
const scaled = tf8.tidy(() => {
|
||||
const l2 = res.norm("euclidean");
|
||||
const scale = res.div(l2);
|
||||
return scale;
|
||||
});
|
||||
data2 = [...scaled.dataSync()];
|
||||
tf8.dispose(scaled);
|
||||
tf8.dispose(res);
|
||||
} else {
|
||||
const profileData = await tf8.profile(() => model4.predict({img_inputs: norm}));
|
||||
const profileData = await tf8.profile(() => model4.predict({img_inputs: image13}));
|
||||
data2 = [...profileData.result.dataSync()];
|
||||
profileData.result.dispose();
|
||||
run("emotion", profileData);
|
||||
}
|
||||
}
|
||||
norm.dispose();
|
||||
image13.dispose();
|
||||
resolve(data2);
|
||||
});
|
||||
}
|
||||
|
@ -23929,7 +23936,8 @@ var config_default = {
|
|||
skipInitial: false,
|
||||
minConfidence: 0.2,
|
||||
iouThreshold: 0.1,
|
||||
scoreThreshold: 0.2
|
||||
scoreThreshold: 0.2,
|
||||
return: true
|
||||
},
|
||||
mesh: {
|
||||
enabled: true,
|
||||
|
@ -25412,7 +25420,8 @@ var Human = class {
|
|||
emotion: emotionRes,
|
||||
embedding: embeddingRes,
|
||||
iris: irisSize !== 0 ? Math.trunc(irisSize) / 100 : 0,
|
||||
angle
|
||||
angle,
|
||||
tensor: this.config.face.detector.return ? face4.image.squeeze() : null
|
||||
});
|
||||
(_f = face4.image) == null ? void 0 : _f.dispose();
|
||||
__privateGet(this, _analyze).call(this, "End Face");
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -4337,32 +4337,39 @@ function simmilarity(embedding1, embedding2, order = 2) {
|
|||
return 0;
|
||||
if ((embedding1 == null ? void 0 : embedding1.length) !== (embedding2 == null ? void 0 : embedding2.length))
|
||||
return 0;
|
||||
const distance = 50 * embedding1.map((val, i) => val - embedding2[i]).reduce((dist, diff) => dist + diff ** order, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - (isNaN(distance) ? 1 : distance))) / 1e3;
|
||||
console.log(distance, res);
|
||||
const distance = embedding1.map((val, i) => Math.abs(embedding1[i] - embedding2[i]) ** order).reduce((sum, now2) => sum + now2, 0) ** (1 / order);
|
||||
const res = Math.trunc(1e3 * (1 - 20 * distance)) / 1e3;
|
||||
return res;
|
||||
}
|
||||
async function predict4(image13, config3) {
|
||||
async function predict4(input, config3) {
|
||||
if (!model4)
|
||||
return null;
|
||||
return new Promise(async (resolve) => {
|
||||
const resize = tf8.image.resizeBilinear(image13, [model4.inputs[0].shape[2], model4.inputs[0].shape[1]], false);
|
||||
const norm = resize.sub(0.5);
|
||||
resize.dispose();
|
||||
const image13 = tf8.tidy(() => {
|
||||
const data3 = tf8.image.resizeBilinear(input, [model4.inputs[0].shape[2], model4.inputs[0].shape[1]], false);
|
||||
const norm = data3.sub(data3.mean());
|
||||
return norm;
|
||||
});
|
||||
let data2 = [];
|
||||
if (config3.face.embedding.enabled) {
|
||||
if (!config3.profile) {
|
||||
const res = await model4.predict({img_inputs: norm});
|
||||
data2 = [...res.dataSync()];
|
||||
const res = await model4.predict({img_inputs: image13});
|
||||
const scaled = tf8.tidy(() => {
|
||||
const l2 = res.norm("euclidean");
|
||||
const scale = res.div(l2);
|
||||
return scale;
|
||||
});
|
||||
data2 = [...scaled.dataSync()];
|
||||
tf8.dispose(scaled);
|
||||
tf8.dispose(res);
|
||||
} else {
|
||||
const profileData = await tf8.profile(() => model4.predict({img_inputs: norm}));
|
||||
const profileData = await tf8.profile(() => model4.predict({img_inputs: image13}));
|
||||
data2 = [...profileData.result.dataSync()];
|
||||
profileData.result.dispose();
|
||||
run("emotion", profileData);
|
||||
}
|
||||
}
|
||||
norm.dispose();
|
||||
image13.dispose();
|
||||
resolve(data2);
|
||||
});
|
||||
}
|
||||
|
@ -23929,7 +23936,8 @@ var config_default = {
|
|||
skipInitial: false,
|
||||
minConfidence: 0.2,
|
||||
iouThreshold: 0.1,
|
||||
scoreThreshold: 0.2
|
||||
scoreThreshold: 0.2,
|
||||
return: true
|
||||
},
|
||||
mesh: {
|
||||
enabled: true,
|
||||
|
@ -25412,7 +25420,8 @@ var Human = class {
|
|||
emotion: emotionRes,
|
||||
embedding: embeddingRes,
|
||||
iris: irisSize !== 0 ? Math.trunc(irisSize) / 100 : 0,
|
||||
angle
|
||||
angle,
|
||||
tensor: this.config.face.detector.return ? face4.image.squeeze() : null
|
||||
});
|
||||
(_f = face4.image) == null ? void 0 : _f.dispose();
|
||||
__privateGet(this, _analyze).call(this, "End Face");
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -6,13 +6,6 @@ import * as profile from '../profile';
|
|||
// modified: https://github.com/sirius-ai/MobileFaceNet_TF/issues/46
|
||||
// download: https://github.com/sirius-ai/MobileFaceNet_TF/files/3551493/FaceMobileNet192_train_false.zip
|
||||
|
||||
/* WiP
|
||||
|
||||
- Should input box be tightly cropped?
|
||||
- What is the best input range? (adjust distance scale accordingly)
|
||||
- How to best normalize output
|
||||
*/
|
||||
|
||||
let model;
|
||||
|
||||
export async function load(config) {
|
||||
|
@ -29,58 +22,44 @@ export function simmilarity(embedding1, embedding2, order = 2) {
|
|||
if (embedding1?.length !== embedding2?.length) return 0;
|
||||
// general minkowski distance
|
||||
// euclidean distance is limited case where order is 2
|
||||
const distance = 50.0 * ((embedding1.map((val, i) => (val - embedding2[i])).reduce((dist, diff) => dist + (diff ** order), 0) ** (1 / order)));
|
||||
const res = (Math.trunc(1000 * (1 - (isNaN(distance) ? 1 : distance))) / 1000);
|
||||
console.log(distance, res);
|
||||
const distance = 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.trunc(1000 * (1 - (20 * distance))) / 1000;
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function predict(image, config) {
|
||||
export async function predict(input, config) {
|
||||
if (!model) return null;
|
||||
return new Promise(async (resolve) => {
|
||||
const resize = tf.image.resizeBilinear(image, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false); // input is already normalized to 0..1
|
||||
// optionally do a tight box crop
|
||||
/*
|
||||
const box = [[0, 0.2, 0.9, 0.8]]; // top, left, bottom, right
|
||||
const resize = tf.image.cropAndResize(image, box, [0], [model.inputs[0].shape[2], model.inputs[0].shape[1]]);
|
||||
*/
|
||||
// debug visualize box
|
||||
// const canvas = document.getElementById('compare-canvas');
|
||||
// await tf.browser.toPixels(resize.squeeze(), canvas);
|
||||
const norm = resize.sub(0.5);
|
||||
// optionally normalizes with mean value being at point 0, better than fixed range -0.5..0.5
|
||||
/*
|
||||
const mean = resize.mean();
|
||||
const norm = resize.sub(mean);
|
||||
*/
|
||||
resize.dispose();
|
||||
const image = tf.tidy(() => {
|
||||
const data = tf.image.resizeBilinear(input, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false); // input is already normalized to 0..1
|
||||
// const box = [[0.05, 0.10, 0.85, 0.90]]; // top, left, bottom, right
|
||||
// const crop = tf.image.cropAndResize(data, box, [0], [model.inputs[0].shape[2], model.inputs[0].shape[1]]); // optionally do a tight box crop
|
||||
const norm = data.sub(data.mean()); // trick to normalize around image mean value
|
||||
return norm;
|
||||
});
|
||||
let data: Array<[]> = [];
|
||||
if (config.face.embedding.enabled) {
|
||||
if (!config.profile) {
|
||||
const res = await model.predict({ img_inputs: norm });
|
||||
/*
|
||||
const res = await model.predict({ img_inputs: image });
|
||||
const scaled = tf.tidy(() => {
|
||||
// run l2 normalization on output
|
||||
const sqr = res.square();
|
||||
const sum = sqr.sum();
|
||||
const sqrt = sum.sqrt();
|
||||
const l2 = res.div(sqrt);
|
||||
// scale outputs
|
||||
const range = l2.max().sub(l2.min());
|
||||
const scale = l2.mul(2).div(range);
|
||||
const l2 = res.norm('euclidean');
|
||||
const scale = res.div(l2);
|
||||
return scale;
|
||||
});
|
||||
*/
|
||||
data = [...res.dataSync()]; // convert object array to standard array
|
||||
data = [...scaled.dataSync()]; // convert object array to standard array
|
||||
tf.dispose(scaled);
|
||||
tf.dispose(res);
|
||||
} else {
|
||||
const profileData = await tf.profile(() => model.predict({ img_inputs: norm }));
|
||||
const profileData = await tf.profile(() => model.predict({ img_inputs: image }));
|
||||
data = [...profileData.result.dataSync()];
|
||||
profileData.result.dispose();
|
||||
profile.run('emotion', profileData);
|
||||
}
|
||||
}
|
||||
norm.dispose();
|
||||
image.dispose();
|
||||
resolve(data);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -303,7 +303,8 @@ class Human {
|
|||
emotion: string,
|
||||
embedding: any,
|
||||
iris: number,
|
||||
angle: any
|
||||
angle: any,
|
||||
tensor: any,
|
||||
}> = [];
|
||||
|
||||
this.state = 'run:face';
|
||||
|
@ -402,7 +403,7 @@ class Human {
|
|||
embedding: embeddingRes,
|
||||
iris: (irisSize !== 0) ? Math.trunc(irisSize) / 100 : 0,
|
||||
angle,
|
||||
// image: face.image.toInt().squeeze(),
|
||||
tensor: this.config.face.detector.return ? face.image.squeeze() : null,
|
||||
});
|
||||
|
||||
// dont need face anymore
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export declare function load(config: any): Promise<any>;
|
||||
export declare function simmilarity(embedding1: any, embedding2: any, order?: number): number;
|
||||
export declare function predict(image: any, config: any): Promise<unknown>;
|
||||
export declare function predict(input: any, config: any): Promise<unknown>;
|
||||
|
|
2
wiki
2
wiki
|
@ -1 +1 @@
|
|||
Subproject commit 69294f7a0a99bd996286f8f5bb655c7ea8bfc10d
|
||||
Subproject commit fa7ac1f695547aa0fd25845e6cac7ed5ee0adcae
|
Loading…
Reference in New Issue