human/src/human.ts

525 lines
20 KiB
TypeScript
Raw Normal View History

2021-03-21 12:49:55 +01:00
import { log, now, mergeDeep } from './helpers';
2021-03-06 16:38:04 +01:00
import * as sysinfo from './sysinfo';
2020-11-18 14:26:28 +01:00
import * as tf from '../dist/tfjs.esm.js';
2021-02-08 17:39:09 +01:00
import * as backend from './tfjs/backend';
2021-03-21 12:49:55 +01:00
import * as faceall from './faceall';
2021-02-08 17:39:09 +01:00
import * as facemesh from './blazeface/facemesh';
import * as age from './age/age';
import * as gender from './gender/gender';
2021-03-21 19:18:51 +01:00
import * as faceres from './faceres/faceres';
2021-02-08 17:39:09 +01:00
import * as emotion from './emotion/emotion';
import * as embedding from './embedding/embedding';
import * as posenet from './posenet/posenet';
import * as handpose from './handpose/handpose';
2021-03-04 16:33:08 +01:00
import * as blazepose from './blazepose/blazepose';
import * as nanodet from './nanodet/nanodet';
2021-02-08 17:39:09 +01:00
import * as gesture from './gesture/gesture';
2021-03-17 23:23:19 +01:00
import * as image from './image/image';
import * as draw from './draw/draw';
2021-02-08 17:39:09 +01:00
import * as profile from './profile';
2021-03-17 23:23:19 +01:00
import { Config, defaults } from './config';
import { Result } from './result';
2021-02-08 17:39:09 +01:00
import * as sample from './sample';
2020-11-10 02:13:38 +01:00
import * as app from '../package.json';
2021-03-17 23:23:19 +01:00
2021-03-18 01:26:43 +01:00
/** Generic Tensor object type */
export type Tensor = typeof tf.Tensor;
2021-03-17 23:23:19 +01:00
export type { Config } from './config';
export type { Result } from './result';
/** Defines all possible input types for **Human** detection */
export type Input = Tensor | ImageData | ImageBitmap | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas;
/** Error message */
2021-03-18 01:16:40 +01:00
export type Error = { error: string };
2021-03-18 01:26:43 +01:00
/** Instance of TensorFlow/JS */
2021-03-17 23:23:19 +01:00
export type TensorFlow = typeof tf;
2021-03-18 01:26:43 +01:00
/** Generic Model object type, holds instance of individual models */
type Model = Object;
2020-10-15 21:25:58 +02:00
2021-03-17 23:23:19 +01:00
/**
2021-03-17 23:33:12 +01:00
* **Human** library main class
2021-03-17 23:23:19 +01:00
*
* All methods and properties are available only as members of Human class
*
2021-03-17 23:33:12 +01:00
* - Configuration object definition: {@link Config}
* - Results object definition: {@link Result}
* - Possible inputs: {@link Input}
2021-03-17 23:23:19 +01:00
*/
2021-03-14 04:31:09 +01:00
export class Human {
2021-03-18 01:16:40 +01:00
version: string;
2021-03-17 23:23:19 +01:00
config: Config;
2021-03-18 01:16:40 +01:00
state: string;
2021-03-14 04:31:09 +01:00
image: { tensor: Tensor, canvas: OffscreenCanvas | HTMLCanvasElement };
// classes
2021-03-17 23:23:19 +01:00
tf: TensorFlow;
draw: {
drawOptions?: typeof draw.drawOptions,
gesture: typeof draw.gesture,
face: typeof draw.face,
body: typeof draw.body,
hand: typeof draw.hand,
canvas: typeof draw.canvas,
all: typeof draw.all,
};
2021-02-08 17:39:09 +01:00
// models
models: {
2021-03-14 04:31:09 +01:00
face: facemesh.MediaPipeFaceMesh | null,
posenet: posenet.PoseNet | null,
blazepose: Model | null,
handpose: handpose.HandPose | null,
iris: Model | null,
age: Model | null,
gender: Model | null,
emotion: Model | null,
embedding: Model | null,
nanodet: Model | null,
2021-03-21 19:18:51 +01:00
faceres: Model | null,
};
classes: {
facemesh: typeof facemesh;
age: typeof age;
gender: typeof gender;
emotion: typeof emotion;
body: typeof posenet | typeof blazepose;
hand: typeof handpose;
nanodet: typeof nanodet;
2021-03-21 19:18:51 +01:00
faceres: typeof faceres;
};
2021-03-18 01:16:40 +01:00
sysinfo: { platform: string, agent: string };
2021-03-21 12:49:55 +01:00
perf: any;
#numTensors: number;
2021-03-18 01:16:40 +01:00
#analyzeMemoryLeaks: boolean;
#checkSanity: boolean;
#firstRun: boolean;
// definition end
2021-02-08 17:39:09 +01:00
2021-03-17 23:23:19 +01:00
constructor(userConfig: Config | Object = {}) {
this.tf = tf;
2021-03-05 17:43:50 +01:00
this.draw = draw;
this.version = app.version;
2021-03-17 23:23:19 +01:00
this.config = mergeDeep(defaults, userConfig);
this.state = 'idle';
this.#numTensors = 0;
this.#analyzeMemoryLeaks = false;
this.#checkSanity = false;
this.#firstRun = true;
2021-03-21 12:49:55 +01:00
this.perf = {};
// object that contains all initialized models
this.models = {
face: null,
posenet: null,
2021-03-04 16:33:08 +01:00
blazepose: null,
handpose: null,
iris: null,
age: null,
gender: null,
emotion: null,
embedding: null,
nanodet: null,
2021-03-21 19:18:51 +01:00
faceres: null,
};
2021-03-04 16:33:08 +01:00
// export access to image processing
// @ts-ignore
2021-03-17 23:23:19 +01:00
this.image = (input: Input) => image.process(input, this.config);
// export raw access to underlying models
this.classes = {
facemesh,
age,
gender,
emotion,
2021-03-21 19:18:51 +01:00
faceres,
2021-03-11 16:26:14 +01:00
body: this.config.body.modelPath.includes('posenet') ? posenet : blazepose,
hand: handpose,
nanodet,
};
2021-03-06 16:38:04 +01:00
// include platform info
this.sysinfo = sysinfo.info();
2020-10-18 18:12:09 +02:00
}
profileData(): { newBytes, newTensors, peakBytes, numKernelOps, timeKernelOps, slowestKernelOps, largestKernelOps } | {} {
2020-11-01 19:07:53 +01:00
if (this.config.profile) return profile.data;
return {};
}
// helper function: measure tensor leak
2021-03-14 04:31:09 +01:00
/** @hidden */
2021-03-21 12:49:55 +01:00
analyze = (...msg) => {
if (!this.#analyzeMemoryLeaks) return;
2021-02-19 14:35:41 +01:00
const current = this.tf.engine().state.numTensors;
const previous = this.#numTensors;
this.#numTensors = current;
const leaked = current - previous;
2020-12-08 15:00:44 +01:00
if (leaked !== 0) log(...msg, leaked);
2020-10-18 18:12:09 +02:00
}
2020-10-17 13:15:23 +02:00
2020-11-04 16:18:22 +01:00
// quick sanity check on inputs
2021-03-14 04:31:09 +01:00
/** @hidden */
2021-03-18 01:16:40 +01:00
#sanity = (input): null | string => {
if (!this.#checkSanity) return null;
2020-11-03 16:55:33 +01:00
if (!input) return 'input is not defined';
2021-03-14 04:31:09 +01:00
if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf.Tensor)) return 'input must be a tensor';
2020-11-03 16:55:33 +01:00
try {
2021-02-19 14:35:41 +01:00
this.tf.getBackend();
2020-11-03 16:55:33 +01:00
} catch {
return 'backend not loaded';
}
return null;
}
2021-03-21 19:18:51 +01:00
similarity(embedding1: Array<number>, embedding2: Array<number>): number {
if (this.config.face.description.enabled) return faceres.similarity(embedding1, embedding2);
if (this.config.face.embedding.enabled) return embedding.similarity(embedding1, embedding2);
2020-11-13 22:13:35 +01:00
return 0;
}
// eslint-disable-next-line class-methods-use-this
2021-03-14 04:31:09 +01:00
enhance(input: Tensor): Tensor | null {
2021-03-21 19:18:51 +01:00
return faceres.enhance(input);
}
// eslint-disable-next-line class-methods-use-this
2021-03-21 19:18:51 +01:00
match(faceEmbedding: Array<number>, db: Array<{ name: string, source: string, embedding: number[] }>, threshold = 0): { name: string, source: string, similarity: number, embedding: number[] } {
return faceres.match(faceEmbedding, db, threshold);
2021-03-12 18:54:08 +01:00
}
2020-11-04 16:18:22 +01:00
// preload models, not explicitly required as it's done automatically on first use
2021-03-17 23:23:19 +01:00
async load(userConfig: Config | Object = {}) {
2020-11-06 17:39:39 +01:00
this.state = 'load';
const timeStamp = now();
2020-11-06 21:35:58 +01:00
if (userConfig) this.config = mergeDeep(this.config, userConfig);
2020-11-03 15:34:36 +01:00
if (this.#firstRun) {
2021-03-06 16:38:04 +01:00
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);
2021-02-19 14:35:41 +01:00
if (this.tf.ENV.flags.IS_BROWSER) {
2021-03-02 17:27:42 +01:00
if (this.config.debug) log('configuration:', this.config);
if (this.config.debug) log('tf flags:', this.tf.ENV.flags);
}
2020-11-03 15:34:36 +01:00
}
2020-11-06 17:39:39 +01:00
if (this.config.async) {
[
2021-02-06 23:41:53 +01:00
this.models.face,
2020-11-06 17:39:39 +01:00
this.models.age,
this.models.gender,
this.models.emotion,
2020-11-13 22:13:35 +01:00
this.models.embedding,
2020-11-06 17:39:39 +01:00
this.models.handpose,
2021-03-04 16:33:08 +01:00
this.models.posenet,
this.models.blazepose,
this.models.nanodet,
2021-03-21 19:18:51 +01:00
this.models.faceres,
2020-11-06 17:39:39 +01:00
] = await Promise.all([
2021-03-10 00:32:35 +01:00
this.models.face || (this.config.face.enabled ? facemesh.load(this.config) : null),
2020-11-12 04:40:05 +01:00
this.models.age || ((this.config.face.enabled && this.config.face.age.enabled) ? age.load(this.config) : null),
this.models.gender || ((this.config.face.enabled && this.config.face.gender.enabled) ? gender.load(this.config) : null),
this.models.emotion || ((this.config.face.enabled && this.config.face.emotion.enabled) ? emotion.load(this.config) : null),
2020-11-13 22:13:35 +01:00
this.models.embedding || ((this.config.face.enabled && this.config.face.embedding.enabled) ? embedding.load(this.config) : null),
this.models.handpose || (this.config.hand.enabled ? handpose.load(this.config) : null),
2021-03-11 16:26:14 +01:00
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes('posenet') ? posenet.load(this.config) : null),
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes('blazepose') ? blazepose.load(this.config) : null),
this.models.nanodet || (this.config.object.enabled ? nanodet.load(this.config) : null),
2021-03-21 19:18:51 +01:00
this.models.faceres || ((this.config.face.enabled && this.config.face.description.enabled) ? faceres.load(this.config) : null),
2020-11-06 17:39:39 +01:00
]);
} else {
2021-03-10 00:32:35 +01:00
if (this.config.face.enabled && !this.models.face) this.models.face = await facemesh.load(this.config);
2020-11-07 16:37:19 +01:00
if (this.config.face.enabled && this.config.face.age.enabled && !this.models.age) this.models.age = await age.load(this.config);
if (this.config.face.enabled && this.config.face.gender.enabled && !this.models.gender) this.models.gender = await gender.load(this.config);
if (this.config.face.enabled && this.config.face.emotion.enabled && !this.models.emotion) this.models.emotion = await emotion.load(this.config);
2020-11-13 22:13:35 +01:00
if (this.config.face.enabled && this.config.face.embedding.enabled && !this.models.embedding) this.models.embedding = await embedding.load(this.config);
if (this.config.hand.enabled && !this.models.handpose) this.models.handpose = await handpose.load(this.config);
2021-03-11 16:26:14 +01:00
if (this.config.body.enabled && !this.models.posenet && this.config.body.modelPath.includes('posenet')) this.models.posenet = await posenet.load(this.config);
if (this.config.body.enabled && !this.models.blazepose && this.config.body.modelPath.includes('blazepose')) this.models.blazepose = await blazepose.load(this.config);
if (this.config.object.enabled && !this.models.nanodet) this.models.nanodet = await nanodet.load(this.config);
2021-03-21 19:18:51 +01:00
if (this.config.face.enabled && this.config.face.description.enabled && !this.models.faceres) this.models.faceres = await faceres.load(this.config);
2020-10-18 18:12:09 +02:00
}
2021-01-12 15:55:08 +01:00
if (this.#firstRun) {
2021-03-02 17:27:42 +01:00
if (this.config.debug) log('tf engine state:', this.tf.engine().state.numBytes, 'bytes', this.tf.engine().state.numTensors, 'tensors');
this.#firstRun = false;
2021-01-12 15:55:08 +01:00
}
2020-11-06 17:39:39 +01:00
const current = Math.trunc(now() - timeStamp);
2021-03-21 12:49:55 +01:00
if (current > (this.perf.load || 0)) this.perf.load = current;
}
2020-10-17 17:38:24 +02:00
2020-11-04 16:18:22 +01:00
// check if backend needs initialization if it changed
2021-03-14 04:31:09 +01:00
/** @hidden */
#checkBackend = async (force = false) => {
2021-02-19 14:35:41 +01:00
if (this.config.backend && (this.config.backend !== '') && force || (this.tf.getBackend() !== this.config.backend)) {
const timeStamp = now();
2020-10-30 15:23:49 +01:00
this.state = 'backend';
2020-11-01 19:07:53 +01:00
/* force backend reload
2020-10-30 16:57:23 +01:00
if (this.config.backend in tf.engine().registry) {
2020-11-01 19:07:53 +01:00
const backendFactory = tf.findBackendFactory(this.config.backend);
tf.removeBackend(this.config.backend);
tf.registerBackend(this.config.backend, backendFactory);
2020-10-30 15:23:49 +01:00
} else {
2020-12-08 15:00:44 +01:00
log('Backend not registred:', this.config.backend);
2020-10-30 15:23:49 +01:00
}
2020-11-01 19:07:53 +01:00
*/
2020-11-10 02:13:38 +01:00
2021-03-02 17:27:42 +01:00
if (this.config.backend && this.config.backend !== '') {
2021-03-21 22:47:00 +01:00
if (this.tf.ENV.flags.IS_BROWSER && this.config.backend === 'tensorflow') this.config.backend = 'webgl';
if (this.tf.ENV.flags.IS_NODE && (this.config.backend === 'webgl' || this.config.backend === 'wasm')) this.config.backend = 'tensorflow';
2021-03-02 17:27:42 +01:00
if (this.config.debug) log('setting backend:', this.config.backend);
2020-11-10 02:13:38 +01:00
2021-03-02 17:27:42 +01:00
if (this.config.backend === 'wasm') {
2021-03-06 16:38:04 +01:00
if (this.config.debug) log('wasm path:', this.config.wasmPath);
2021-03-02 17:27:42 +01:00
this.tf.setWasmPaths(this.config.wasmPath);
const simd = await this.tf.env().getAsync('WASM_HAS_SIMD_SUPPORT');
2021-03-06 16:38:04 +01:00
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'}`);
2021-03-02 17:27:42 +01:00
if (!simd) log('warning: wasm simd support is not enabled');
}
2020-11-10 02:13:38 +01:00
2021-03-02 17:27:42 +01:00
if (this.config.backend === 'humangl') backend.register();
try {
await this.tf.setBackend(this.config.backend);
} catch (err) {
log('error: cannot set backend:', this.config.backend, err);
}
2020-12-13 00:34:30 +01:00
}
2021-02-19 14:35:41 +01:00
this.tf.enableProdMode();
2020-11-01 19:07:53 +01:00
/* debug mode is really too mcuh
2020-11-09 12:32:11 +01:00
tf.enableDebugMode();
2020-11-01 19:07:53 +01:00
*/
2021-02-19 14:35:41 +01:00
if (this.tf.getBackend() === 'webgl') {
2020-11-09 12:32:11 +01:00
if (this.config.deallocate) {
2020-12-08 15:00:44 +01:00
log('changing webgl: WEBGL_DELETE_TEXTURE_THRESHOLD:', this.config.deallocate);
2021-02-19 14:35:41 +01:00
this.tf.ENV.set('WEBGL_DELETE_TEXTURE_THRESHOLD', this.config.deallocate ? 0 : -1);
2020-11-09 12:32:11 +01:00
}
2021-02-22 15:13:11 +01:00
// this.tf.ENV.set('WEBGL_FORCE_F16_TEXTURES', true);
// this.tf.ENV.set('WEBGL_PACK_DEPTHWISECONV', true);
2021-02-19 14:35:41 +01:00
const gl = await this.tf.backend().getGPGPUContext().gl;
2021-03-02 17:27:42 +01:00
if (this.config.debug) log(`gl version:${gl.getParameter(gl.VERSION)} renderer:${gl.getParameter(gl.RENDERER)}`);
2020-11-01 19:07:53 +01:00
}
2021-02-19 14:35:41 +01:00
await this.tf.ready();
2021-03-21 12:49:55 +01:00
this.perf.backend = Math.trunc(now() - timeStamp);
2020-11-06 17:39:39 +01:00
}
2020-10-30 15:23:49 +01:00
}
2020-11-04 16:18:22 +01:00
// main detect function
2021-03-17 23:23:19 +01:00
async detect(input: Input, userConfig: Config | Object = {}): Promise<Result | Error> {
2020-11-04 16:18:22 +01:00
// detection happens inside a promise
return new Promise(async (resolve) => {
2020-11-13 22:13:35 +01:00
this.state = 'config';
let timeStamp;
// update configuration
this.config = mergeDeep(this.config, userConfig);
// sanity checks
this.state = 'check';
const error = this.#sanity(input);
2020-11-13 22:13:35 +01:00
if (error) {
2020-12-08 15:00:44 +01:00
log(error, input);
2020-11-13 22:13:35 +01:00
resolve({ error });
}
const timeStart = now();
// configure backend
await this.#checkBackend();
// load models if enabled
await this.load();
2021-02-19 14:35:41 +01:00
if (this.config.scoped) this.tf.engine().startScope();
2021-03-21 12:49:55 +01:00
this.analyze('Start Scope:');
2020-10-12 01:22:43 +02:00
2020-10-16 16:12:12 +02:00
timeStamp = now();
2020-11-04 16:18:22 +01:00
const process = image.process(input, this.config);
2020-11-20 13:52:50 +01:00
if (!process || !process.tensor) {
2020-12-08 15:00:44 +01:00
log('could not convert input to tensor');
2020-11-20 13:52:50 +01:00
resolve({ error: 'could not convert input to tensor' });
return;
}
2021-03-21 12:49:55 +01:00
this.perf.image = Math.trunc(now() - timeStamp);
this.analyze('Get Image:');
2021-03-06 23:22:47 +01:00
// prepare where to store model results
let bodyRes;
let handRes;
let faceRes;
let objectRes;
2021-03-23 19:46:44 +01:00
let current;
2021-03-06 23:22:47 +01:00
2020-11-06 17:39:39 +01:00
// run face detection followed by all models that rely on face bounding box: face mesh, age, gender, emotion
if (this.config.async) {
2021-03-21 12:49:55 +01:00
faceRes = this.config.face.enabled ? faceall.detectFace(this, process.tensor) : [];
if (this.perf.face) delete this.perf.face;
2020-11-06 17:39:39 +01:00
} else {
this.state = 'run:face';
2020-10-16 16:12:12 +02:00
timeStamp = now();
2021-03-21 12:49:55 +01:00
faceRes = this.config.face.enabled ? await faceall.detectFace(this, process.tensor) : [];
2021-03-23 19:46:44 +01:00
current = Math.trunc(now() - timeStamp);
if (current > 0) this.perf.face = current;
2020-10-14 02:52:30 +02:00
}
2020-10-13 04:01:35 +02:00
2021-03-04 16:33:08 +01:00
// run body: can be posenet or blazepose
2021-03-21 12:49:55 +01:00
this.analyze('Start Body:');
2020-11-04 07:11:24 +01:00
if (this.config.async) {
2021-03-11 16:26:14 +01:00
if (this.config.body.modelPath.includes('posenet')) bodyRes = this.config.body.enabled ? this.models.posenet?.estimatePoses(process.tensor, this.config) : [];
2021-03-04 16:33:08 +01:00
else bodyRes = this.config.body.enabled ? blazepose.predict(process.tensor, this.config) : [];
2021-03-21 12:49:55 +01:00
if (this.perf.body) delete this.perf.body;
2020-11-04 07:11:24 +01:00
} else {
this.state = 'run:body';
timeStamp = now();
2021-03-11 16:26:14 +01:00
if (this.config.body.modelPath.includes('posenet')) bodyRes = this.config.body.enabled ? await this.models.posenet?.estimatePoses(process.tensor, this.config) : [];
2021-03-04 16:33:08 +01:00
else bodyRes = this.config.body.enabled ? await blazepose.predict(process.tensor, this.config) : [];
2021-03-23 19:46:44 +01:00
current = Math.trunc(now() - timeStamp);
if (current > 0) this.perf.body = current;
2020-11-04 07:11:24 +01:00
}
2021-03-21 12:49:55 +01:00
this.analyze('End Body:');
2020-11-04 07:11:24 +01:00
// run handpose
2021-03-21 12:49:55 +01:00
this.analyze('Start Hand:');
2020-11-04 07:11:24 +01:00
if (this.config.async) {
2020-12-27 14:12:22 +01:00
handRes = this.config.hand.enabled ? this.models.handpose?.estimateHands(process.tensor, this.config) : [];
2021-03-21 12:49:55 +01:00
if (this.perf.hand) delete this.perf.hand;
2020-11-04 07:11:24 +01:00
} else {
this.state = 'run:hand';
timeStamp = now();
2020-12-27 14:12:22 +01:00
handRes = this.config.hand.enabled ? await this.models.handpose?.estimateHands(process.tensor, this.config) : [];
2021-03-23 19:46:44 +01:00
current = Math.trunc(now() - timeStamp);
if (current > 0) this.perf.hand = current;
2020-11-04 07:11:24 +01:00
}
2021-03-21 12:49:55 +01:00
this.analyze('End Hand:');
2020-11-04 07:11:24 +01:00
// run nanodet
2021-03-21 12:49:55 +01:00
this.analyze('Start Object:');
if (this.config.async) {
objectRes = this.config.object.enabled ? nanodet.predict(process.tensor, this.config) : [];
2021-03-21 12:49:55 +01:00
if (this.perf.object) delete this.perf.object;
} else {
this.state = 'run:object';
timeStamp = now();
objectRes = this.config.object.enabled ? await nanodet.predict(process.tensor, this.config) : [];
2021-03-23 19:46:44 +01:00
current = Math.trunc(now() - timeStamp);
if (current > 0) this.perf.object = current;
}
2021-03-21 12:49:55 +01:00
this.analyze('End Object:');
2020-11-06 17:39:39 +01:00
// if async wait for results
if (this.config.async) {
[faceRes, bodyRes, handRes, objectRes] = await Promise.all([faceRes, bodyRes, handRes, objectRes]);
2020-11-06 17:39:39 +01:00
}
2020-11-04 16:18:22 +01:00
process.tensor.dispose();
2020-10-14 17:43:33 +02:00
2021-02-19 14:35:41 +01:00
if (this.config.scoped) this.tf.engine().endScope();
2021-03-21 12:49:55 +01:00
this.analyze('End Scope:');
2020-10-17 13:15:23 +02:00
2020-11-04 16:18:22 +01:00
let gestureRes = [];
if (this.config.gesture.enabled) {
timeStamp = now();
2021-02-08 18:47:38 +01:00
// @ts-ignore
2021-03-04 16:33:08 +01:00
gestureRes = [...gesture.face(faceRes), ...gesture.body(bodyRes), ...gesture.hand(handRes), ...gesture.iris(faceRes)];
2021-03-21 12:49:55 +01:00
if (!this.config.async) this.perf.gesture = Math.trunc(now() - timeStamp);
else if (this.perf.gesture) delete this.perf.gesture;
2020-11-04 16:18:22 +01:00
}
2021-03-21 12:49:55 +01:00
this.perf.total = Math.trunc(now() - timeStart);
2020-11-06 17:39:39 +01:00
this.state = 'idle';
2021-03-17 19:35:11 +01:00
const result = {
face: faceRes,
body: bodyRes,
hand: handRes,
gesture: gestureRes,
object: objectRes,
2021-03-21 12:49:55 +01:00
performance: this.perf,
2021-03-17 19:35:11 +01:00
canvas: process.canvas,
};
// log('Result:', result);
resolve(result);
});
}
2020-11-09 20:26:10 +01:00
2021-03-14 04:31:09 +01:00
/** @hidden */
#warmupBitmap = async () => {
2020-12-16 20:49:14 +01:00
const b64toBlob = (base64, type = 'application/octet-stream') => fetch(`data:${type};base64,${base64}`).then((res) => res.blob());
let blob;
2021-01-12 14:24:00 +01:00
let res;
2020-12-16 20:49:14 +01:00
switch (this.config.warmup) {
case 'face': blob = await b64toBlob(sample.face); break;
case 'full': blob = await b64toBlob(sample.body); break;
default: blob = null;
}
2021-01-12 14:24:00 +01:00
if (blob) {
const bitmap = await createImageBitmap(blob);
2021-01-30 19:23:07 +01:00
res = await this.detect(bitmap, this.config);
2021-01-12 14:24:00 +01:00
bitmap.close();
}
return res;
}
2021-03-14 04:31:09 +01:00
/** @hidden */
#warmupCanvas = async () => new Promise((resolve) => {
let src;
let size = 0;
switch (this.config.warmup) {
case 'face':
size = 256;
src = 'data:image/jpeg;base64,' + sample.face;
break;
case 'full':
case 'body':
size = 1200;
src = 'data:image/jpeg;base64,' + sample.body;
break;
default:
src = null;
}
// src = encodeURI('../assets/human-sample-upper.jpg');
const img = new Image();
img.onload = async () => {
const canvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(size, size) : document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
ctx?.drawImage(img, 0, 0);
// const data = ctx?.getImageData(0, 0, canvas.height, canvas.width);
const res = await this.detect(canvas, this.config);
resolve(res);
};
if (src) img.src = src;
else resolve(null);
});
2021-01-12 14:24:00 +01:00
2021-03-14 04:31:09 +01:00
/** @hidden */
#warmupNode = async () => {
2021-01-30 19:23:07 +01:00
const atob = (str) => Buffer.from(str, 'base64');
const img = this.config.warmup === 'face' ? atob(sample.face) : atob(sample.body);
2021-02-08 17:39:09 +01:00
// @ts-ignore
const data = tf.node.decodeJpeg(img); // tf.node is only defined when compiling for nodejs
2021-01-30 19:23:07 +01:00
const expanded = data.expandDims(0);
2021-02-19 14:35:41 +01:00
this.tf.dispose(data);
2021-01-30 19:23:07 +01:00
// log('Input:', expanded);
const res = await this.detect(expanded, this.config);
2021-02-19 14:35:41 +01:00
this.tf.dispose(expanded);
2021-01-30 19:23:07 +01:00
return res;
}
2021-03-17 23:23:19 +01:00
async warmup(userConfig: Config | Object = {}): Promise<Result | { error }> {
2020-12-16 20:49:14 +01:00
const t0 = now();
2021-01-12 14:24:00 +01:00
if (userConfig) this.config = mergeDeep(this.config, userConfig);
const save = this.config.videoOptimized;
2021-01-12 14:24:00 +01:00
this.config.videoOptimized = false;
let res;
if (typeof createImageBitmap === 'function') res = await this.#warmupBitmap();
else if (typeof Image !== 'undefined') res = await this.#warmupCanvas();
else res = await this.#warmupNode();
this.config.videoOptimized = save;
2021-01-12 14:24:00 +01:00
const t1 = now();
2021-03-06 23:22:47 +01:00
if (this.config.debug) log('Warmup', this.config.warmup, Math.round(t1 - t0), 'ms', res);
2021-01-12 14:24:00 +01:00
return res;
2020-11-09 20:26:10 +01:00
}
2020-10-12 01:22:43 +02:00
}
2021-03-17 23:23:19 +01:00
/**
* Class Human is also available as default export
*/
export { Human as default };