autodetect number of bodies and hands
|
@ -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
|
||||
|
||||
|
|
|
@ -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/
|
||||
|
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 371 KiB |
Before Width: | Height: | Size: 295 KiB After Width: | Height: | Size: 358 KiB |
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 350 KiB |
Before Width: | Height: | Size: 464 KiB After Width: | Height: | Size: 451 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 182 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 248 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 166 KiB |
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 266 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 340 KiB After Width: | Height: | Size: 374 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 305 KiB After Width: | Height: | Size: 321 KiB |
Before Width: | Height: | Size: 296 KiB After Width: | Height: | Size: 327 KiB |
Before Width: | Height: | Size: 386 KiB After Width: | Height: | Size: 424 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 223 KiB |
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 232 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 127 KiB |
After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 185 KiB |
After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 181 KiB |
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
28
src/human.ts
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|