mirror of https://github.com/vladmandic/human
add curve draw output
parent
eac219509e
commit
456d34ee22
|
@ -71,7 +71,7 @@ export default {
|
|||
// 'blazeface-back' is blazeface model optimized for smaller and/or distanct faces
|
||||
// 'faceboxes' is alternative model to 'blazeface'
|
||||
inputSize: 256, // fixed value: 128 for front and 256 for 'back'
|
||||
rotation: false, // use best-guess rotated face image or just box with rotation as-is
|
||||
rotation: true, // use best-guess rotated face image or just box with rotation as-is
|
||||
// false means higher performance, but incorrect mesh mapping if face angle is above 20 degrees
|
||||
maxFaces: 10, // maximum number of faces detected in the input
|
||||
// should be set to the minimum number for performance
|
||||
|
|
|
@ -2,7 +2,7 @@ import Human from '../dist/human.esm.js'; // equivalent of @vladmandic/human
|
|||
import Menu from './menu.js';
|
||||
import GLBench from './gl-bench.js';
|
||||
|
||||
const userConfig = { }; // add any user configuration overrides
|
||||
const userConfig = { backend: 'wasm' }; // add any user configuration overrides
|
||||
|
||||
/*
|
||||
const userConfig = {
|
||||
|
@ -40,11 +40,11 @@ const ui = {
|
|||
drawFPS: [], // internal, holds fps values for draw performance
|
||||
buffered: false, // experimental, should output be buffered between frames
|
||||
drawWarmup: false, // debug only, should warmup image processing be displayed on startup
|
||||
drawThread: null, // perform draw operations in a separate thread
|
||||
detectThread: null, // perform detect operations in a separate thread
|
||||
drawThread: null, // internl, perform draw operations in a separate thread
|
||||
detectThread: null, // internl, perform detect operations in a separate thread
|
||||
framesDraw: 0, // internal, statistics on frames drawn
|
||||
framesDetect: 0, // internal, statistics on frames detected
|
||||
bench: false, // show gl fps benchmark window
|
||||
bench: true, // show gl fps benchmark window
|
||||
lastFrame: 0, // time of last frame processing
|
||||
};
|
||||
|
||||
|
@ -110,7 +110,11 @@ async function drawResults(input) {
|
|||
await menu.process.updateChart('FPS', ui.detectFPS);
|
||||
|
||||
// get updated canvas
|
||||
if (ui.buffered || !result.canvas) result.canvas = await human.image(input).canvas;
|
||||
if (ui.buffered || !result.canvas) {
|
||||
const image = await human.image(input);
|
||||
result.canvas = image.canvas;
|
||||
human.tf.dispose(image.tensor);
|
||||
}
|
||||
|
||||
// draw image from video
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
@ -421,10 +425,10 @@ function setupMenu() {
|
|||
menu.display.addHTML('<hr style="border-style: inset; border-color: dimgray">');
|
||||
menu.display.addBool('use 3D depth', human.draw.options, 'useDepth');
|
||||
menu.display.addBool('print labels', human.draw.options, 'drawLabels');
|
||||
menu.display.addBool('draw points', human.draw.options, 'drawPoints');
|
||||
menu.display.addBool('draw boxes', human.draw.options, 'drawBoxes');
|
||||
menu.display.addBool('draw polygons', human.draw.options, 'drawPolygons');
|
||||
menu.display.addBool('Fill Polygons', human.draw.options, 'fillPolygons');
|
||||
menu.display.addBool('draw points', human.draw.options, 'drawPoints');
|
||||
menu.display.addBool('fill polygons', human.draw.options, 'fillPolygons');
|
||||
|
||||
menu.image = new Menu(document.body, '', { top: `${document.getElementById('menubar').offsetHeight}px`, left: x[1] });
|
||||
menu.image.addBool('enabled', human.config.filter, 'enabled', (val) => human.config.filter.enabled = val);
|
||||
|
@ -449,8 +453,8 @@ function setupMenu() {
|
|||
menu.process = new Menu(document.body, '', { top: `${document.getElementById('menubar').offsetHeight}px`, left: x[2] });
|
||||
menu.process.addList('backend', ['cpu', 'webgl', 'wasm', 'humangl'], human.config.backend, (val) => human.config.backend = val);
|
||||
menu.process.addBool('async operations', human.config, 'async', (val) => human.config.async = val);
|
||||
menu.process.addBool('enable profiler', human.config, 'profile', (val) => human.config.profile = val);
|
||||
menu.process.addBool('memory shield', human.config, 'deallocate', (val) => human.config.deallocate = val);
|
||||
// menu.process.addBool('enable profiler', human.config, 'profile', (val) => human.config.profile = val);
|
||||
// menu.process.addBool('memory shield', human.config, 'deallocate', (val) => human.config.deallocate = val);
|
||||
menu.process.addBool('use web worker', ui, 'useWorker');
|
||||
menu.process.addHTML('<hr style="border-style: inset; border-color: dimgray">');
|
||||
menu.process.addLabel('model parameters');
|
||||
|
@ -526,7 +530,6 @@ async function drawWarmup(res) {
|
|||
|
||||
async function main() {
|
||||
log('Demo starting ...');
|
||||
log('Browser:', navigator?.userAgent);
|
||||
setupMenu();
|
||||
document.getElementById('log').innerText = `Human: version ${human.version}`;
|
||||
if (ui.modelsPreload && !ui.useWorker) {
|
||||
|
|
|
@ -20,7 +20,7 @@ const UISVG = `
|
|||
<svg viewBox="0 0 55 60">
|
||||
<text x="27" y="56" class="gl-fps">00 FPS</text>
|
||||
<text x="30" y="8" class="gl-mem"></text>
|
||||
<rect x="0" y="14" rx="4" ry="4" width="55" height="32"></rect>
|
||||
<rect x="0" y="14" rx="4" ry="4" width="65" height="32"></rect>
|
||||
<polyline class="gl-chart"></polyline>
|
||||
</svg>
|
||||
<svg viewBox="0 0 14 60" class="gl-cpu-svg">
|
||||
|
|
|
@ -18,7 +18,7 @@ let theme = {
|
|||
function createCSS() {
|
||||
if (CSScreated) return;
|
||||
const css = `
|
||||
:root { --rounded: 0.2rem; }
|
||||
:root { --rounded: 0.1rem; }
|
||||
.menu { position: absolute; top: 0rem; right: 0; width: max-content; padding: 0 0.2rem 0 0.2rem; line-height: 1.8rem; z-index: 10;
|
||||
box-shadow: 0 0 8px dimgrey; background: ${theme.background}; border-radius: var(--rounded); border-color: black; border-style: solid; border-width: thin; }
|
||||
|
||||
|
|
26
demo/node.js
26
demo/node.js
|
@ -40,8 +40,6 @@ async function init() {
|
|||
// pre-load models
|
||||
log.info('Human:', human.version);
|
||||
log.info('Active Configuration', human.config);
|
||||
log.info('TFJS Version:', human.tf.version_core, 'Backend:', tf.getBackend());
|
||||
log.info('TFJS Flags:', human.tf.env().features);
|
||||
await human.load();
|
||||
const loaded = Object.keys(human.models).filter((a) => human.models[a]);
|
||||
log.info('Loaded:', loaded);
|
||||
|
@ -63,27 +61,31 @@ async function detect(input) {
|
|||
// dispose image tensor as we no longer need it
|
||||
image.dispose();
|
||||
// print data to console
|
||||
log.data(result);
|
||||
log.data('Face: ', result.face);
|
||||
log.data('Body:', result.body);
|
||||
log.data('Hand:', result.hand);
|
||||
log.data('Gesture:', result.gesture);
|
||||
}
|
||||
|
||||
async function test() {
|
||||
// test with embedded face image
|
||||
// test with embedded full body image
|
||||
let result;
|
||||
|
||||
log.state('Processing embedded warmup image: face');
|
||||
myConfig.warmup = 'face';
|
||||
const resultFace = await human.warmup(myConfig);
|
||||
log.data('Face: ', resultFace.face);
|
||||
result = await human.warmup(myConfig);
|
||||
log.data('Face: ', result.face);
|
||||
|
||||
// test with embedded full body image
|
||||
log.state('Processing embedded warmup image: full');
|
||||
myConfig.warmup = 'full';
|
||||
const resultFull = await human.warmup(myConfig);
|
||||
log.data('Body:', resultFull.body);
|
||||
log.data('Hand:', resultFull.hand);
|
||||
log.data('Gesture:', resultFull.gesture);
|
||||
result = await human.warmup(myConfig);
|
||||
log.data('Body:', result.body);
|
||||
log.data('Hand:', result.hand);
|
||||
log.data('Gesture:', result.gesture);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
log.info('NodeJS:', process.version);
|
||||
log.header();
|
||||
log.info('Current folder:', process.env.PWD);
|
||||
await init();
|
||||
if (process.argv.length !== 3) {
|
||||
|
|
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 @@
|
|||
{
|
||||
"inputs": {
|
||||
"dist/human.esm.js": {
|
||||
"bytes": 1351634,
|
||||
"bytes": 1352771,
|
||||
"imports": []
|
||||
},
|
||||
"demo/menu.js": {
|
||||
|
@ -13,7 +13,7 @@
|
|||
"imports": []
|
||||
},
|
||||
"demo/browser.js": {
|
||||
"bytes": 27915,
|
||||
"bytes": 27984,
|
||||
"imports": [
|
||||
{
|
||||
"path": "dist/human.esm.js",
|
||||
|
@ -35,7 +35,7 @@
|
|||
"imports": [],
|
||||
"exports": [],
|
||||
"inputs": {},
|
||||
"bytes": 2061107
|
||||
"bytes": 2059743
|
||||
},
|
||||
"dist/demo-browser-index.js": {
|
||||
"imports": [],
|
||||
|
@ -43,7 +43,7 @@
|
|||
"entryPoint": "demo/browser.js",
|
||||
"inputs": {
|
||||
"dist/human.esm.js": {
|
||||
"bytesInOutput": 1344140
|
||||
"bytesInOutput": 1345275
|
||||
},
|
||||
"demo/menu.js": {
|
||||
"bytesInOutput": 10696
|
||||
|
@ -52,10 +52,10 @@
|
|||
"bytesInOutput": 6759
|
||||
},
|
||||
"demo/browser.js": {
|
||||
"bytesInOutput": 17673
|
||||
"bytesInOutput": 17494
|
||||
}
|
||||
},
|
||||
"bytes": 1386653
|
||||
"bytes": 1387609
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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
|
@ -4,6 +4,10 @@
|
|||
"bytes": 401,
|
||||
"imports": []
|
||||
},
|
||||
"src/sysinfo.ts": {
|
||||
"bytes": 612,
|
||||
"imports": []
|
||||
},
|
||||
"dist/tfjs.esm.js": {
|
||||
"bytes": 1065682,
|
||||
"imports": []
|
||||
|
@ -52,7 +56,7 @@
|
|||
"imports": []
|
||||
},
|
||||
"src/blazeface/facepipeline.ts": {
|
||||
"bytes": 14154,
|
||||
"bytes": 14094,
|
||||
"imports": [
|
||||
{
|
||||
"path": "dist/tfjs.esm.js",
|
||||
|
@ -274,7 +278,7 @@
|
|||
]
|
||||
},
|
||||
"src/posenet/util.ts": {
|
||||
"bytes": 2017,
|
||||
"bytes": 2041,
|
||||
"imports": [
|
||||
{
|
||||
"path": "src/posenet/keypoints.ts",
|
||||
|
@ -388,7 +392,7 @@
|
|||
"imports": []
|
||||
},
|
||||
"src/blazepose/blazepose.ts": {
|
||||
"bytes": 3259,
|
||||
"bytes": 2939,
|
||||
"imports": [
|
||||
{
|
||||
"path": "src/log.ts",
|
||||
|
@ -442,11 +446,11 @@
|
|||
"imports": []
|
||||
},
|
||||
"package.json": {
|
||||
"bytes": 2594,
|
||||
"bytes": 2581,
|
||||
"imports": []
|
||||
},
|
||||
"src/draw.ts": {
|
||||
"bytes": 19435,
|
||||
"bytes": 16222,
|
||||
"imports": [
|
||||
{
|
||||
"path": "config.js",
|
||||
|
@ -459,12 +463,16 @@
|
|||
]
|
||||
},
|
||||
"src/human.ts": {
|
||||
"bytes": 21110,
|
||||
"bytes": 21589,
|
||||
"imports": [
|
||||
{
|
||||
"path": "src/log.ts",
|
||||
"kind": "import-statement"
|
||||
},
|
||||
{
|
||||
"path": "src/sysinfo.ts",
|
||||
"kind": "import-statement"
|
||||
},
|
||||
{
|
||||
"path": "dist/tfjs.esm.js",
|
||||
"kind": "import-statement"
|
||||
|
@ -545,7 +553,7 @@
|
|||
"imports": [],
|
||||
"exports": [],
|
||||
"inputs": {},
|
||||
"bytes": 1979344
|
||||
"bytes": 1977987
|
||||
},
|
||||
"dist/human.esm.js": {
|
||||
"imports": [],
|
||||
|
@ -563,8 +571,11 @@
|
|||
"src/log.ts": {
|
||||
"bytesInOutput": 252
|
||||
},
|
||||
"src/sysinfo.ts": {
|
||||
"bytesInOutput": 394
|
||||
},
|
||||
"dist/tfjs.esm.js": {
|
||||
"bytesInOutput": 1056715
|
||||
"bytesInOutput": 1056721
|
||||
},
|
||||
"src/tfjs/backend.ts": {
|
||||
"bytesInOutput": 1053
|
||||
|
@ -582,10 +593,10 @@
|
|||
"bytesInOutput": 28983
|
||||
},
|
||||
"src/blazeface/facepipeline.ts": {
|
||||
"bytesInOutput": 5054
|
||||
"bytesInOutput": 5043
|
||||
},
|
||||
"src/human.ts": {
|
||||
"bytesInOutput": 11325
|
||||
"bytesInOutput": 11629
|
||||
},
|
||||
"src/faceboxes/faceboxes.ts": {
|
||||
"bytesInOutput": 1576
|
||||
|
@ -630,7 +641,7 @@
|
|||
"bytesInOutput": 529
|
||||
},
|
||||
"src/posenet/util.ts": {
|
||||
"bytesInOutput": 354
|
||||
"bytesInOutput": 378
|
||||
},
|
||||
"src/handpose/handpose.ts": {
|
||||
"bytesInOutput": 1281
|
||||
|
@ -672,13 +683,13 @@
|
|||
"bytesInOutput": 55295
|
||||
},
|
||||
"package.json": {
|
||||
"bytesInOutput": 2596
|
||||
"bytesInOutput": 2583
|
||||
},
|
||||
"src/draw.ts": {
|
||||
"bytesInOutput": 9164
|
||||
"bytesInOutput": 9597
|
||||
}
|
||||
},
|
||||
"bytes": 1351634
|
||||
"bytes": 1352771
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
"bytes": 401,
|
||||
"imports": []
|
||||
},
|
||||
"src/sysinfo.ts": {
|
||||
"bytes": 612,
|
||||
"imports": []
|
||||
},
|
||||
"dist/tfjs.esm.js": {
|
||||
"bytes": 1065682,
|
||||
"imports": []
|
||||
|
@ -52,7 +56,7 @@
|
|||
"imports": []
|
||||
},
|
||||
"src/blazeface/facepipeline.ts": {
|
||||
"bytes": 14154,
|
||||
"bytes": 14094,
|
||||
"imports": [
|
||||
{
|
||||
"path": "dist/tfjs.esm.js",
|
||||
|
@ -274,7 +278,7 @@
|
|||
]
|
||||
},
|
||||
"src/posenet/util.ts": {
|
||||
"bytes": 2017,
|
||||
"bytes": 2041,
|
||||
"imports": [
|
||||
{
|
||||
"path": "src/posenet/keypoints.ts",
|
||||
|
@ -388,7 +392,7 @@
|
|||
"imports": []
|
||||
},
|
||||
"src/blazepose/blazepose.ts": {
|
||||
"bytes": 3259,
|
||||
"bytes": 2939,
|
||||
"imports": [
|
||||
{
|
||||
"path": "src/log.ts",
|
||||
|
@ -442,11 +446,11 @@
|
|||
"imports": []
|
||||
},
|
||||
"package.json": {
|
||||
"bytes": 2594,
|
||||
"bytes": 2581,
|
||||
"imports": []
|
||||
},
|
||||
"src/draw.ts": {
|
||||
"bytes": 19435,
|
||||
"bytes": 16222,
|
||||
"imports": [
|
||||
{
|
||||
"path": "config.js",
|
||||
|
@ -459,12 +463,16 @@
|
|||
]
|
||||
},
|
||||
"src/human.ts": {
|
||||
"bytes": 21110,
|
||||
"bytes": 21589,
|
||||
"imports": [
|
||||
{
|
||||
"path": "src/log.ts",
|
||||
"kind": "import-statement"
|
||||
},
|
||||
{
|
||||
"path": "src/sysinfo.ts",
|
||||
"kind": "import-statement"
|
||||
},
|
||||
{
|
||||
"path": "dist/tfjs.esm.js",
|
||||
"kind": "import-statement"
|
||||
|
@ -545,7 +553,7 @@
|
|||
"imports": [],
|
||||
"exports": [],
|
||||
"inputs": {},
|
||||
"bytes": 1979355
|
||||
"bytes": 1977998
|
||||
},
|
||||
"dist/human.ts": {
|
||||
"imports": [],
|
||||
|
@ -559,13 +567,16 @@
|
|||
"bytesInOutput": 1690
|
||||
},
|
||||
"src/human.ts": {
|
||||
"bytesInOutput": 11361
|
||||
"bytesInOutput": 11665
|
||||
},
|
||||
"src/log.ts": {
|
||||
"bytesInOutput": 252
|
||||
},
|
||||
"src/sysinfo.ts": {
|
||||
"bytesInOutput": 394
|
||||
},
|
||||
"dist/tfjs.esm.js": {
|
||||
"bytesInOutput": 1056715
|
||||
"bytesInOutput": 1056721
|
||||
},
|
||||
"src/tfjs/backend.ts": {
|
||||
"bytesInOutput": 1053
|
||||
|
@ -583,7 +594,7 @@
|
|||
"bytesInOutput": 28983
|
||||
},
|
||||
"src/blazeface/facepipeline.ts": {
|
||||
"bytesInOutput": 5054
|
||||
"bytesInOutput": 5043
|
||||
},
|
||||
"src/faceboxes/faceboxes.ts": {
|
||||
"bytesInOutput": 1576
|
||||
|
@ -628,7 +639,7 @@
|
|||
"bytesInOutput": 529
|
||||
},
|
||||
"src/posenet/util.ts": {
|
||||
"bytesInOutput": 354
|
||||
"bytesInOutput": 378
|
||||
},
|
||||
"src/handpose/handpose.ts": {
|
||||
"bytesInOutput": 1281
|
||||
|
@ -670,13 +681,13 @@
|
|||
"bytesInOutput": 55295
|
||||
},
|
||||
"package.json": {
|
||||
"bytesInOutput": 2596
|
||||
"bytesInOutput": 2583
|
||||
},
|
||||
"src/draw.ts": {
|
||||
"bytesInOutput": 9164
|
||||
"bytesInOutput": 9597
|
||||
}
|
||||
},
|
||||
"bytes": 1351676
|
||||
"bytes": 1352813
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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
|
@ -4,6 +4,10 @@
|
|||
"bytes": 401,
|
||||
"imports": []
|
||||
},
|
||||
"src/sysinfo.ts": {
|
||||
"bytes": 612,
|
||||
"imports": []
|
||||
},
|
||||
"dist/tfjs.esm.js": {
|
||||
"bytes": 737,
|
||||
"imports": []
|
||||
|
@ -52,7 +56,7 @@
|
|||
"imports": []
|
||||
},
|
||||
"src/blazeface/facepipeline.ts": {
|
||||
"bytes": 14154,
|
||||
"bytes": 14094,
|
||||
"imports": [
|
||||
{
|
||||
"path": "dist/tfjs.esm.js",
|
||||
|
@ -274,7 +278,7 @@
|
|||
]
|
||||
},
|
||||
"src/posenet/util.ts": {
|
||||
"bytes": 2017,
|
||||
"bytes": 2041,
|
||||
"imports": [
|
||||
{
|
||||
"path": "src/posenet/keypoints.ts",
|
||||
|
@ -388,7 +392,7 @@
|
|||
"imports": []
|
||||
},
|
||||
"src/blazepose/blazepose.ts": {
|
||||
"bytes": 3259,
|
||||
"bytes": 2939,
|
||||
"imports": [
|
||||
{
|
||||
"path": "src/log.ts",
|
||||
|
@ -442,11 +446,11 @@
|
|||
"imports": []
|
||||
},
|
||||
"package.json": {
|
||||
"bytes": 2594,
|
||||
"bytes": 2581,
|
||||
"imports": []
|
||||
},
|
||||
"src/draw.ts": {
|
||||
"bytes": 19435,
|
||||
"bytes": 16222,
|
||||
"imports": [
|
||||
{
|
||||
"path": "config.js",
|
||||
|
@ -459,12 +463,16 @@
|
|||
]
|
||||
},
|
||||
"src/human.ts": {
|
||||
"bytes": 21110,
|
||||
"bytes": 21589,
|
||||
"imports": [
|
||||
{
|
||||
"path": "src/log.ts",
|
||||
"kind": "import-statement"
|
||||
},
|
||||
{
|
||||
"path": "src/sysinfo.ts",
|
||||
"kind": "import-statement"
|
||||
},
|
||||
{
|
||||
"path": "dist/tfjs.esm.js",
|
||||
"kind": "import-statement"
|
||||
|
@ -545,7 +553,7 @@
|
|||
"imports": [],
|
||||
"exports": [],
|
||||
"inputs": {},
|
||||
"bytes": 746101
|
||||
"bytes": 744738
|
||||
},
|
||||
"dist/human.node-gpu.js": {
|
||||
"imports": [],
|
||||
|
@ -562,11 +570,14 @@
|
|||
"bytesInOutput": 1677
|
||||
},
|
||||
"src/human.ts": {
|
||||
"bytesInOutput": 11332
|
||||
"bytesInOutput": 11632
|
||||
},
|
||||
"src/log.ts": {
|
||||
"bytesInOutput": 251
|
||||
},
|
||||
"src/sysinfo.ts": {
|
||||
"bytesInOutput": 394
|
||||
},
|
||||
"src/tfjs/backend.ts": {
|
||||
"bytesInOutput": 1145
|
||||
},
|
||||
|
@ -574,7 +585,7 @@
|
|||
"bytesInOutput": 2338
|
||||
},
|
||||
"src/blazeface/facepipeline.ts": {
|
||||
"bytesInOutput": 5101
|
||||
"bytesInOutput": 5090
|
||||
},
|
||||
"src/blazeface/box.ts": {
|
||||
"bytesInOutput": 854
|
||||
|
@ -628,7 +639,7 @@
|
|||
"bytesInOutput": 525
|
||||
},
|
||||
"src/posenet/util.ts": {
|
||||
"bytesInOutput": 352
|
||||
"bytesInOutput": 376
|
||||
},
|
||||
"src/handpose/handpose.ts": {
|
||||
"bytesInOutput": 1322
|
||||
|
@ -670,13 +681,13 @@
|
|||
"bytesInOutput": 55295
|
||||
},
|
||||
"package.json": {
|
||||
"bytesInOutput": 2593
|
||||
"bytesInOutput": 2580
|
||||
},
|
||||
"src/draw.ts": {
|
||||
"bytesInOutput": 9054
|
||||
"bytesInOutput": 9486
|
||||
}
|
||||
},
|
||||
"bytes": 289059
|
||||
"bytes": 290185
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -2866,9 +2866,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.3.0-dev.20210305",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.0-dev.20210305.tgz",
|
||||
"integrity": "sha512-OTALeeen7kl6FU1tcXRk3h+WY1NnE5lwyTGAZUCt9hw6tdaifgLXqEkfw9NHJc0xKV6PnU8GgnYFFVVyHLPSHg==",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
|
||||
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
|
||||
"dev": true
|
||||
},
|
||||
"unbox-primitive": {
|
||||
|
@ -2899,9 +2899,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz",
|
||||
"integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
|
||||
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
|
||||
"dev": true
|
||||
},
|
||||
"validate-npm-package-license": {
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
"seedrandom": "^3.0.5",
|
||||
"simple-git": "^2.36.0",
|
||||
"tslib": "^2.1.0",
|
||||
"typescript": "^4.3.0-dev.20210305"
|
||||
"typescript": "^4.2.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught --no-deprecation src/node.js",
|
||||
|
|
|
@ -19,8 +19,9 @@ const IRIS_LOWER_CENTER_INDEX = 4;
|
|||
const IRIS_IRIS_INDEX = 71;
|
||||
const IRIS_NUM_COORDINATES = 76;
|
||||
|
||||
// Replace the raw coordinates returned by facemesh with refined iris model coordinates. Update the z coordinate to be an average of the original and the new. This produces the best visual effect.
|
||||
function replaceRawCoordinates(rawCoords, newCoords, prefix, keys = null) {
|
||||
// Replace the raw coordinates returned by facemesh with refined iris model coordinates
|
||||
// Update the z coordinate to be an average of the original and the new.
|
||||
function replaceRawCoordinates(rawCoords, newCoords, prefix, keys) {
|
||||
for (let i = 0; i < coords.MESH_TO_IRIS_INDICES_MAP.length; i++) {
|
||||
const { key, indices } = coords.MESH_TO_IRIS_INDICES_MAP[i];
|
||||
const originalIndices = coords.MESH_ANNOTATIONS[`${prefix}${key}`];
|
||||
|
@ -179,7 +180,7 @@ export class Pipeline {
|
|||
});
|
||||
}
|
||||
|
||||
// log('face', `skipped: ${this.skipped} max: ${config.face.detector.maxFaces} detected: ${this.detectedFaces} stored: ${this.storedBoxes.length} new: ${detector?.boxes?.length}`);
|
||||
// console.log('face', `skipped: ${this.skipped} max: ${config.face.detector.maxFaces} detected: ${this.detectedFaces} stored: ${this.storedBoxes.length} new: ${detector?.boxes?.length}`);
|
||||
let results = tf.tidy(() => this.storedBoxes.map((box, i) => {
|
||||
// The facial bounding box landmarks could come either from blazeface (if we are using a fresh box), or from the mesh model (if we are reusing an old box).
|
||||
let face;
|
||||
|
@ -211,7 +212,7 @@ export class Pipeline {
|
|||
return prediction;
|
||||
}
|
||||
|
||||
const [, confidence, contourCoords] = this.meshDetector.predict(face); // The first returned tensor represents facial contours, which are included in the coordinates.
|
||||
const [, confidence, contourCoords] = this.meshDetector.predict(face); // The first returned tensor represents facial contours which are already included in the coordinates.
|
||||
const faceConfidence = confidence.dataSync()[0];
|
||||
if (faceConfidence < config.face.detector.minConfidence) return null; // if below confidence just exit
|
||||
const coordsReshaped = tf.reshape(contourCoords, [-1, 3]);
|
||||
|
@ -228,14 +229,13 @@ export class Pipeline {
|
|||
const { rawCoords: rightEyeRawCoords, iris: rightIrisRawCoords } = this.getEyeCoords(rightEyeData, rightEyeBox, rightEyeBoxSize);
|
||||
const leftToRightEyeDepthDifference = this.getLeftToRightEyeDepthDifference(rawCoords);
|
||||
if (Math.abs(leftToRightEyeDepthDifference) < 30) { // User is looking straight ahead.
|
||||
replaceRawCoordinates(rawCoords, leftEyeRawCoords, 'left');
|
||||
replaceRawCoordinates(rawCoords, rightEyeRawCoords, 'right');
|
||||
// If the user is looking to the left or to the right, the iris coordinates tend to diverge too much from the mesh coordinates for them to be merged. So we only update a single contour line above and below the eye.
|
||||
replaceRawCoordinates(rawCoords, leftEyeRawCoords, 'left', null);
|
||||
replaceRawCoordinates(rawCoords, rightEyeRawCoords, 'right', null);
|
||||
// If the user is looking to the left or to the right, the iris coordinates tend to diverge too much from the mesh coordinates for them to be merged
|
||||
// So we only update a single contour line above and below the eye.
|
||||
} else if (leftToRightEyeDepthDifference < 1) { // User is looking towards the right.
|
||||
// @ts-ignore
|
||||
replaceRawCoordinates(rawCoords, leftEyeRawCoords, 'left', ['EyeUpper0', 'EyeLower0']);
|
||||
} else { // User is looking towards the left.
|
||||
// @ts-ignore
|
||||
replaceRawCoordinates(rawCoords, rightEyeRawCoords, 'right', ['EyeUpper0', 'EyeLower0']);
|
||||
}
|
||||
const adjustedLeftIrisCoords = this.getAdjustedIrisCoords(rawCoords, leftIrisRawCoords, 'left');
|
||||
|
@ -256,7 +256,7 @@ export class Pipeline {
|
|||
rawCoords,
|
||||
};
|
||||
if (!config.face.mesh.returnRawData) delete prediction.rawCoords;
|
||||
this.storedBoxes[i] = { ...squarifiedLandmarksBox, landmarks: transformedCoords.arraySync(), confidence: box.confidence, faceConfidence };
|
||||
this.storedBoxes[i] = { ...squarifiedLandmarksBox, landmarks: transformedCoordsData, confidence: box.confidence, faceConfidence };
|
||||
|
||||
return prediction;
|
||||
}));
|
||||
|
|
|
@ -26,10 +26,11 @@ export async function predict(image, config) {
|
|||
let points;
|
||||
if (!config.profile) { // run through profiler or just execute
|
||||
const resT = await model.predict(normalize);
|
||||
// const segmentationT = resT.find((t) => (t.size === 16384 || t.size === 0)).squeeze();
|
||||
// const segmentationT = resT.find((t) => (t.size === 16384))?.squeeze();
|
||||
// const segmentation = segmentationT.arraySync(); // array 128 x 128
|
||||
// tf.dispose(segmentationT);
|
||||
// segmentationT.dispose();
|
||||
points = resT.find((t) => (t.size === 195 || t.size === 155)).dataSync(); // order of output tensors may change between models, full has 195 and upper has 155 items
|
||||
// console.log(resT, points, segmentation);
|
||||
resT.forEach((t) => t.dispose());
|
||||
} else {
|
||||
const profileData = await tf.profile(() => model.predict(normalize));
|
||||
|
@ -57,11 +58,3 @@ export async function predict(image, config) {
|
|||
// console.log('POINTS', imgSize, pts.length, pts);
|
||||
return [{ keypoints }];
|
||||
}
|
||||
|
||||
/*
|
||||
Model card:
|
||||
- https://drive.google.com/file/d/10IU-DRP2ioSNjKFdiGbmmQX81xAYj88s/view
|
||||
Download:
|
||||
- https://github.com/PINTO0309/PINTO_model_zoo/tree/main/058_BlazePose_Full_Keypoints/10_new_256x256/saved_model/tfjs_model_float16
|
||||
- https://github.com/PINTO0309/PINTO_model_zoo/tree/main/053_BlazePose/20_new_256x256/saved_model/tfjs_model_float16
|
||||
*/
|
||||
|
|
265
src/draw.ts
265
src/draw.ts
|
@ -9,13 +9,14 @@ export const options = {
|
|||
lineHeight: 20,
|
||||
lineWidth: 6,
|
||||
pointSize: 2,
|
||||
roundRect: 8,
|
||||
roundRect: 28,
|
||||
drawPoints: false,
|
||||
drawLabels: true,
|
||||
drawBoxes: true,
|
||||
drawPolygons: true,
|
||||
fillPolygons: false,
|
||||
useDepth: true,
|
||||
useCurves: true,
|
||||
bufferedOutput: false,
|
||||
};
|
||||
|
||||
|
@ -27,9 +28,13 @@ function point(ctx, x, y) {
|
|||
}
|
||||
|
||||
function rect(ctx, x, y, width, height) {
|
||||
if (options.roundRect && options.roundRect > 0) {
|
||||
ctx.beginPath();
|
||||
if (options.useCurves) {
|
||||
const cx = (x + x + width) / 2;
|
||||
const cy = (y + y + height) / 2;
|
||||
ctx.ellipse(cx, cy, width / 2, height / 2, 0, 0, 2 * Math.PI);
|
||||
} else {
|
||||
ctx.lineWidth = options.lineWidth;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + options.roundRect, y);
|
||||
ctx.lineTo(x + width - options.roundRect, y);
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + options.roundRect);
|
||||
|
@ -40,122 +45,40 @@ function rect(ctx, x, y, width, height) {
|
|||
ctx.lineTo(x, y + options.roundRect);
|
||||
ctx.quadraticCurveTo(x, y, x + options.roundRect, y);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
} else {
|
||||
rect(ctx, x, y, width, height);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
function lines(ctx, points) {
|
||||
function lines(ctx, points: number[] = []) {
|
||||
if (points === undefined || points.length === 0) return;
|
||||
ctx.beginPath();
|
||||
const path = new Path2D();
|
||||
path.moveTo(points[0][0], points[0][1]);
|
||||
for (const pt of points) {
|
||||
path.lineTo(pt[0], parseInt(pt[1]));
|
||||
}
|
||||
ctx.stroke(path);
|
||||
ctx.moveTo(points[0][0], points[0][1]);
|
||||
for (const pt of points) ctx.lineTo(pt[0], parseInt(pt[1]));
|
||||
ctx.stroke();
|
||||
if (options.fillPolygons) {
|
||||
ctx.closePath();
|
||||
ctx.fill(path);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
function curve(ctx, points = []) {
|
||||
if (points.length < 2) return;
|
||||
ctx.lineWidth = options.lineWidth;
|
||||
ctx.beginPath();
|
||||
function curves(ctx, points: number[] = []) {
|
||||
if (points === undefined || points.length === 0) return;
|
||||
if (!options.useCurves || points.length <= 2) {
|
||||
lines(ctx, points);
|
||||
return;
|
||||
}
|
||||
ctx.moveTo(points[0][0], points[0][1]);
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
const xMid = (points[i][0] + points[i + 1][0]) / 2;
|
||||
const yMid = (points[i][1] + points[i + 1][1]) / 2;
|
||||
const cpX1 = (xMid + points[i][0]) / 2;
|
||||
const cpX2 = (xMid + points[i + 1][1]) / 2;
|
||||
ctx.quadraticCurveTo(cpX1, points[i][1], xMid, yMid);
|
||||
ctx.quadraticCurveTo(cpX2, points[i + 1][1], points[i + 1][0], points[i + 1][0]);
|
||||
for (let i = 0; i < points.length - 2; i++) {
|
||||
const xc = (points[i][0] + points[i + 1][0]) / 2;
|
||||
const yc = (points[i][1] + points[i + 1][1]) / 2;
|
||||
ctx.quadraticCurveTo(points[i][0], points[i][1], xc, yc);
|
||||
}
|
||||
ctx.strokeStyle = options.color;
|
||||
ctx.quadraticCurveTo(points[points.length - 2][0], points[points.length - 2][1], points[points.length - 1][0], points[points.length - 1][1]);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
function bezier(ctx, points) {
|
||||
const tension = 0; // tension at 0 will be straight line
|
||||
const factor = 1; // factor is normally 1, but changing the value can control the smoothness too
|
||||
if (points.length < 2) return;
|
||||
ctx.lineWidth = options.lineWidth;
|
||||
ctx.strokeStyle = options.color;
|
||||
ctx.fillStyle = options.color;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[0][0], points[0][1]);
|
||||
let dx1 = 0;
|
||||
let dy1 = 0;
|
||||
let preP = points[0];
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const curP = points[i];
|
||||
const nexP = points[i + 1];
|
||||
const m = nexP ? (nexP[1] - preP[1]) / (nexP[0] - preP[0]) : 0;
|
||||
const dx2 = nexP ? (nexP[0] - curP[0]) * -factor : 0;
|
||||
const dy2 = nexP ? dx2 * m * tension : 0;
|
||||
ctx.bezierCurveTo(preP[0] - dx1, preP[1] - dy1, curP[0] + dx2, curP[1] + dy2, curP[0], curP[1]);
|
||||
dx1 = dx2;
|
||||
dy1 = dy2;
|
||||
preP = curP;
|
||||
if (options.fillPolygons) {
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
function spline(ctx, points) {
|
||||
const tension = 0.8;
|
||||
if (points.length < 2) return;
|
||||
const va = (arr, i, j) => [arr[2 * j] - arr[2 * i], arr[2 * j + 1] - arr[2 * i + 1]];
|
||||
const distance = (arr, i, j) => Math.sqrt(((arr[2 * i] - arr[2 * j]) ** 2) + ((arr[2 * i + 1] - arr[2 * j + 1]) ** 2));
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
|
||||
const ctlpts = (x1, y1, x2, y2, x3, y3) => {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
const v = va(arguments, 0, 2);
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
const d01 = distance(arguments, 0, 1);
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
const d12 = distance(arguments, 1, 2);
|
||||
const d012 = d01 + d12;
|
||||
return [
|
||||
x2 - v[0] * tension * d01 / d012, y2 - v[1] * tension * d01 / d012,
|
||||
x2 + v[0] * tension * d12 / d012, y2 + v[1] * tension * d12 / d012,
|
||||
];
|
||||
};
|
||||
const pts: any[] = [];
|
||||
for (const pt of points) {
|
||||
pts.push(pt[0]);
|
||||
pts.push(pt[1]);
|
||||
}
|
||||
let cps = [];
|
||||
for (let i = 0; i < pts.length - 2; i += 1) {
|
||||
// @ts-ignore
|
||||
cps = cps.concat(ctlpts(pts[2 * i + 0], pts[2 * i + 1], pts[2 * i + 2], pts[2 * i + 3], pts[2 * i + 4], pts[2 * i + 5]));
|
||||
}
|
||||
ctx.lineWidth = options.lineWidth;
|
||||
ctx.strokeStyle = options.color;
|
||||
if (points.length === 2) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pts[0], pts[1]);
|
||||
ctx.lineTo(pts[2], pts[3]);
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pts[0], pts[1]);
|
||||
// first segment is a quadratic
|
||||
ctx.quadraticCurveTo(cps[0], cps[1], pts[2], pts[3]);
|
||||
// for all middle points, connect with bezier
|
||||
let i;
|
||||
for (i = 2; i < ((pts.length / 2) - 1); i += 1) {
|
||||
ctx.bezierCurveTo(cps[(2 * (i - 1) - 1) * 2], cps[(2 * (i - 1) - 1) * 2 + 1], cps[(2 * (i - 1)) * 2], cps[(2 * (i - 1)) * 2 + 1], pts[i * 2], pts[i * 2 + 1]);
|
||||
}
|
||||
// last segment is a quadratic
|
||||
ctx.quadraticCurveTo(cps[(2 * (i - 1) - 1) * 2], cps[(2 * (i - 1) - 1) * 2 + 1], pts[i * 2], pts[i * 2 + 1]);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
export async function gesture(inCanvas, result) {
|
||||
|
@ -193,7 +116,9 @@ export async function face(inCanvas, result) {
|
|||
ctx.font = options.font;
|
||||
ctx.strokeStyle = options.color;
|
||||
ctx.fillStyle = options.color;
|
||||
if (options.drawBoxes) rect(ctx, f.box[0], f.box[1], f.box[2], f.box[3]);
|
||||
if (options.drawBoxes) {
|
||||
rect(ctx, f.box[0], f.box[1], f.box[2], f.box[3]);
|
||||
}
|
||||
// silly hack since fillText does not suport new line
|
||||
const labels:string[] = [];
|
||||
labels.push(`face confidence: ${Math.trunc(100 * f.confidence)}%`);
|
||||
|
@ -299,82 +224,70 @@ export async function body(inCanvas, result) {
|
|||
}
|
||||
}
|
||||
if (options.drawPolygons) {
|
||||
const path = new Path2D();
|
||||
let root;
|
||||
let part;
|
||||
const points: any[] = [];
|
||||
// torso
|
||||
root = result[i].keypoints.find((a) => a.part === 'leftShoulder');
|
||||
if (root && root.score > config.body.scoreThreshold) {
|
||||
const points: any[] = [];
|
||||
points.push([root.position.x, root.position.y, 'leftShoulder']);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightShoulder');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightHip');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftHip');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftShoulder');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
lines(ctx, points);
|
||||
}
|
||||
points.length = 0;
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftShoulder');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightShoulder');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightHip');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftHip');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftShoulder');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
if (points.length === 5) lines(ctx, points); // only draw if we have complete torso
|
||||
// leg left
|
||||
root = result[i].keypoints.find((a) => a.part === 'leftHip');
|
||||
if (root && root.score > config.body.scoreThreshold) {
|
||||
const points: any[] = [];
|
||||
points.push([root.position.x, root.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftKnee');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftAnkle');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftHeel');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftFoot');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
lines(ctx, points);
|
||||
}
|
||||
points.length = 0;
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftHip');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftKnee');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftAnkle');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftHeel');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftFoot');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
curves(ctx, points);
|
||||
// leg right
|
||||
root = result[i].keypoints.find((a) => a.part === 'rightHip');
|
||||
if (root && root.score > config.body.scoreThreshold) {
|
||||
const points: any[] = [];
|
||||
points.push([root.position.x, root.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightKnee');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightAnkle');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightHeel');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightFoot');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
lines(ctx, points);
|
||||
}
|
||||
points.length = 0;
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightHip');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightKnee');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightAnkle');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightHeel');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightFoot');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
curves(ctx, points);
|
||||
// arm left
|
||||
root = result[i].keypoints.find((a) => a.part === 'leftShoulder');
|
||||
if (root && root.score > config.body.scoreThreshold) {
|
||||
const points: any[] = [];
|
||||
points.push([root.position.x, root.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftElbow');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftWrist');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftPalm');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
lines(ctx, points);
|
||||
}
|
||||
points.length = 0;
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftShoulder');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftElbow');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftWrist');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'leftPalm');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
curves(ctx, points);
|
||||
// arm right
|
||||
root = result[i].keypoints.find((a) => a.part === 'rightShoulder');
|
||||
if (root && root.score > config.body.scoreThreshold) {
|
||||
const points: any[] = [];
|
||||
points.push([root.position.x, root.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightElbow');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightWrist');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightPalm');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
lines(ctx, points);
|
||||
}
|
||||
points.length = 0;
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightShoulder');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightElbow');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightWrist');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
part = result[i].keypoints.find((a) => a.part === 'rightPalm');
|
||||
if (part && part.score > config.body.scoreThreshold) points.push([part.position.x, part.position.y]);
|
||||
curves(ctx, points);
|
||||
// draw all
|
||||
ctx.stroke(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
16
src/human.ts
16
src/human.ts
|
@ -1,4 +1,5 @@
|
|||
import { log } from './log';
|
||||
import * as sysinfo from './sysinfo';
|
||||
import * as tf from '../dist/tfjs.esm.js';
|
||||
import * as backend from './tfjs/backend';
|
||||
import * as facemesh from './blazeface/facemesh';
|
||||
|
@ -61,6 +62,7 @@ class Human {
|
|||
emotion: any;
|
||||
body: any;
|
||||
hand: any;
|
||||
sysinfo: any;
|
||||
|
||||
constructor(userConfig = {}) {
|
||||
this.tf = tf;
|
||||
|
@ -95,6 +97,8 @@ class Human {
|
|||
this.emotion = emotion;
|
||||
this.body = this.config.body.modelType.startsWith('posenet') ? posenet : blazepose;
|
||||
this.hand = handpose;
|
||||
// include platform info
|
||||
this.sysinfo = sysinfo.info();
|
||||
}
|
||||
|
||||
profile() {
|
||||
|
@ -139,7 +143,11 @@ class Human {
|
|||
if (userConfig) this.config = mergeDeep(this.config, userConfig);
|
||||
|
||||
if (this.firstRun) {
|
||||
if (this.config.debug) log(`version: ${this.version} TensorFlow/JS version: ${this.tf.version_core}`);
|
||||
if (this.config.debug) log(`version: ${this.version}`);
|
||||
if (this.config.debug) log(`tfjs version: ${this.tf.version_core}`);
|
||||
if (this.config.debug) log('platform:', this.sysinfo.platform);
|
||||
if (this.config.debug) log('agent:', this.sysinfo.agent);
|
||||
|
||||
await this.checkBackend(true);
|
||||
if (this.tf.ENV.flags.IS_BROWSER) {
|
||||
if (this.config.debug) log('configuration:', this.config);
|
||||
|
@ -206,9 +214,11 @@ class Human {
|
|||
if (this.config.debug) log('setting backend:', this.config.backend);
|
||||
|
||||
if (this.config.backend === 'wasm') {
|
||||
if (this.config.debug) log('settings wasm path:', this.config.wasmPath);
|
||||
if (this.config.debug) log('wasm path:', this.config.wasmPath);
|
||||
this.tf.setWasmPaths(this.config.wasmPath);
|
||||
const simd = await this.tf.env().getAsync('WASM_HAS_SIMD_SUPPORT');
|
||||
const mt = await this.tf.env().getAsync('WASM_HAS_MULTITHREAD_SUPPORT');
|
||||
if (this.config.debug) log(`wasm execution: ${simd ? 'SIMD' : 'no SIMD'} ${mt ? 'multithreaded' : 'singlethreaded'}`);
|
||||
if (!simd) log('warning: wasm simd support is not enabled');
|
||||
}
|
||||
|
||||
|
@ -538,7 +548,7 @@ class Human {
|
|||
else res = await this.warmupNode();
|
||||
this.config.videoOptimized = video;
|
||||
const t1 = now();
|
||||
if (this.config.debug) log('Warmup', this.config.warmup, Math.round(t1 - t0), 'ms', res);
|
||||
if (this.config.debug) log('Warmup', this.config.warmup, Math.round(t1 - t0), 'ms');
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
61
src/node.js
61
src/node.js
|
@ -1,61 +0,0 @@
|
|||
const console = require('console');
|
||||
const process = require('process');
|
||||
// const URL = require('url');
|
||||
const tf = require('@tensorflow/tfjs-node');
|
||||
const Human = require('../dist/human.node').default; // this resolves to project root which is '@vladmandic/human'
|
||||
|
||||
const logger = new console.Console({
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
ignoreErrors: true,
|
||||
// groupIndentation: 2,
|
||||
inspectOptions: {
|
||||
showHidden: true,
|
||||
depth: 5,
|
||||
colors: true,
|
||||
showProxy: true,
|
||||
maxArrayLength: 1024,
|
||||
maxStringLength: 10240,
|
||||
breakLength: 300,
|
||||
compact: 64,
|
||||
sorted: false,
|
||||
getters: true,
|
||||
},
|
||||
});
|
||||
|
||||
const config = {
|
||||
backend: 'tensorflow',
|
||||
videoOptimized: false,
|
||||
face: {
|
||||
detector: { modelPath: 'file://models/blazeface-back.json' },
|
||||
mesh: { modelPath: 'file://models/facemesh.json' },
|
||||
iris: { modelPath: 'file://models/iris.json' },
|
||||
age: { modelPath: 'file://models/age-ssrnet-imdb.json' },
|
||||
gender: { modelPath: 'file://models/gender.json' },
|
||||
emotion: { modelPath: 'file://models/emotion.json' },
|
||||
embedding: { modelPath: 'file://models/mobilefacenet.json' },
|
||||
},
|
||||
body: { modelPath: 'file://models/posenet.json' },
|
||||
hand: {
|
||||
detector: { modelPath: 'file://models/handdetect.json' },
|
||||
skeleton: { modelPath: 'file://models/handskeleton.json' },
|
||||
},
|
||||
};
|
||||
|
||||
async function main() {
|
||||
await tf.ready();
|
||||
const human = new Human(config);
|
||||
logger.info('Human:', human.version);
|
||||
logger.info('Current folder:', process.env.PWD);
|
||||
logger.info('Active Configuration', human.config);
|
||||
logger.info('TFJS Version:', tf.version_core, 'Backend:', tf.getBackend());
|
||||
logger.info('TFJS Flags:', tf.env().features);
|
||||
logger.info('Loading models:');
|
||||
await human.load(config);
|
||||
const loaded = Object.keys(human.models).filter((a) => human.models[a]);
|
||||
logger.info('Loaded:', loaded);
|
||||
logger.info('Memory state:', human.tf.engine().memory());
|
||||
logger.info('Test Complete');
|
||||
}
|
||||
|
||||
main();
|
|
@ -44,7 +44,7 @@ export function scalePose(pose, scaleY, scaleX) {
|
|||
keypoints: pose.keypoints.map(({ score, part, position }) => ({
|
||||
score,
|
||||
part,
|
||||
position: { x: position.x * scaleX, y: position.y * scaleY },
|
||||
position: { x: Math.trunc(position.x * scaleX), y: Math.trunc(position.y * scaleY) },
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
export function info() {
|
||||
let platform;
|
||||
let agent;
|
||||
if (typeof navigator !== 'undefined') {
|
||||
const raw = navigator.userAgent.match(/\(([^()]+)\)/g);
|
||||
if (raw && raw[0]) {
|
||||
// @ts-ignore
|
||||
platform = raw[0].match(/\(([^()]+)\)/g)[0].replace(/\(|\)/g, '');
|
||||
agent = navigator.userAgent.replace(raw[0], '');
|
||||
if (platform[1]) agent = agent.replace(raw[1], '');
|
||||
agent = agent.replace(/ /g, ' ');
|
||||
}
|
||||
} else if (typeof process !== 'undefined') {
|
||||
platform = `${process.platform} ${process.arch}`;
|
||||
agent = `NodeJS ${process.version}`;
|
||||
}
|
||||
return { platform, agent };
|
||||
}
|
|
@ -13,6 +13,7 @@ export declare const options: {
|
|||
drawPolygons: boolean;
|
||||
fillPolygons: boolean;
|
||||
useDepth: boolean;
|
||||
useCurves: boolean;
|
||||
bufferedOutput: boolean;
|
||||
};
|
||||
export declare function gesture(inCanvas: any, result: any): Promise<void>;
|
||||
|
|
|
@ -19,6 +19,7 @@ declare class Human {
|
|||
emotion: any;
|
||||
body: any;
|
||||
hand: any;
|
||||
sysinfo: any;
|
||||
constructor(userConfig?: {});
|
||||
profile(): {};
|
||||
analyze(...msg: any[]): void;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export declare function info(): {
|
||||
platform: any;
|
||||
agent: any;
|
||||
};
|
2
wiki
2
wiki
|
@ -1 +1 @@
|
|||
Subproject commit ce4f3e12bd49ee404186c55ab977b4b1612d17f4
|
||||
Subproject commit 562a698daecaecf8580120fc4e1c9b6ac66ba537
|
Loading…
Reference in New Issue