autodetect number of bodies and hands

pull/280/head
Vladimir Mandic 2021-09-25 19:14:03 -04:00
parent 7da344b8d5
commit cd35d39352
41 changed files with 82 additions and 65 deletions

View File

@ -9,7 +9,10 @@
## Changelog
### **HEAD -> main** 2021/09/24 mandic00@live.com
### **HEAD -> main** 2021/09/25 mandic00@live.com
### **origin/main** 2021/09/25 mandic00@live.com
- new release

View File

@ -3,7 +3,7 @@
Sample Images used by `Human` library demos and automated tests
Not required for normal funcioning of library
Samples were generated using default configuration without any fine-tuning using command:
Samples were generated using command:
```shell
node test/test-node-canvas.js samples/in/ samples/out/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 KiB

After

Width:  |  Height:  |  Size: 451 KiB

View File

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
samples/out/person-lexi.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

View File

@ -9,34 +9,37 @@
<meta name="description" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="msapplication-tooltip" content="Human: 3D Face Detection, Body Pose, Hand & Finger Tracking, Iris Tracking, Age & Gender Prediction, Emotion Prediction & Gesture Recognition; Author: Vladimir Mandic <https://github.com/vladmandic>">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="../manifest.webmanifest">
<link rel="shortcut icon" href="../../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../../assets/icon.png">
<link rel="manifest" href="../demo/manifest.webmanifest">
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../assets/icon.png">
<style>
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../../assets/lato-light.woff2') }
html { font-family: 'Lato', 'Segoe UI'; font-size: 24px; font-variant: small-caps; }
body { margin: 24px; background: black; color: white; overflow-x: hidden; overflow-y: auto; text-align: -webkit-center; min-height: 100%; max-height: 100%; }
body { margin: 24px; background: black; color: white; overflow-x: auto; overflow-y: auto; text-align: -webkit-center; min-height: 100%; max-height: 100%; }
::-webkit-scrollbar { height: 8px; border: 0; border-radius: 0; }
::-webkit-scrollbar-thumb { background: grey }
::-webkit-scrollbar-track { margin: 3px; }
.text { margin: 24px }
.strip { display: flex; width: 100%; overflow: auto; }
.thumb { height: 150px; margin: 2px; padding: 2px; }
.thumb:hover { filter: grayscale(1); background: white; }
.strip { display: flex; width: 100%; overflow-x: auto; overflow-y: hidden; scroll-behavior: smooth; }
.thumb { height: 180px; margin: 2px; padding: 2px; }
.thumb:hover { filter: grayscale(1); transform: scale(1.08); transition : all 0.3s ease; }
.image-container { margin: 24px 3px 3px 3px }
.image { max-width: -webkit-fill-available; }
.image-zoomwidth { max-width: 94vw; }
.image-zoomheight { max-height: 70vh; }
.image-zoomfull { max-height: -webkit-fill-available; }
</style>
</head>
<body>
<div class="text">Human Examples Gallery</div>
<div id="strip" class="strip"></div>
<div class="image-container">
<img id="image" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" alt="" class="image" />
<img id="image" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" alt="" class="image-zoomwidth" />
</div>
<script>
const samples = [
'ai-body.jpg', 'ai-upper.jpg',
'person-vlado.jpg', 'person-linda.jpg', 'person-celeste.jpg', 'person-tetiana.jpg',
'person-carolina.jpg', 'person-celeste.jpg', 'person-leila1.jpg', 'person-leila2.jpg', 'person-lexi.jpg', 'person-linda.jpg', 'person-nicole.jpg', 'person-tasia.jpg',
'person-tetiana.jpg', 'person-vlado1.jpg', 'person-vlado5.jpg', 'person-vlado.jpg', 'person-christina.jpg', 'person-lauren.jpg',
'group-1.jpg', 'group-2.jpg', 'group-3.jpg', 'group-4.jpg', 'group-5.jpg', 'group-6.jpg', 'group-7.jpg',
'daz3d-brianna.jpg', 'daz3d-chiyo.jpg', 'daz3d-cody.jpg', 'daz3d-drew-01.jpg', 'daz3d-drew-02.jpg', 'daz3d-ella-01.jpg', 'daz3d-ella-02.jpg', 'daz3d-gillian.jpg',
'daz3d-hye-01.jpg', 'daz3d-hye-02.jpg', 'daz3d-kaia.jpg', 'daz3d-karen.jpg', 'daz3d-kiaria-01.jpg', 'daz3d-kiaria-02.jpg', 'daz3d-lilah-01.jpg', 'daz3d-lilah-02.jpg',
@ -45,12 +48,22 @@
'daz3d-_emotions01.jpg', 'daz3d-_emotions02.jpg', 'daz3d-_emotions03.jpg', 'daz3d-_emotions04.jpg', 'daz3d-_emotions05.jpg',
];
const image = document.getElementById('image');
image.addEventListener('click', () => {
if (image.classList.contains('image-zoomwidth')) image.className = 'image-zoomheight';
else if (image.classList.contains('image-zoomheight')) image.className = 'image-zoomfull';
else if (image.classList.contains('image-zoomfull')) image.className = 'image-zoomwidth';
});
const strip = document.getElementById('strip');
for (const sample of samples) {
const el = document.createElement('img');
el.className = 'thumb';
el.src = el.title = el.alt = `/samples/in/${sample}`;
el.addEventListener('click', () => image.src = image.alt = image.title = el.src.replace('/in/', '/out/'));
document.getElementById('strip')?.appendChild(el);
el.addEventListener('click', (evt) => {
image.src = image.alt = image.title = el.src.replace('/in/', '/out/');
console.log(evt);
strip.scrollLeft = evt.target.offsetLeft - window.innerWidth / 2 + evt.target.offsetWidth / 2;
});
strip.appendChild(el);
}
</script>
</body>

View File

@ -71,8 +71,11 @@ export interface FaceConfig {
* - minConfidence: threshold for discarding a prediction
* - maxDetected: maximum number of people detected in the input, should be set to the minimum number for performance
*
* `maxDetected` is valid for `posenet` and `movenet-multipose` as other models are single-pose only
* `maxDetected` can be set to -1 to auto-detect based on number of detected faces
*
* Changing `modelPath` will change module responsible for hand detection and tracking
* Allowed values are 'posenet.json', 'blazepose.json', 'efficientpose.json', 'movenet-lightning.json', 'movenet-thunder.json', 'movenet-multipose.json'
* Allowed values are `posenet.json`, `blazepose.json`, `efficientpose.json`, `movenet-lightning.json`, `movenet-thunder.json`, `movenet-multipose.json`
*/
export interface BodyConfig {
enabled: boolean,
@ -93,6 +96,8 @@ export interface BodyConfig {
* - maxDetected: maximum number of hands detected in the input, should be set to the minimum number for performance
* - rotation: use best-guess rotated hand image or just box with rotation as-is, false means higher performance, but incorrect finger mapping if hand is inverted
*
* `maxDetected` can be set to -1 to auto-detect based on number of detected faces
*
* Changing `detector.modelPath` will change module responsible for hand detection and tracking
* Allowed values are `handdetect.json` and `handtrack.json`
*/
@ -394,9 +399,10 @@ const config: Config = {
enabled: true,
modelPath: 'movenet-lightning.json', // body model, can be absolute path or relative to modelBasePath
// can be 'posenet', 'blazepose', 'efficientpose', 'movenet-lightning', 'movenet-thunder'
maxDetected: 1, // maximum number of people detected in the input
maxDetected: -1, // maximum number of people detected in the input
// should be set to the minimum number for performance
// only valid for posenet as other models detects single pose
// only valid for posenet and movenet-multipose as other models detects single pose
// set to -1 to autodetect based on number of detected faces
minConfidence: 0.2, // threshold for discarding a prediction
skipFrames: 1, // how many max frames to go without re-running the detector
// only used when cacheSensitivity is not zero
@ -414,8 +420,9 @@ const config: Config = {
// hasn't moved much in short time (10 * 1/25 = 0.25 sec)
minConfidence: 0.8, // threshold for discarding a prediction
iouThreshold: 0.2, // ammount of overlap between two detected objects before one object is removed
maxDetected: 1, // maximum number of hands detected in the input
maxDetected: -1, // maximum number of hands detected in the input
// should be set to the minimum number for performance
// set to -1 to autodetect based on number of detected faces
landmarks: true, // detect hand landmarks or just hand boundary box
detector: {
modelPath: 'handdetect.json', // hand detector model, can be absolute path or relative to modelBasePath

View File

@ -15,6 +15,7 @@ import { env } from '../env';
import * as fingerPose from '../fingerpose/fingerpose';
import { fakeOps } from '../tfjs/backend';
const boxScaleFact = 1.5; // hand finger model prefers slighly larger box
const models: [GraphModel | null, GraphModel | null] = [null, null];
const modelOutputNodes = ['StatefulPartitionedCall/Postprocessor/Slice', 'StatefulPartitionedCall/Postprocessor/ExpandDims_1'];
@ -118,7 +119,15 @@ async function detectHands(input: Tensor, config: Config): Promise<HandDetectRes
tf.dispose(t.nms);
for (const res of Array.from(nms)) { // generates results for each class
const boxSlice = tf.slice(t.boxes, res, 1);
const yxBox = await boxSlice.data();
let yxBox: [number, number, number, number] = [0, 0, 0, 0];
if (config.hand.landmarks) { // scale box
const detectedBox: [number, number, number, number] = await boxSlice.data();
const boxCenter: [number, number] = [(detectedBox[0] + detectedBox[2]) / 2, (detectedBox[1] + detectedBox[3]) / 2];
const boxDiff: [number, number, number, number] = [+boxCenter[0] - detectedBox[0], +boxCenter[1] - detectedBox[1], -boxCenter[0] + detectedBox[2], -boxCenter[1] + detectedBox[3]];
yxBox = [boxCenter[0] - boxScaleFact * boxDiff[0], boxCenter[1] - boxScaleFact * boxDiff[1], boxCenter[0] + boxScaleFact * boxDiff[2], boxCenter[1] + boxScaleFact * boxDiff[3]];
} else { // use box as-is
yxBox = await boxSlice.data();
}
const boxRaw: [number, number, number, number] = [yxBox[1], yxBox[0], yxBox[3] - yxBox[1], yxBox[2] - yxBox[0]];
const box: [number, number, number, number] = [Math.trunc(boxRaw[0] * outputSize[0]), Math.trunc(boxRaw[1] * outputSize[1]), Math.trunc(boxRaw[2] * outputSize[0]), Math.trunc(boxRaw[3] * outputSize[1])];
tf.dispose(boxSlice);
@ -136,7 +145,6 @@ async function detectHands(input: Tensor, config: Config): Promise<HandDetectRes
return hands;
}
const boxScaleFact = 1.5; // hand finger model prefers slighly larger box
function updateBoxes(h, keypoints) {
const finger = [keypoints.map((pt) => pt[0]), keypoints.map((pt) => pt[1])]; // all fingers coords
const minmax = [Math.min(...finger[0]), Math.max(...finger[0]), Math.min(...finger[1]), Math.max(...finger[1])]; // find min and max coordinates for x and y of all fingers

View File

@ -453,21 +453,24 @@ export class Human {
if (elapsedTime > 0) this.performance.face = elapsedTime;
}
if (this.config.async && (this.config.body.maxDetected === -1 || this.config.hand.maxDetected === -1)) faceRes = await faceRes; // need face result for auto-detect number of hands or bodies
// run body: can be posenet, blazepose, efficientpose, movenet
this.analyze('Start Body:');
this.state = 'detect:body';
const bodyConfig = this.config.body.maxDetected === -1 ? mergeDeep(this.config, { body: { maxDetected: 1 * (faceRes as FaceResult[]).length } }) : this.config; // autodetect number of bodies
if (this.config.async) {
if (this.config.body.modelPath?.includes('posenet')) bodyRes = this.config.body.enabled ? posenet.predict(img.tensor, this.config) : [];
else if (this.config.body.modelPath?.includes('blazepose')) bodyRes = this.config.body.enabled ? blazepose.predict(img.tensor, this.config) : [];
else if (this.config.body.modelPath?.includes('efficientpose')) bodyRes = this.config.body.enabled ? efficientpose.predict(img.tensor, this.config) : [];
else if (this.config.body.modelPath?.includes('movenet')) bodyRes = this.config.body.enabled ? movenet.predict(img.tensor, this.config) : [];
if (this.config.body.modelPath?.includes('posenet')) bodyRes = this.config.body.enabled ? posenet.predict(img.tensor, bodyConfig) : [];
else if (this.config.body.modelPath?.includes('blazepose')) bodyRes = this.config.body.enabled ? blazepose.predict(img.tensor, bodyConfig) : [];
else if (this.config.body.modelPath?.includes('efficientpose')) bodyRes = this.config.body.enabled ? efficientpose.predict(img.tensor, bodyConfig) : [];
else if (this.config.body.modelPath?.includes('movenet')) bodyRes = this.config.body.enabled ? movenet.predict(img.tensor, bodyConfig) : [];
if (this.performance.body) delete this.performance.body;
} else {
timeStamp = now();
if (this.config.body.modelPath?.includes('posenet')) bodyRes = this.config.body.enabled ? await posenet.predict(img.tensor, this.config) : [];
else if (this.config.body.modelPath?.includes('blazepose')) bodyRes = this.config.body.enabled ? await blazepose.predict(img.tensor, this.config) : [];
else if (this.config.body.modelPath?.includes('efficientpose')) bodyRes = this.config.body.enabled ? await efficientpose.predict(img.tensor, this.config) : [];
else if (this.config.body.modelPath?.includes('movenet')) bodyRes = this.config.body.enabled ? await movenet.predict(img.tensor, this.config) : [];
if (this.config.body.modelPath?.includes('posenet')) bodyRes = this.config.body.enabled ? await posenet.predict(img.tensor, bodyConfig) : [];
else if (this.config.body.modelPath?.includes('blazepose')) bodyRes = this.config.body.enabled ? await blazepose.predict(img.tensor, bodyConfig) : [];
else if (this.config.body.modelPath?.includes('efficientpose')) bodyRes = this.config.body.enabled ? await efficientpose.predict(img.tensor, bodyConfig) : [];
else if (this.config.body.modelPath?.includes('movenet')) bodyRes = this.config.body.enabled ? await movenet.predict(img.tensor, bodyConfig) : [];
elapsedTime = Math.trunc(now() - timeStamp);
if (elapsedTime > 0) this.performance.body = elapsedTime;
}
@ -476,14 +479,15 @@ export class Human {
// run handpose
this.analyze('Start Hand:');
this.state = 'detect:hand';
const handConfig = this.config.hand.maxDetected === -1 ? mergeDeep(this.config, { hand: { maxDetected: 2 * (faceRes as FaceResult[]).length } }) : this.config; // autodetect number of hands
if (this.config.async) {
if (this.config.hand.detector?.modelPath?.includes('handdetect')) handRes = this.config.hand.enabled ? handpose.predict(img.tensor, this.config) : [];
else if (this.config.hand.detector?.modelPath?.includes('handtrack')) handRes = this.config.hand.enabled ? handtrack.predict(img.tensor, this.config) : [];
if (this.config.hand.detector?.modelPath?.includes('handdetect')) handRes = this.config.hand.enabled ? handpose.predict(img.tensor, handConfig) : [];
else if (this.config.hand.detector?.modelPath?.includes('handtrack')) handRes = this.config.hand.enabled ? handtrack.predict(img.tensor, handConfig) : [];
if (this.performance.hand) delete this.performance.hand;
} else {
timeStamp = now();
if (this.config.hand.detector?.modelPath?.includes('handdetect')) handRes = this.config.hand.enabled ? await handpose.predict(img.tensor, this.config) : [];
else if (this.config.hand.detector?.modelPath?.includes('handtrack')) handRes = this.config.hand.enabled ? await handtrack.predict(img.tensor, this.config) : [];
if (this.config.hand.detector?.modelPath?.includes('handdetect')) handRes = this.config.hand.enabled ? await handpose.predict(img.tensor, handConfig) : [];
else if (this.config.hand.detector?.modelPath?.includes('handtrack')) handRes = this.config.hand.enabled ? await handtrack.predict(img.tensor, handConfig) : [];
elapsedTime = Math.trunc(now() - timeStamp);
if (elapsedTime > 0) this.performance.hand = elapsedTime;
}

View File

@ -9,6 +9,7 @@ import * as tf from '../../dist/tfjs.esm.js';
import type { BodyResult } from '../result';
import type { GraphModel, Tensor } from '../tfjs/types';
import type { Config } from '../config';
import { fakeOps } from '../tfjs/backend';
import { env } from '../env';
let model: GraphModel | null;
@ -27,6 +28,7 @@ const bodyParts = ['nose', 'leftEye', 'rightEye', 'leftEar', 'rightEar', 'leftSh
export async function load(config: Config): Promise<GraphModel> {
if (env.initial) model = null;
if (!model) {
fakeOps(['size'], config);
model = await tf.loadGraphModel(join(config.modelBasePath, config.body.modelPath || '')) as unknown as GraphModel;
if (!model || !model['modelUrl']) log('load model failed:', config.body.modelPath);
else if (config.debug) log('load model:', model['modelUrl']);
@ -78,8 +80,8 @@ async function parseSinglePose(res, config, image) {
async function parseMultiPose(res, config, image) {
const persons: Array<Person> = [];
for (let p = 0; p < res[0].length; p++) {
const kpt = res[0][p];
for (let id = 0; id < res[0].length; id++) {
const kpt = res[0][id];
score = Math.round(100 * kpt[51 + 4]) / 100;
// eslint-disable-next-line no-continue
if (score < config.body.minConfidence) continue;
@ -90,20 +92,14 @@ async function parseMultiPose(res, config, image) {
keypoints.push({
part: bodyParts[i],
score: partScore,
positionRaw: [
kpt[3 * i + 1],
kpt[3 * i + 0],
],
position: [
Math.trunc(kpt[3 * i + 1] * (image.shape[2] || 0)),
Math.trunc(kpt[3 * i + 0] * (image.shape[1] || 0)),
],
positionRaw: [kpt[3 * i + 1], kpt[3 * i + 0]],
position: [Math.trunc(kpt[3 * i + 1] * (image.shape[2] || 0)), Math.trunc(kpt[3 * i + 0] * (image.shape[1] || 0))],
});
}
}
boxRaw = [kpt[51 + 1], kpt[51 + 0], kpt[51 + 3] - kpt[51 + 1], kpt[51 + 2] - kpt[51 + 0]];
persons.push({
id: p,
id,
score,
boxRaw,
box: [
@ -112,7 +108,7 @@ async function parseMultiPose(res, config, image) {
Math.trunc(boxRaw[2] * (image.shape[2] || 0)),
Math.trunc(boxRaw[3] * (image.shape[1] || 0)),
],
keypoints,
keypoints: [...keypoints],
});
}
return persons;
@ -140,11 +136,11 @@ export async function predict(image: Tensor, config: Config): Promise<BodyResult
if (!resT) resolve([]);
const res = await resT.array();
let persons;
if (resT.shape[2] === 17) persons = await parseSinglePose(res, config, image);
else if (resT.shape[2] === 56) persons = await parseMultiPose(res, config, image);
let body;
if (resT.shape[2] === 17) body = await parseSinglePose(res, config, image);
else if (resT.shape[2] === 56) body = await parseMultiPose(res, config, image);
tf.dispose(resT);
resolve(persons);
resolve(body);
});
}

View File

@ -296,7 +296,7 @@ async function test(Human, inputConfig) {
res = await testDetect(human, 'samples/in/ai-body.jpg', 'default');
if (!res || res?.face?.length !== 1 || res?.face[0]?.gender || res?.face[0]?.age || res?.face[0]?.embedding) log('error', 'failed: detectors result face mismatch', res?.face);
else log('state', 'passed: detector result face match');
if (!res || res?.hand?.length !== 1 || res?.hand[0]?.landmarks) log('error', 'failed: detectors result hand mismatch', res?.hand?.length);
if (!res || res?.hand?.length !== 2 || res?.hand[0]?.landmarks) log('error', 'failed: detectors result hand mismatch', res?.hand?.length);
else log('state', 'passed: detector result hand match');
// test posenet and movenet

View File

@ -11,17 +11,10 @@ const config = { // just enable all and leave default settings
async: false,
cacheSensitivity: 0,
face: { enabled: true },
hand: { enabled: true },
body: { enabled: true },
object: { enabled: true },
gesture: { enabled: true },
/*
face: { enabled: true, detector: { minConfidence: 0.1 } },
hand: { enabled: true, maxDetected: 2, minConfidence: 0.1, detector: { modelPath: 'handtrack.json' } }, // use alternative hand model
body: { enabled: true, minConfidence: 0.1 },
object: { enabled: true, minConfidence: 0.1 },
gesture: { enabled: true },
*/
hand: { enabled: true, minConfidence: 0.4, detector: { modelPath: 'handtrack.json' } },
body: { enabled: true, modelPath: 'https://vladmandic.github.io/human-models/models/movenet-multipose.json' },
};
async function main() {
@ -53,13 +46,6 @@ async function main() {
log.info(`Processing folder: ${inDir} entries:`, dir.length, 'images', images.length);
for (const image of images) {
const inFile = path.join(inDir, image);
/*
const inputImage = await canvas.loadImage(inFile); // load image using canvas library
log.state('Loaded image:', inFile, inputImage.width, inputImage.height);
const inputCanvas = new canvas.Canvas(inputImage.width, inputImage.height); // create canvas
const inputCtx = inputCanvas.getContext('2d');
inputCtx.drawImage(inputImage, 0, 0); // draw input image onto canvas
*/
const buffer = fs.readFileSync(inFile);
const tensor = human.tf.tidy(() => {
const decode = human.tf.node.decodeImage(buffer, 3);