mirror of https://github.com/vladmandic/human
switch to custom tfjs for demos
parent
8c941597ed
commit
81d5336498
44
.build.json
44
.build.json
|
@ -111,34 +111,13 @@
|
||||||
"sourcemap": true,
|
"sourcemap": true,
|
||||||
"external": ["@tensorflow", "fs", "os", "buffer", "util"]
|
"external": ["@tensorflow", "fs", "os", "buffer", "util"]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"name": "tfjs/browser/esm/custom",
|
|
||||||
"platform": "browser",
|
|
||||||
"format": "esm",
|
|
||||||
"input": "tfjs/tf-custom.ts",
|
|
||||||
"output": "dist/tfjs.esm.js",
|
|
||||||
"sourcemap": true,
|
|
||||||
"external": ["fs", "os", "buffer", "util"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "human/browser/esm/custom",
|
|
||||||
"platform": "browser",
|
|
||||||
"format": "esm",
|
|
||||||
"input": "src/human.ts",
|
|
||||||
"output": "dist/human.custom.esm.js",
|
|
||||||
"sourcemap": true,
|
|
||||||
"minify": true,
|
|
||||||
"external": ["fs", "os", "buffer", "util"]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "tfjs/browser/esm/bundle",
|
"name": "tfjs/browser/esm/bundle",
|
||||||
"platform": "browser",
|
"platform": "browser",
|
||||||
"format": "esm",
|
"format": "esm",
|
||||||
"input": "tfjs/tf-browser.ts",
|
"input": "tfjs/tf-browser.ts",
|
||||||
"output": "dist/tfjs.esm.js",
|
"output": "dist/tfjs.esm.js",
|
||||||
"minify": true,
|
"minify": false,
|
||||||
"sourcemap": true,
|
"sourcemap": true,
|
||||||
"external": ["fs", "os", "buffer", "util"]
|
"external": ["fs", "os", "buffer", "util"]
|
||||||
},
|
},
|
||||||
|
@ -158,7 +137,28 @@
|
||||||
"format": "esm",
|
"format": "esm",
|
||||||
"input": "src/human.ts",
|
"input": "src/human.ts",
|
||||||
"output": "dist/human.esm.js",
|
"output": "dist/human.esm.js",
|
||||||
|
"minify": true,
|
||||||
"sourcemap": true,
|
"sourcemap": true,
|
||||||
|
"external": ["fs", "os", "buffer", "util"]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "tfjs/browser/esm/custom",
|
||||||
|
"platform": "browser",
|
||||||
|
"format": "esm",
|
||||||
|
"input": "tfjs/tf-custom.ts",
|
||||||
|
"output": "dist/tfjs.esm.js",
|
||||||
|
"sourcemap": false,
|
||||||
|
"external": ["fs", "os", "buffer", "util"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "human/browser/esm/custom",
|
||||||
|
"platform": "browser",
|
||||||
|
"format": "esm",
|
||||||
|
"input": "src/human.ts",
|
||||||
|
"output": "dist/human.custom.esm.js",
|
||||||
|
"sourcemap": true,
|
||||||
|
"minify": false,
|
||||||
"external": ["fs", "os", "buffer", "util"],
|
"external": ["fs", "os", "buffer", "util"],
|
||||||
"typings": "types",
|
"typings": "types",
|
||||||
"typedoc": "typedoc"
|
"typedoc": "typedoc"
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
"ecmaVersion": 2021
|
"ecmaVersion": 2021
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@typescript-eslint"
|
"@typescript-eslint",
|
||||||
|
"html"
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"airbnb-base",
|
"airbnb-base",
|
||||||
|
|
2
.hintrc
2
.hintrc
|
@ -5,7 +5,7 @@
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"chrome >= 90",
|
"chrome >= 90",
|
||||||
"edge >= 90",
|
"edge >= 90",
|
||||||
"firefox >= 90",
|
"firefox >= 100",
|
||||||
"android >= 90",
|
"android >= 90",
|
||||||
"safari >= 15"
|
"safari >= 15"
|
||||||
],
|
],
|
||||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -9,11 +9,15 @@
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### **HEAD -> main** 2021/10/25 mandic00@live.com
|
||||||
|
|
||||||
|
|
||||||
|
### **release: 2.4.1** 2021/10/25 mandic00@live.com
|
||||||
|
|
||||||
|
|
||||||
### **2.4.1** 2021/10/25 mandic00@live.com
|
### **2.4.1** 2021/10/25 mandic00@live.com
|
||||||
|
|
||||||
|
- refactoring plus jsdoc comments
|
||||||
### **origin/main** 2021/10/25 mandic00@live.com
|
|
||||||
|
|
||||||
- increase face similarity match resolution
|
- increase face similarity match resolution
|
||||||
- time based caching
|
- time based caching
|
||||||
- turn on minification
|
- turn on minification
|
||||||
|
|
6
TODO.md
6
TODO.md
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
## Work in Progress
|
## Work in Progress
|
||||||
|
|
||||||
|
- Switch to custom `tfjs` for main `human` ESM bundle
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
### Exploring
|
### Exploring
|
||||||
|
|
||||||
- Switch to custom `tfjs` for main `human` ESM bundle
|
|
||||||
- Optical Flow: <https://docs.opencv.org/3.3.1/db/d7f/tutorial_js_lucas_kanade.html>
|
- Optical Flow: <https://docs.opencv.org/3.3.1/db/d7f/tutorial_js_lucas_kanade.html>
|
||||||
- Histogram Equalization: Regular, Adaptive, Contrast Limited
|
- Histogram Equalization: Regular, Adaptive, Contrast Limited
|
||||||
- TFLite Models: <https://js.tensorflow.org/api_tflite/0.0.1-alpha.4/>
|
- TFLite Models: <https://js.tensorflow.org/api_tflite/0.0.1-alpha.4/>
|
||||||
|
@ -22,6 +23,9 @@ Experimental support only until support is officially added in Chromium
|
||||||
|
|
||||||
## Known Issues
|
## Known Issues
|
||||||
|
|
||||||
|
- `tfjs.esm.d.ts` missing namespace `OptimizerConstructors`
|
||||||
|
- exports from `match` are marked as private
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
### Face Detection
|
### Face Detection
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
// @ts-nocheck // typescript checks disabled as this is pure javascript
|
// @ts-nocheck
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Human demo for browsers
|
* Human demo for browsers
|
||||||
*
|
*
|
||||||
* Demo for face descriptor analysis and face simmilarity analysis
|
* Demo for face descriptor analysis and face simmilarity analysis
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Human from '../../dist/human.esm.js';
|
/** @type {Human} */
|
||||||
|
import Human from '../../dist/human.custom.esm.js';
|
||||||
|
|
||||||
const userConfig = {
|
const userConfig = {
|
||||||
backend: 'wasm',
|
backend: 'wasm',
|
||||||
|
@ -15,7 +15,6 @@ const userConfig = {
|
||||||
cacheSensitivity: 0,
|
cacheSensitivity: 0,
|
||||||
debug: true,
|
debug: true,
|
||||||
modelBasePath: '../../models/',
|
modelBasePath: '../../models/',
|
||||||
// wasmPath: 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-wasm@3.9.0/dist/',
|
|
||||||
face: {
|
face: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
detector: { rotation: true, return: true, maxDetected: 50 },
|
detector: { rotation: true, return: true, maxDetected: 50 },
|
||||||
|
@ -165,6 +164,7 @@ async function AddFaceCanvas(index, res, fileName) {
|
||||||
await human.tf.browser.toPixels(res.face[i].tensor, canvas);
|
await human.tf.browser.toPixels(res.face[i].tensor, canvas);
|
||||||
document.getElementById('faces').appendChild(canvas);
|
document.getElementById('faces').appendChild(canvas);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return false;
|
||||||
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].genderScore || 0)).toFixed(1)}% ${res.face[i].gender}`, 4, canvas.height - 6);
|
ctx.fillText(`${res.face[i].age}y ${(100 * (res.face[i].genderScore || 0)).toFixed(1)}% ${res.face[i].gender}`, 4, canvas.height - 6);
|
||||||
|
@ -258,6 +258,8 @@ async function main() {
|
||||||
|
|
||||||
title('');
|
title('');
|
||||||
log('Ready');
|
log('Ready');
|
||||||
|
human.validate(userConfig);
|
||||||
|
human.similarity([], []);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = main;
|
window.onload = main;
|
||||||
|
|
|
@ -37,7 +37,7 @@ const UISVG = `
|
||||||
|
|
||||||
class GLBench {
|
class GLBench {
|
||||||
/** GLBench constructor
|
/** GLBench constructor
|
||||||
* @param { WebGLRenderingContext | WebGL2RenderingContext } gl context
|
* @param { WebGLRenderingContext | WebGL2RenderingContext | null } gl context
|
||||||
* @param { Object | undefined } settings additional settings
|
* @param { Object | undefined } settings additional settings
|
||||||
*/
|
*/
|
||||||
constructor(gl, settings = {}) {
|
constructor(gl, settings = {}) {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
//@ts-nocheck
|
|
||||||
|
|
||||||
let instance = 0;
|
let instance = 0;
|
||||||
let CSScreated = false;
|
let CSScreated = false;
|
||||||
|
|
||||||
|
@ -86,6 +84,7 @@ class Menu {
|
||||||
}
|
}
|
||||||
|
|
||||||
createMenu(parent, title = '', position = { top: null, left: null, bottom: null, right: null }) {
|
createMenu(parent, title = '', position = { top: null, left: null, bottom: null, right: null }) {
|
||||||
|
/** @type {HTMLDivElement} */
|
||||||
this.menu = document.createElement('div');
|
this.menu = document.createElement('div');
|
||||||
this.menu.id = `menu-${instance}`;
|
this.menu.id = `menu-${instance}`;
|
||||||
this.menu.className = 'menu';
|
this.menu.className = 'menu';
|
||||||
|
@ -120,6 +119,7 @@ class Menu {
|
||||||
|
|
||||||
this.menu.appendChild(this.container);
|
this.menu.appendChild(this.container);
|
||||||
if (typeof parent === 'object') parent.appendChild(this.menu);
|
if (typeof parent === 'object') parent.appendChild(this.menu);
|
||||||
|
// @ts-ignore undefined
|
||||||
else document.getElementById(parent).appendChild(this.menu);
|
else document.getElementById(parent).appendChild(this.menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,11 +133,11 @@ class Menu {
|
||||||
}
|
}
|
||||||
|
|
||||||
get width() {
|
get width() {
|
||||||
return this.menu.offsetWidth || 0;
|
return this.menu ? this.menu.offsetWidth : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get height() {
|
get height() {
|
||||||
return this.menu.offsetHeight || 0;
|
return this.menu ? this.menu.offsetHeight : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
|
@ -184,6 +184,7 @@ class Menu {
|
||||||
this.hidden = !this.hidden;
|
this.hidden = !this.hidden;
|
||||||
const all = document.getElementsByClassName('menu');
|
const all = document.getElementsByClassName('menu');
|
||||||
for (const item of all) {
|
for (const item of all) {
|
||||||
|
// @ts-ignore
|
||||||
item.style.display = this.hidden ? 'none' : 'block';
|
item.style.display = this.hidden ? 'none' : 'block';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -205,8 +206,10 @@ class Menu {
|
||||||
el.innerHTML = `<div class="menu-checkbox"><input class="menu-checkbox" type="checkbox" id="${this.newID}" ${object[variable] ? 'checked' : ''}/><label class="menu-checkbox-label" for="${this.ID}"></label></div>${title}`;
|
el.innerHTML = `<div class="menu-checkbox"><input class="menu-checkbox" type="checkbox" id="${this.newID}" ${object[variable] ? 'checked' : ''}/><label class="menu-checkbox-label" for="${this.ID}"></label></div>${title}`;
|
||||||
if (this.container) this.container.appendChild(el);
|
if (this.container) this.container.appendChild(el);
|
||||||
el.addEventListener('change', (evt) => {
|
el.addEventListener('change', (evt) => {
|
||||||
object[variable] = evt.target.checked;
|
if (evt.target) {
|
||||||
if (callback) callback(evt.target.checked);
|
object[variable] = evt.target['checked'];
|
||||||
|
if (callback) callback(evt.target['checked']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
@ -225,7 +228,7 @@ class Menu {
|
||||||
el.style.fontVariant = document.body.style.fontVariant;
|
el.style.fontVariant = document.body.style.fontVariant;
|
||||||
if (this.container) this.container.appendChild(el);
|
if (this.container) this.container.appendChild(el);
|
||||||
el.addEventListener('change', (evt) => {
|
el.addEventListener('change', (evt) => {
|
||||||
if (callback) callback(items[evt.target.selectedIndex]);
|
if (callback && evt.target) callback(items[evt.target['selectedIndex']]);
|
||||||
});
|
});
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
@ -237,12 +240,13 @@ class Menu {
|
||||||
if (this.container) this.container.appendChild(el);
|
if (this.container) this.container.appendChild(el);
|
||||||
el.addEventListener('change', (evt) => {
|
el.addEventListener('change', (evt) => {
|
||||||
if (evt.target) {
|
if (evt.target) {
|
||||||
object[variable] = parseInt(evt.target.value) === parseFloat(evt.target.value) ? parseInt(evt.target.value) : parseFloat(evt.target.value);
|
object[variable] = parseInt(evt.target['value']) === parseFloat(evt.target['value']) ? parseInt(evt.target['value']) : parseFloat(evt.target['value']);
|
||||||
evt.target.setAttribute('value', evt.target.value);
|
// @ts-ignore
|
||||||
if (callback) callback(evt.target.value);
|
evt.target.setAttribute('value', evt.target['value']);
|
||||||
|
if (callback) callback(evt.target['value']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
el.input = el.children[0];
|
el['input'] = el.children[0];
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,9 +306,12 @@ class Menu {
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
async updateChart(id, values) {
|
async updateChart(id, values) {
|
||||||
if (!values || (values.length === 0)) return;
|
if (!values || (values.length === 0)) return;
|
||||||
|
/** @type {HTMLCanvasElement} */
|
||||||
|
// @ts-ignore undefined
|
||||||
const canvas = document.getElementById(`menu-canvas-${id}`);
|
const canvas = document.getElementById(`menu-canvas-${id}`);
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return;
|
||||||
ctx.fillStyle = theme.background;
|
ctx.fillStyle = theme.background;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
const width = canvas.width / values.length;
|
const width = canvas.width / values.length;
|
||||||
|
@ -318,7 +325,7 @@ class Menu {
|
||||||
ctx.fillRect(i * width, 0, width - 4, canvas.height);
|
ctx.fillRect(i * width, 0, width - 4, canvas.height);
|
||||||
ctx.fillStyle = theme.background;
|
ctx.fillStyle = theme.background;
|
||||||
ctx.font = `${width / 1.5}px "Segoe UI"`;
|
ctx.font = `${width / 1.5}px "Segoe UI"`;
|
||||||
ctx.fillText(Math.round(values[i]), i * width + 1, canvas.height - 1, width - 1);
|
ctx.fillText(Math.round(values[i]).toString(), i * width + 1, canvas.height - 1, width - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
/// <reference lib="webworker" />
|
/**
|
||||||
|
* Web worker used by main demo app
|
||||||
|
* Loaded from index.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference lib="webworker"/>
|
||||||
|
|
||||||
// load Human using IIFE script as Chome Mobile does not support Modules as Workers
|
// load Human using IIFE script as Chome Mobile does not support Modules as Workers
|
||||||
// import Human from '../dist/human.esm.js';
|
/** @type {Human} */
|
||||||
|
const Human = {};
|
||||||
self.importScripts('../dist/human.js');
|
self.importScripts('../dist/human.js');
|
||||||
|
|
||||||
let busy = false;
|
let busy = false;
|
||||||
// @ts-ignore // Human is registered as global namespace using IIFE script
|
// eslint-disable-next-line new-cap
|
||||||
// eslint-disable-next-line no-undef, new-cap
|
|
||||||
const human = new Human.default();
|
const human = new Human.default();
|
||||||
|
|
||||||
onmessage = async (msg) => { // receive message from main thread
|
onmessage = async (msg) => { // receive message from main thread
|
||||||
|
|
|
@ -4,9 +4,8 @@
|
||||||
* @description Demo app that enables all Human modules and runs them in separate worker threads
|
* @description Demo app that enables all Human modules and runs them in separate worker threads
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
// @ts-nocheck // typescript checks disabled as this is pure javascript
|
|
||||||
|
|
||||||
import Human from '../../dist/human.esm.js'; // equivalent of @vladmandic/human
|
import Human from '../../dist/human.custom.esm.js'; // equivalent of @vladmandic/human
|
||||||
import GLBench from '../helpers/gl-bench.js';
|
import GLBench from '../helpers/gl-bench.js';
|
||||||
|
|
||||||
const workerJS = './worker.js';
|
const workerJS = './worker.js';
|
||||||
|
@ -92,9 +91,13 @@ const busy = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const workers = {
|
const workers = {
|
||||||
|
/** @type {Worker | null} */
|
||||||
face: null,
|
face: null,
|
||||||
|
/** @type {Worker | null} */
|
||||||
body: null,
|
body: null,
|
||||||
|
/** @type {Worker | null} */
|
||||||
hand: null,
|
hand: null,
|
||||||
|
/** @type {Worker | null} */
|
||||||
object: null,
|
object: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -138,7 +141,8 @@ async function drawResults() {
|
||||||
time.draw = Math.round(1 + human.now() - start.draw);
|
time.draw = Math.round(1 + human.now() - start.draw);
|
||||||
const fps = Math.round(10 * 1000 / time.main) / 10;
|
const fps = Math.round(10 * 1000 / time.main) / 10;
|
||||||
const draw = Math.round(10 * 1000 / time.draw) / 10;
|
const draw = Math.round(10 * 1000 / time.draw) / 10;
|
||||||
document.getElementById('log').innerText = `Human: version ${human.version} | Performance: Main ${time.main}ms Face: ${time.face}ms Body: ${time.body}ms Hand: ${time.hand}ms Object ${time.object}ms | FPS: ${fps} / ${draw}`;
|
const div = document.getElementById('log');
|
||||||
|
if (div) div.innerText = `Human: version ${human.version} | Performance: Main ${time.main}ms Face: ${time.face}ms Body: ${time.body}ms Hand: ${time.hand}ms Object ${time.object}ms | FPS: ${fps} / ${draw}`;
|
||||||
requestAnimationFrame(drawResults);
|
requestAnimationFrame(drawResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +156,7 @@ async function runDetection() {
|
||||||
start.main = human.now();
|
start.main = human.now();
|
||||||
if (!bench) {
|
if (!bench) {
|
||||||
bench = new GLBench(null, { trackGPU: false, chartHz: 20, chartLen: 20 });
|
bench = new GLBench(null, { trackGPU: false, chartHz: 20, chartLen: 20 });
|
||||||
bench.begin();
|
bench.begin('human');
|
||||||
}
|
}
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
|
@ -160,22 +164,22 @@ async function runDetection() {
|
||||||
if (!busy.face) {
|
if (!busy.face) {
|
||||||
busy.face = true;
|
busy.face = true;
|
||||||
start.face = human.now();
|
start.face = human.now();
|
||||||
workers.face.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.face, type: 'face' }, [imageData.data.buffer.slice(0)]);
|
if (workers.face) workers.face.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.face, type: 'face' }, [imageData.data.buffer.slice(0)]);
|
||||||
}
|
}
|
||||||
if (!busy.body) {
|
if (!busy.body) {
|
||||||
busy.body = true;
|
busy.body = true;
|
||||||
start.body = human.now();
|
start.body = human.now();
|
||||||
workers.body.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.body, type: 'body' }, [imageData.data.buffer.slice(0)]);
|
if (workers.body) workers.body.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.body, type: 'body' }, [imageData.data.buffer.slice(0)]);
|
||||||
}
|
}
|
||||||
if (!busy.hand) {
|
if (!busy.hand) {
|
||||||
busy.hand = true;
|
busy.hand = true;
|
||||||
start.hand = human.now();
|
start.hand = human.now();
|
||||||
workers.hand.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.hand, type: 'hand' }, [imageData.data.buffer.slice(0)]);
|
if (workers.hand) workers.hand.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.hand, type: 'hand' }, [imageData.data.buffer.slice(0)]);
|
||||||
}
|
}
|
||||||
if (!busy.object) {
|
if (!busy.object) {
|
||||||
busy.object = true;
|
busy.object = true;
|
||||||
start.object = human.now();
|
start.object = human.now();
|
||||||
workers.object.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.object, type: 'object' }, [imageData.data.buffer.slice(0)]);
|
if (workers.object) workers.object.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.object, type: 'object' }, [imageData.data.buffer.slice(0)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
time.main = Math.round(human.now() - start.main);
|
time.main = Math.round(human.now() - start.main);
|
||||||
|
@ -204,27 +208,29 @@ async function setupCamera() {
|
||||||
try {
|
try {
|
||||||
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
output.innerText += `\n${err.name}: ${err.message}`;
|
if (output) output.innerText += `\n${err.name}: ${err.message}`;
|
||||||
status(err.name);
|
|
||||||
log('camera error:', err);
|
log('camera error:', err);
|
||||||
}
|
}
|
||||||
const tracks = stream.getVideoTracks();
|
if (stream) {
|
||||||
log('enumerated viable tracks:', tracks);
|
const tracks = stream.getVideoTracks();
|
||||||
const track = stream.getVideoTracks()[0];
|
log('enumerated viable tracks:', tracks);
|
||||||
const settings = track.getSettings();
|
const track = stream.getVideoTracks()[0];
|
||||||
log('selected video source:', track, settings);
|
const settings = track.getSettings();
|
||||||
|
log('selected video source:', track, settings);
|
||||||
|
} else {
|
||||||
|
log('missing video stream');
|
||||||
|
}
|
||||||
const promise = !stream || new Promise((resolve) => {
|
const promise = !stream || new Promise((resolve) => {
|
||||||
video.onloadeddata = () => {
|
video.onloadeddata = () => {
|
||||||
if (settings.width > settings.height) canvas.style.width = '100vw';
|
canvas.style.height = '100vh';
|
||||||
else canvas.style.height = '100vh';
|
|
||||||
canvas.width = video.videoWidth;
|
canvas.width = video.videoWidth;
|
||||||
canvas.height = video.videoHeight;
|
canvas.height = video.videoHeight;
|
||||||
video.play();
|
video.play();
|
||||||
resolve();
|
resolve(true);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
// attach input to video element
|
// attach input to video element
|
||||||
if (stream) video.srcObject = stream;
|
if (stream && video) video['srcObject'] = stream;
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,21 +246,13 @@ async function startWorkers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
window.addEventListener('unhandledrejection', (evt) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(evt.reason || evt);
|
|
||||||
document.getElementById('log').innerHTML = evt.reason.message || evt.reason || evt;
|
|
||||||
status('exception error');
|
|
||||||
evt.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof Worker === 'undefined' || typeof OffscreenCanvas === 'undefined') {
|
if (typeof Worker === 'undefined' || typeof OffscreenCanvas === 'undefined') {
|
||||||
status('workers are not supported');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
human = new Human(config.main);
|
human = new Human(config.main);
|
||||||
document.getElementById('log').innerText = `Human: version ${human.version}`;
|
const div = document.getElementById('log');
|
||||||
|
if (div) div.innerText = `Human: version ${human.version}`;
|
||||||
|
|
||||||
await startWorkers();
|
await startWorkers();
|
||||||
await setupCamera();
|
await setupCamera();
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// load Human using IIFE script as Chome Mobile does not support Modules as Workers
|
|
||||||
|
|
||||||
/// <reference lib="webworker" />
|
/// <reference lib="webworker" />
|
||||||
|
|
||||||
// import Human from '../dist/human.esm.js';
|
// load Human using IIFE script as Chome Mobile does not support Modules as Workers
|
||||||
self.importScripts('../../dist/human.js');
|
self.importScripts('../../dist/human.js');
|
||||||
|
|
||||||
let human;
|
let human;
|
||||||
|
|
|
@ -66,16 +66,18 @@
|
||||||
"@tensorflow/tfjs-layers": "^3.10.0",
|
"@tensorflow/tfjs-layers": "^3.10.0",
|
||||||
"@tensorflow/tfjs-node": "^3.10.0",
|
"@tensorflow/tfjs-node": "^3.10.0",
|
||||||
"@tensorflow/tfjs-node-gpu": "^3.10.0",
|
"@tensorflow/tfjs-node-gpu": "^3.10.0",
|
||||||
"@types/node": "^16.11.5",
|
"@types/node": "^16.11.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.2.0",
|
"@typescript-eslint/eslint-plugin": "^5.2.0",
|
||||||
"@typescript-eslint/parser": "^5.2.0",
|
"@typescript-eslint/parser": "^5.2.0",
|
||||||
"@vladmandic/build": "^0.6.3",
|
"@vladmandic/build": "^0.6.3",
|
||||||
"@vladmandic/pilogger": "^0.3.3",
|
"@vladmandic/pilogger": "^0.3.3",
|
||||||
"canvas": "^2.8.0",
|
"canvas": "^2.8.0",
|
||||||
"dayjs": "^1.10.7",
|
"dayjs": "^1.10.7",
|
||||||
|
"long": "^4.0.0",
|
||||||
"esbuild": "^0.13.9",
|
"esbuild": "^0.13.9",
|
||||||
"eslint": "8.1.0",
|
"eslint": "8.1.0",
|
||||||
"eslint-config-airbnb-base": "^14.2.1",
|
"eslint-config-airbnb-base": "^14.2.1",
|
||||||
|
"eslint-plugin-html": "^6.2.0",
|
||||||
"eslint-plugin-import": "^2.25.2",
|
"eslint-plugin-import": "^2.25.2",
|
||||||
"eslint-plugin-json": "^3.1.0",
|
"eslint-plugin-json": "^3.1.0",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
@ -88,6 +90,5 @@
|
||||||
"typescript": "4.4.4"
|
"typescript": "4.4.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"long": "^4.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/** Face descriptor type as number array */
|
/** Face descriptor type as number array */
|
||||||
export type Descriptor = Array<number>
|
export type Descriptor = Array<number>
|
||||||
|
export type Options = { order?: number, threshold?: number, multiplier?: number } | undefined;
|
||||||
|
|
||||||
/** Calculates distance between two descriptors
|
/** Calculates distance between two descriptors
|
||||||
* @param {object} options
|
* @param {object} options
|
||||||
|
@ -9,12 +10,12 @@ export type Descriptor = Array<number>
|
||||||
* - default is 20 which normalizes results to similarity above 0.5 can be considered a match
|
* - default is 20 which normalizes results to similarity above 0.5 can be considered a match
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function distance(descriptor1: Descriptor, descriptor2: Descriptor, options = { order: 2, multiplier: 20 }) {
|
export function distance(descriptor1: Descriptor, descriptor2: Descriptor, options: Options = { order: 2, multiplier: 20 }) {
|
||||||
// general minkowski distance, euclidean distance is limited case where order is 2
|
// general minkowski distance, euclidean distance is limited case where order is 2
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
for (let i = 0; i < descriptor1.length; i++) {
|
for (let i = 0; i < descriptor1.length; i++) {
|
||||||
const diff = (options.order === 2) ? (descriptor1[i] - descriptor2[i]) : (Math.abs(descriptor1[i] - descriptor2[i]));
|
const diff = (!options.order || options.order === 2) ? (descriptor1[i] - descriptor2[i]) : (Math.abs(descriptor1[i] - descriptor2[i]));
|
||||||
sum += (options.order === 2) ? (diff * diff) : (diff ** options.order);
|
sum += (!options.order || options.order === 2) ? (diff * diff) : (diff ** options.order);
|
||||||
}
|
}
|
||||||
return (options.multiplier || 20) * sum;
|
return (options.multiplier || 20) * sum;
|
||||||
}
|
}
|
||||||
|
@ -27,9 +28,9 @@ export function distance(descriptor1: Descriptor, descriptor2: Descriptor, optio
|
||||||
* - default is 20 which normalizes results to similarity above 0.5 can be considered a match
|
* - default is 20 which normalizes results to similarity above 0.5 can be considered a match
|
||||||
* @returns {number} similarity between two face descriptors normalized to 0..1 range where 0 is no similarity and 1 is perfect similarity
|
* @returns {number} similarity between two face descriptors normalized to 0..1 range where 0 is no similarity and 1 is perfect similarity
|
||||||
*/
|
*/
|
||||||
export function similarity(descriptor1: Descriptor, descriptor2: Descriptor, options = { order: 2, multiplier: 20 }) {
|
export function similarity(descriptor1: Descriptor, descriptor2: Descriptor, options: Options = { order: 2, multiplier: 20 }) {
|
||||||
const dist = distance(descriptor1, descriptor2, options);
|
const dist = distance(descriptor1, descriptor2, options);
|
||||||
const root = (options.order === 2) ? Math.sqrt(dist) : dist ** (1 / options.order);
|
const root = (!options.order || options.order === 2) ? Math.sqrt(dist) : dist ** (1 / options.order);
|
||||||
const invert = Math.max(0, 100 - root) / 100.0;
|
const invert = Math.max(0, 100 - root) / 100.0;
|
||||||
return invert;
|
return invert;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +46,7 @@ export function similarity(descriptor1: Descriptor, descriptor2: Descriptor, opt
|
||||||
* - {@link distance} calculated `distance` of given descriptor to the best match
|
* - {@link distance} calculated `distance` of given descriptor to the best match
|
||||||
* - {@link similarity} calculated normalized `similarity` of given descriptor to the best match
|
* - {@link similarity} calculated normalized `similarity` of given descriptor to the best match
|
||||||
*/
|
*/
|
||||||
export function match(descriptor: Descriptor, descriptors: Array<Descriptor>, options = { order: 2, threshold: 0, multiplier: 20 }) {
|
export function match(descriptor: Descriptor, descriptors: Array<Descriptor>, options: Options = { order: 2, multiplier: 20, threshold: 0 }) {
|
||||||
if (!Array.isArray(descriptor) || !Array.isArray(descriptors) || descriptor.length < 64 || descriptors.length === 0 || descriptor.length !== descriptors[0].length) { // validate input
|
if (!Array.isArray(descriptor) || !Array.isArray(descriptors) || descriptor.length < 64 || descriptors.length === 0 || descriptor.length !== descriptors[0].length) { // validate input
|
||||||
return { index: -1, distance: Number.POSITIVE_INFINITY, similarity: 0 };
|
return { index: -1, distance: Number.POSITIVE_INFINITY, similarity: 0 };
|
||||||
}
|
}
|
||||||
|
@ -57,8 +58,8 @@ export function match(descriptor: Descriptor, descriptors: Array<Descriptor>, op
|
||||||
best = res;
|
best = res;
|
||||||
index = i;
|
index = i;
|
||||||
}
|
}
|
||||||
if (best < options.threshold) break;
|
if (best < (options.threshold || 0)) break;
|
||||||
}
|
}
|
||||||
best = (options.order === 2) ? Math.sqrt(best) : best ** (1 / options.order);
|
best = (!options.order || options.order === 2) ? Math.sqrt(best) : best ** (1 / options.order);
|
||||||
return { index, distance: best, similarity: Math.max(0, 100 - best) / 100.0 };
|
return { index, distance: best, similarity: Math.max(0, 100 - best) / 100.0 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,7 +212,7 @@ export class Human {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Validate current configuration schema */
|
/** Validate current configuration schema */
|
||||||
public validate(userConfig?: Partial<Config>) {
|
validate(userConfig?: Partial<Config>) {
|
||||||
return validate(defaults, userConfig || this.config);
|
return validate(defaults, userConfig || this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,7 +294,7 @@ export class Human {
|
||||||
await tf.ready();
|
await tf.ready();
|
||||||
if (this.env.browser) {
|
if (this.env.browser) {
|
||||||
if (this.config.debug) log('configuration:', this.config);
|
if (this.config.debug) log('configuration:', this.config);
|
||||||
if (this.config.debug) log('tf flags:', this.tf.ENV.flags);
|
if (this.config.debug) log('tf flags:', this.tf.ENV['flags']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
debug: true,
|
debug: true,
|
||||||
cacheSensitivity: 0,
|
cacheSensitivity: 0,
|
||||||
object: { enabled: true },
|
object: { enabled: true },
|
||||||
}
|
};
|
||||||
|
|
||||||
const backends = ['wasm', 'webgl', 'humangl', 'webgpu'];
|
const backends = ['wasm', 'webgl', 'humangl', 'webgpu'];
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@
|
||||||
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')}`;
|
||||||
const elap = (dt - last).toString().padStart(5, '0');
|
const elap = (dt - last).toString().padStart(5, '0');
|
||||||
document.getElementById('log').innerHTML += ts + ' +' + elap + 'ms' + ' ' + str(...msgs);
|
document.getElementById('log').innerHTML += ts + ' +' + elap + 'ms  ' + str(...msgs);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(ts, elap, ...msgs);
|
console.log(ts, elap, ...msgs);
|
||||||
last = dt;
|
last = dt;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +64,7 @@
|
||||||
async function image(url) {
|
async function image(url) {
|
||||||
const el = document.createElement('img');
|
const el = document.createElement('img');
|
||||||
el.id = 'image';
|
el.id = 'image';
|
||||||
const loaded = new Promise((resolve) => { el.onload = () => resolve(true) });
|
const loaded = new Promise((resolve) => { el.onload = () => resolve(true); });
|
||||||
el.src = url;
|
el.src = url;
|
||||||
await loaded;
|
await loaded;
|
||||||
return el;
|
return el;
|
||||||
|
@ -88,7 +89,7 @@
|
||||||
async function main() {
|
async function main() {
|
||||||
log('human tests');
|
log('human tests');
|
||||||
let res;
|
let res;
|
||||||
let human = new Human(config);
|
const human = new Human(config);
|
||||||
await human.init();
|
await human.init();
|
||||||
human.events.addEventListener('warmup', () => events('warmup'));
|
human.events.addEventListener('warmup', () => events('warmup'));
|
||||||
human.events.addEventListener('image', () => events('image'));
|
human.events.addEventListener('image', () => events('image'));
|
||||||
|
@ -110,7 +111,7 @@
|
||||||
log({ memory: human.tf.memory() });
|
log({ memory: human.tf.memory() });
|
||||||
res = await human.validate();
|
res = await human.validate();
|
||||||
log({ validate: res });
|
log({ validate: res });
|
||||||
res = await human.warmup({ warmup: 'face'});
|
res = await human.warmup({ warmup: 'face' });
|
||||||
draw(res.canvas);
|
draw(res.canvas);
|
||||||
log({ warmup: 'face' });
|
log({ warmup: 'face' });
|
||||||
let img = await image('../../samples/in/ai-body.jpg');
|
let img = await image('../../samples/in/ai-body.jpg');
|
||||||
|
@ -119,11 +120,11 @@
|
||||||
draw(res.canvas);
|
draw(res.canvas);
|
||||||
res = await human.detect(input.tensor);
|
res = await human.detect(input.tensor);
|
||||||
log({ detect: true });
|
log({ detect: true });
|
||||||
const interpolated = human.next();
|
human.next();
|
||||||
log({ interpolated: true });
|
log({ interpolated: true });
|
||||||
const persons = res.persons;
|
const persons = res.persons;
|
||||||
log({ persons: true });
|
log({ persons: true });
|
||||||
log({ summary: { persons: persons.length, face: res.face.length, body: res.body.length, hand: res.hand.length, object: res.object.length, gesture: res.gesture.length }});
|
log({ summary: { persons: persons.length, face: res.face.length, body: res.body.length, hand: res.hand.length, object: res.object.length, gesture: res.gesture.length } });
|
||||||
log({ performance: human.performance });
|
log({ performance: human.performance });
|
||||||
human.tf.dispose(input.tensor);
|
human.tf.dispose(input.tensor);
|
||||||
draw();
|
draw();
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"outDir": "types",
|
"outDir": "types",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": { "tslib": ["./node_modules/tslib/tslib.d.ts"] },
|
"paths": { "tslib": ["./node_modules/tslib/tslib.d.ts"] },
|
||||||
|
"lib": ["es2020", "dom", "webworker"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"allowSyntheticDefaultImports": false,
|
"allowSyntheticDefaultImports": false,
|
||||||
"allowUnreachableCode": false,
|
"allowUnreachableCode": false,
|
||||||
|
@ -50,7 +51,7 @@
|
||||||
"tabSize": 2
|
"tabSize": 2
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules/", "types/", "tfjs/", "dist/"],
|
"exclude": ["node_modules/", "types/", "tfjs/", "dist/"],
|
||||||
"include": ["src"],
|
"include": ["src", "types/human.d.ts"],
|
||||||
"typedocOptions": {
|
"typedocOptions": {
|
||||||
"externalPattern": ["node_modules/", "tfjs/"]
|
"externalPattern": ["node_modules/", "tfjs/"]
|
||||||
}
|
}
|
||||||
|
|
2
wiki
2
wiki
|
@ -1 +1 @@
|
||||||
Subproject commit 82ade650a7cd593e29a98a8b8a1cba893e14c2f0
|
Subproject commit 20389b9779834324acbbcf2b25041a489a688d18
|
Loading…
Reference in New Issue