mirror of https://github.com/vladmandic/human
updated face description
parent
ccc4518b18
commit
11888adbb2
1271
CHANGELOG.md
1271
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
# Human Library
|
# Human Library
|
||||||
|
|
||||||
**3D Face Detection & Rotation Tracking, Face Embedding & Recognition,**
|
**3D Face Detection & Rotation Tracking, Face Description & Recognition,**
|
||||||
**Body Pose Tracking, 3D Hand & Finger Tracking,**
|
**Body Pose Tracking, 3D Hand & Finger Tracking,**
|
||||||
**Iris Analysis, Age & Gender & Emotion Prediction,**
|
**Iris Analysis, Age & Gender & Emotion Prediction,**
|
||||||
**Gesture Recognition**
|
**Gesture Recognition**
|
||||||
|
@ -46,8 +46,9 @@ Check out [**Live Demo**](https://vladmandic.github.io/human/demo/index.html) fo
|
||||||
- [**Usage & Functions**](https://github.com/vladmandic/human/wiki/Usage)
|
- [**Usage & Functions**](https://github.com/vladmandic/human/wiki/Usage)
|
||||||
- [**Configuration Details**](https://github.com/vladmandic/human/wiki/Configuration)
|
- [**Configuration Details**](https://github.com/vladmandic/human/wiki/Configuration)
|
||||||
- [**Output Details**](https://github.com/vladmandic/human/wiki/Outputs)
|
- [**Output Details**](https://github.com/vladmandic/human/wiki/Outputs)
|
||||||
- [**Face Recognition & Face Embedding**](https://github.com/vladmandic/human/wiki/Embedding)
|
- [**Face Recognition & Face Description**](https://github.com/vladmandic/human/wiki/Embedding)
|
||||||
- [**Gesture Recognition**](https://github.com/vladmandic/human/wiki/Gesture)
|
- [**Gesture Recognition**](https://github.com/vladmandic/human/wiki/Gesture)
|
||||||
|
- [**Common Issues**](https://github.com/vladmandic/human/wiki/Issues)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
@ -144,12 +145,10 @@ Default models in Human library are:
|
||||||
|
|
||||||
- **Face Detection**: MediaPipe BlazeFace-Back
|
- **Face Detection**: MediaPipe BlazeFace-Back
|
||||||
- **Face Mesh**: MediaPipe FaceMesh
|
- **Face Mesh**: MediaPipe FaceMesh
|
||||||
|
- **Face Description**: HSE FaceRes
|
||||||
- **Face Iris Analysis**: MediaPipe Iris
|
- **Face Iris Analysis**: MediaPipe Iris
|
||||||
- **Emotion Detection**: Oarriaga Emotion
|
- **Emotion Detection**: Oarriaga Emotion
|
||||||
- **Gender Detection**: Oarriaga Gender
|
|
||||||
- **Age Detection**: SSR-Net Age IMDB
|
|
||||||
- **Body Analysis**: PoseNet
|
- **Body Analysis**: PoseNet
|
||||||
- **Face Embedding**: BecauseofAI MobileFace Embedding
|
|
||||||
|
|
||||||
Note that alternative models are provided and can be enabled via configuration
|
Note that alternative models are provided and can be enabled via configuration
|
||||||
For example, `PoseNet` model can be switched for `BlazePose` model depending on the use case
|
For example, `PoseNet` model can be switched for `BlazePose` model depending on the use case
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<link rel="manifest" href="./manifest.webmanifest">
|
<link rel="manifest" href="./manifest.webmanifest">
|
||||||
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
|
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
|
||||||
<link rel="apple-touch-icon" href="../assets/icon.png">
|
<link rel="apple-touch-icon" href="../assets/icon.png">
|
||||||
<script src="./embedding.js" type="module"></script>
|
<script src="./facematch.js" type="module"></script>
|
||||||
<style>
|
<style>
|
||||||
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../assets/lato-light.woff2') }
|
@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: 24px; font-variant: small-caps; }
|
html { font-family: 'Lato', 'Segoe UI'; font-size: 24px; font-variant: small-caps; }
|
||||||
|
@ -27,12 +27,12 @@
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
<div>
|
<div>
|
||||||
Selected Face<br>
|
Selected Face<br>
|
||||||
<canvas id="orig" style="width: 200px; height: 200px;"></canvas>
|
<canvas id="orig" style="width: 200px; height: 200px; padding: 20px"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 20px"></div>
|
<div style="width: 20px"></div>
|
||||||
<div>
|
<div>
|
||||||
Sample Images<br>
|
Sample Images<br>
|
||||||
<div id="images"></div>
|
<div id="images" style="display: flex; flex-wrap: wrap; width: 85vw"></div>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<span id="desc" style="visibility: hidden; font-size: 0.4rem;"></span><br>
|
<span id="desc" style="visibility: hidden; font-size: 0.4rem;"></span><br>
|
|
@ -30,6 +30,9 @@ const human = new Human(userConfig); // new instance of human
|
||||||
const all = []; // array that will hold all detected faces
|
const all = []; // array that will hold all detected faces
|
||||||
let db = []; // array that holds all known faces
|
let db = []; // array that holds all known faces
|
||||||
|
|
||||||
|
const minScore = 0.6;
|
||||||
|
const minConfidence = 0.8;
|
||||||
|
|
||||||
function log(...msg) {
|
function log(...msg) {
|
||||||
const dt = new Date();
|
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')}`;
|
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')}`;
|
||||||
|
@ -37,13 +40,30 @@ function log(...msg) {
|
||||||
console.log(ts, ...msg);
|
console.log(ts, ...msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getFaceDB() {
|
||||||
|
// download db with known faces
|
||||||
|
try {
|
||||||
|
const res = await fetch('/demo/faces.json');
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function analyze(face) {
|
async function analyze(face) {
|
||||||
|
// refresh faces database
|
||||||
|
await getFaceDB();
|
||||||
|
|
||||||
// if we have face image tensor, enhance it and display it
|
// if we have face image tensor, enhance it and display it
|
||||||
if (face.tensor) {
|
if (face.tensor) {
|
||||||
const enhanced = human.enhance(face);
|
const enhanced = human.enhance(face);
|
||||||
// const desc = document.getElementById('desc');
|
const desc = document.getElementById('desc');
|
||||||
// desc.innerText = `{"name":"unknown", "source":"${face.fileName}", "embedding":[${face.embedding}]},`;
|
desc.innerText = `{"name":"unknown", "source":"${face.fileName}", "embedding":[${face.embedding}]},`;
|
||||||
navigator.clipboard.writeText(`{"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}]},`);
|
||||||
if (enhanced) {
|
if (enhanced) {
|
||||||
const c = document.getElementById('orig');
|
const c = document.getElementById('orig');
|
||||||
const squeeze = enhanced.squeeze().div(255);
|
const squeeze = enhanced.squeeze().div(255);
|
||||||
|
@ -73,11 +93,11 @@ async function analyze(face) {
|
||||||
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
||||||
ctx.fillText(`${(100 * similarity).toFixed(1)}%`, 4, 24);
|
ctx.fillText(`${(100 * similarity).toFixed(1)}%`, 4, 24);
|
||||||
ctx.font = 'small-caps 0.8rem "Lato"';
|
ctx.font = 'small-caps 0.8rem "Lato"';
|
||||||
ctx.fillText(`${current.age}y ${(100 * current.genderConfidence).toFixed(1)}% ${current.gender}`, 4, canvas.height - 6);
|
ctx.fillText(`${current.age}y ${(100 * (current.genderConfidence || 0)).toFixed(1)}% ${current.gender}`, 4, canvas.height - 6);
|
||||||
// identify person
|
// identify person
|
||||||
// ctx.font = 'small-caps 1rem "Lato"';
|
ctx.font = 'small-caps 1rem "Lato"';
|
||||||
// const person = await human.match(current.embedding, db);
|
const person = await human.match(current.embedding, db);
|
||||||
// if (person.similarity) ctx.fillText(`${(100 * person.similarity).toFixed(1)}% ${person.name}`, 4, canvas.height - 30);
|
if (person.similarity && person.similarity > minScore && current.confidence > minConfidence) ctx.fillText(`${(100 * person.similarity).toFixed(1)}% ${person.name}`, 4, canvas.height - 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort all faces by similarity
|
// sort all faces by similarity
|
||||||
|
@ -90,7 +110,6 @@ async function analyze(face) {
|
||||||
async function faces(index, res, fileName) {
|
async function faces(index, res, fileName) {
|
||||||
all[index] = res.face;
|
all[index] = res.face;
|
||||||
for (const i in res.face) {
|
for (const i in res.face) {
|
||||||
// log(res.face[i]);
|
|
||||||
all[index][i].fileName = fileName;
|
all[index][i].fileName = fileName;
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.tag = { sample: index, face: i };
|
canvas.tag = { sample: index, face: i };
|
||||||
|
@ -109,10 +128,10 @@ async function faces(index, res, fileName) {
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.font = 'small-caps 0.8rem "Lato"';
|
ctx.font = 'small-caps 0.8rem "Lato"';
|
||||||
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
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);
|
ctx.fillText(`${res.face[i].age}y ${(100 * (res.face[i].genderConfidence || 0)).toFixed(1)}% ${res.face[i].gender}`, 4, canvas.height - 6);
|
||||||
// const person = await human.match(res.face[i].embedding, db);
|
const person = await human.match(res.face[i].embedding, db);
|
||||||
// ctx.font = 'small-caps 1rem "Lato"';
|
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);
|
if (person.similarity && person.similarity > minScore && res.face[i].confidence > minConfidence) ctx.fillText(`${(100 * person.similarity).toFixed(1)}% ${person.name}`, 4, canvas.height - 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,24 +164,23 @@ async function main() {
|
||||||
// pre-load human models
|
// pre-load human models
|
||||||
await human.load();
|
await human.load();
|
||||||
|
|
||||||
// download db with known faces
|
let res;
|
||||||
let res = await fetch('/demo/faces.json');
|
let images = [];
|
||||||
db = (res && res.ok) ? await res.json() : [];
|
let dir = [];
|
||||||
|
await getFaceDB();
|
||||||
// enumerate all sample images in /assets
|
// enumerate all sample images in /assets
|
||||||
res = await fetch('/assets');
|
res = await fetch('/assets');
|
||||||
let dir = (res && res.ok) ? await res.json() : [];
|
dir = (res && res.ok) ? await res.json() : [];
|
||||||
let images = dir.filter((img) => (img.endsWith('.jpg') && img.includes('sample')));
|
images = images.concat(dir.filter((img) => (img.endsWith('.jpg') && img.includes('sample'))));
|
||||||
|
|
||||||
// enumerate additional private test images in /private, not includded in git repository
|
// enumerate additional private test images in /private, not includded in git repository
|
||||||
res = await fetch('/private/me');
|
res = await fetch('/private/me');
|
||||||
dir = (res && res.ok) ? await res.json() : [];
|
dir = (res && res.ok) ? await res.json() : [];
|
||||||
images = images.concat(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
|
// enumerate additional error images, not includded in git repository
|
||||||
// res = await fetch('/private/err');
|
res = await fetch('/private/err');
|
||||||
// dir = (res && res.ok) ? await res.json() : [];
|
dir = (res && res.ok) ? await res.json() : [];
|
||||||
// images = dir.filter((img) => (img.endsWith('.jpg')));
|
images = images.concat(dir.filter((img) => (img.endsWith('.jpg'))));
|
||||||
|
|
||||||
// download and analyze all images
|
// download and analyze all images
|
||||||
log('Enumerated:', images.length, 'images');
|
log('Enumerated:', images.length, 'images');
|
126
demo/faces.json
126
demo/faces.json
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
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
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
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
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@vladmandic/human",
|
"name": "@vladmandic/human",
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"description": "Human: AI-powered 3D Face Detection, Face Embedding & Recognition, Body Pose Tracking, Hand & Finger Tracking, Iris Analysis, Age & Gender & Emotion Prediction & Gesture Recognition",
|
"description": "Human: AI-powered 3D Face Detection, Face Description & Recognition, Body Pose Tracking, Hand & Finger Tracking, Iris Analysis, Age & Gender & Emotion Prediction & Gesture Recognition",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"main": "dist/human.node.js",
|
"main": "dist/human.node.js",
|
||||||
"module": "dist/human.esm.js",
|
"module": "dist/human.esm.js",
|
||||||
|
|
|
@ -32,7 +32,7 @@ const options = {
|
||||||
httpsPort: 10031,
|
httpsPort: 10031,
|
||||||
insecureHTTPParser: false,
|
insecureHTTPParser: false,
|
||||||
minElapsed: 2,
|
minElapsed: 2,
|
||||||
monitor: ['package.json', 'config.ts', 'demo', 'src'],
|
monitor: ['package.json', 'config.ts', 'demo/*.js', 'demo/*.html', 'src'],
|
||||||
};
|
};
|
||||||
|
|
||||||
// just some predefined mime types
|
// just some predefined mime types
|
||||||
|
|
|
@ -218,7 +218,7 @@ const config: Config = {
|
||||||
},
|
},
|
||||||
|
|
||||||
description: {
|
description: {
|
||||||
enabled: true, // to improve accuracy of face embedding extraction it is
|
enabled: true, // to improve accuracy of face description extraction it is
|
||||||
// recommended to enable detector.rotation and mesh.enabled
|
// recommended to enable detector.rotation and mesh.enabled
|
||||||
modelPath: '../models/faceres.json',
|
modelPath: '../models/faceres.json',
|
||||||
skipFrames: 31, // how many frames to go without re-running the detector
|
skipFrames: 31, // how many frames to go without re-running the detector
|
||||||
|
|
2
wiki
2
wiki
|
@ -1 +1 @@
|
||||||
Subproject commit ad7e00dab159a3730e1558e6e134a5727990babd
|
Subproject commit 11e68676b2bf9aadd7ff1e2cc80dd4a70052f9d3
|
Loading…
Reference in New Issue