2021-05-25 14:58:20 +02:00
/ * *
* Human main module
2021-10-27 15:45:38 +02:00
* @default Human Library
* @summary < https : / / github.com / vladmandic / human >
2021-10-25 19:09:00 +02:00
* @author < https : / / github.com / vladmandic >
2021-10-27 15:45:38 +02:00
* @copyright < https : / / github.com / vladmandic >
* @license MIT
2021-05-25 14:58:20 +02:00
* /
2021-09-30 20:28:16 +02:00
// module imports
2021-09-27 19:58:13 +02:00
import { log , now , mergeDeep , validate } from './util/util' ;
2021-09-30 20:28:16 +02:00
import { defaults } from './config' ;
2021-10-21 16:26:44 +02:00
import { env , Env } from './util/env' ;
2020-11-18 14:26:28 +01:00
import * as tf from '../dist/tfjs.esm.js' ;
2021-09-30 20:28:16 +02:00
import * as app from '../package.json' ;
import * as backend from './tfjs/backend' ;
import * as blazepose from './body/blazepose' ;
import * as centernet from './object/centernet' ;
import * as draw from './util/draw' ;
import * as efficientpose from './body/efficientpose' ;
2021-09-27 19:58:13 +02:00
import * as face from './face/face' ;
2021-09-28 18:01:48 +02:00
import * as facemesh from './face/facemesh' ;
2021-09-27 19:58:13 +02:00
import * as faceres from './face/faceres' ;
2021-09-30 20:28:16 +02:00
import * as gesture from './gesture/gesture' ;
2021-10-20 15:10:57 +02:00
import * as handpose from './hand/handpose' ;
2021-09-30 20:28:16 +02:00
import * as handtrack from './hand/handtrack' ;
import * as humangl from './tfjs/humangl' ;
import * as image from './image/image' ;
import * as interpolate from './util/interpolate' ;
import * as match from './face/match' ;
import * as models from './models' ;
2021-09-27 19:58:13 +02:00
import * as movenet from './body/movenet' ;
2021-05-19 14:27:28 +02:00
import * as nanodet from './object/nanodet' ;
2021-09-28 18:01:48 +02:00
import * as persons from './util/persons' ;
2021-09-30 20:28:16 +02:00
import * as posenet from './body/posenet' ;
import * as segmentation from './segmentation/segmentation' ;
2021-09-13 00:37:06 +02:00
import * as warmups from './warmup' ;
2021-09-30 20:28:16 +02:00
// type definitions
2021-11-14 17:22:52 +01:00
import type { Input , Tensor , DrawOptions , Config , Result , FaceResult , HandResult , BodyResult , ObjectResult , GestureResult , PersonResult , AnyCanvas } from './exports' ;
2021-10-25 19:09:00 +02:00
// type exports
export * from './exports' ;
2021-04-13 17:05:52 +02:00
2021-09-17 20:07:44 +02:00
/ * * * * H u m a n * * l i b r a r y m a i n c l a s s
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-05-31 00:45:39 +02:00
*
2021-11-17 21:45:49 +01:00
* @param userConfig - { @link Config }
2021-10-25 19:09:00 +02:00
* @returns instance of { @link Human }
2021-03-17 23:23:19 +01:00
* /
2021-03-14 04:31:09 +01:00
export class Human {
2021-05-31 16:40:07 +02:00
/** Current version of Human library in *semver* format */
2021-09-05 22:42:11 +02:00
version : string ;
2021-09-16 00:58:54 +02:00
2021-04-13 17:05:52 +02:00
/ * * C u r r e n t c o n f i g u r a t i o n
2021-11-07 16:03:33 +01:00
* - Defaults : [ config ] ( https : //github.com/vladmandic/human/blob/main/src/config.ts#L262)
2021-04-13 17:05:52 +02:00
* /
2021-03-17 23:23:19 +01:00
config : Config ;
2021-09-16 00:58:54 +02:00
2021-05-30 00:29:57 +02:00
/ * * L a s t k n o w n r e s u l t o f d e t e c t r u n
* - Can be accessed anytime after initial detection
2021-09-24 15:55:27 +02:00
* /
2021-05-30 00:29:57 +02:00
result : Result ;
2021-09-16 00:58:54 +02:00
2021-04-13 17:05:52 +02:00
/ * * C u r r e n t s t a t e o f H u m a n l i b r a r y
* - Can be polled to determine operations that are currently executed
2021-05-31 16:40:07 +02:00
* - Progresses through : 'config' , 'check' , 'backend' , 'load' , 'run:<model>' , 'idle'
2021-04-13 17:05:52 +02:00
* /
2021-03-18 01:16:40 +01:00
state : string ;
2021-09-16 00:58:54 +02:00
2021-09-11 22:00:16 +02:00
/** currenty processed image tensor and canvas */
2021-11-14 17:22:52 +01:00
process : { tensor : Tensor | null , canvas : AnyCanvas | null } ;
2021-09-16 00:58:54 +02:00
2021-09-17 20:07:44 +02:00
/ * * I n s t a n c e o f T e n s o r F l o w / J S u s e d b y H u m a n
* - Can be embedded or externally provided
2021-11-17 21:45:49 +01:00
* [ TFJS API ] : { @link https : //js.tensorflow.org/api/latest/}
2021-04-13 17:05:52 +02:00
* /
2021-11-17 21:45:49 +01:00
tf ;
2021-09-16 00:58:54 +02:00
2021-09-17 20:07:44 +02:00
/** Object containing environment information used for diagnostics */
2021-10-21 16:26:44 +02:00
env : Env ;
2021-09-16 00:58:54 +02:00
2021-06-18 15:16:21 +02:00
/ * * D r a w h e l p e r c l a s s e s t h a t c a n d r a w d e t e c t e d o b j e c t s o n c a n v a s u s i n g s p e c i f i e d d r a w
2021-11-17 21:45:49 +01:00
* - canvas : draws input to canvas
* - options : are global settings for all draw operations , can be overriden for each draw method { @link DrawOptions }
* - face , body , hand , gesture , object , person : draws detected results as overlays on canvas
2021-04-13 17:05:52 +02:00
* /
2021-10-21 16:54:51 +02:00
draw : { canvas : typeof draw . canvas , face : typeof draw . face , body : typeof draw . body , hand : typeof draw . hand , gesture : typeof draw . gesture , object : typeof draw . object , person : typeof draw . person , all : typeof draw . all , options : DrawOptions } ;
2021-09-16 00:58:54 +02:00
2021-09-17 20:07:44 +02:00
/ * * C u r r e n t l y l o a d e d m o d e l s
* @internal
2021-09-24 15:55:27 +02:00
* { @link Models }
2021-09-17 20:07:44 +02:00
* /
2021-09-23 20:09:41 +02:00
models : models.Models ;
2021-09-16 00:58:54 +02:00
2021-09-11 22:00:16 +02:00
/ * * C o n t a i n e r f o r e v e n t s d i s p a t c h e d b y H u m a n
* Possible events :
* - ` create ` : triggered when Human object is instantiated
* - ` load ` : triggered when models are loaded ( explicitly or on - demand )
2021-09-22 21:16:14 +02:00
* - ` image ` : triggered when input image is processed
2021-09-11 22:00:16 +02:00
* - ` result ` : triggered when detection is complete
* - ` warmup ` : triggered when warmup is complete
2021-09-17 20:07:44 +02:00
* - ` error ` : triggered on some errors
2021-09-11 22:00:16 +02:00
* /
2021-09-30 20:28:16 +02:00
events : EventTarget | undefined ;
2021-05-31 16:40:07 +02:00
/** Reference face triangualtion array of 468 points, used for triangle references between points */
2021-11-17 21:45:49 +01:00
faceTriangulation : number [ ] ;
2021-05-31 16:40:07 +02:00
/** Refernce UV map of 468 values, used for 3D mapping of the face mesh */
2021-11-17 21:45:49 +01:00
faceUVMap : [ number , number ] [ ] ;
2021-04-13 17:05:52 +02:00
/** Performance object that contains values for all recently performed operations */
2021-06-03 15:41:53 +02:00
performance : Record < string , number > ; // perf members are dynamically defined as needed
2021-03-10 15:44:45 +01:00
# numTensors : number ;
2021-03-18 01:16:40 +01:00
# analyzeMemoryLeaks : boolean ;
# checkSanity : boolean ;
2021-09-17 17:23:00 +02:00
/** WebGL debug info */
gl : Record < string , unknown > ;
2021-03-10 15:44:45 +01:00
// definition end
2021-02-08 17:39:09 +01:00
2021-09-17 20:07:44 +02:00
/ * * C o n s t r u c t o r f o r * * H u m a n * * l i b r a r y t h a t i s f u t h e r u s e d f o r a l l o p e r a t i o n s
2021-11-17 21:45:49 +01:00
* @param userConfig - user configuration object { @link Config }
2021-04-13 17:05:52 +02:00
* /
2021-09-11 22:11:00 +02:00
constructor ( userConfig? : Partial < Config > ) {
2021-10-21 16:26:44 +02:00
this . env = env ;
2021-10-21 18:42:08 +02:00
defaults . wasmPath = tf . version_core . includes ( '-' ) // custom build or official build
? 'https://vladmandic.github.io/tfjs/dist/'
: ` https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-wasm@ ${ tf . version_core } /dist/ ` ;
2021-10-21 16:26:44 +02:00
defaults . modelBasePath = env . browser ? '../models/' : 'file://models/' ;
defaults . backend = env . browser ? 'humangl' : 'tensorflow' ;
2021-09-05 22:42:11 +02:00
this . version = app . version ; // expose version property on instance of class
2021-09-02 14:50:16 +02:00
Object . defineProperty ( this , 'version' , { value : app.version } ) ; // expose version property directly on class itself
2021-09-19 20:07:53 +02:00
this . config = JSON . parse ( JSON . stringify ( defaults ) ) ;
Object . seal ( this . config ) ;
if ( userConfig ) this . config = mergeDeep ( this . config , userConfig ) ;
2020-10-19 17:03:48 +02:00
this . tf = tf ;
this . state = 'idle' ;
2021-03-10 15:44:45 +01:00
this . # numTensors = 0 ;
this . # analyzeMemoryLeaks = false ;
this . # checkSanity = false ;
2021-10-27 15:45:38 +02:00
this . performance = { } ;
2021-09-30 20:28:16 +02:00
this . events = ( typeof EventTarget !== 'undefined' ) ? new EventTarget ( ) : undefined ;
2020-10-19 17:03:48 +02:00
// object that contains all initialized models
2021-09-23 20:09:41 +02:00
this . models = new models . Models ( ) ;
2021-09-16 00:58:54 +02:00
// reexport draw methods
this . draw = {
options : draw.options as DrawOptions ,
2021-11-14 17:22:52 +01:00
canvas : ( input : AnyCanvas | HTMLImageElement | HTMLMediaElement | HTMLVideoElement , output : AnyCanvas ) = > draw . canvas ( input , output ) ,
face : ( output : AnyCanvas , result : FaceResult [ ] , options? : Partial < DrawOptions > ) = > draw . face ( output , result , options ) ,
body : ( output : AnyCanvas , result : BodyResult [ ] , options? : Partial < DrawOptions > ) = > draw . body ( output , result , options ) ,
hand : ( output : AnyCanvas , result : HandResult [ ] , options? : Partial < DrawOptions > ) = > draw . hand ( output , result , options ) ,
gesture : ( output : AnyCanvas , result : GestureResult [ ] , options? : Partial < DrawOptions > ) = > draw . gesture ( output , result , options ) ,
object : ( output : AnyCanvas , result : ObjectResult [ ] , options? : Partial < DrawOptions > ) = > draw . object ( output , result , options ) ,
person : ( output : AnyCanvas , result : PersonResult [ ] , options? : Partial < DrawOptions > ) = > draw . person ( output , result , options ) ,
all : ( output : AnyCanvas , result : Result , options? : Partial < DrawOptions > ) = > draw . all ( output , result , options ) ,
2021-09-16 00:58:54 +02:00
} ;
2021-11-14 17:22:52 +01:00
this . result = { face : [ ] , body : [ ] , hand : [ ] , gesture : [ ] , object : [ ] , performance : { } , timestamp : 0 , persons : [ ] , error : null } ;
2021-09-22 21:16:14 +02:00
// export access to image processing
2021-04-14 18:53:00 +02:00
// @ts-ignore eslint-typescript cannot correctly infer type in anonymous function
2021-09-11 22:00:16 +02:00
this . process = { tensor : null , canvas : null } ;
2020-10-19 17:03:48 +02:00
// export raw access to underlying models
2021-03-29 21:59:16 +02:00
this . faceTriangulation = facemesh . triangulation ;
this . faceUVMap = facemesh . uvmap ;
2021-09-17 17:23:00 +02:00
// set gl info
this . gl = humangl . config ;
2021-03-06 16:38:04 +01:00
// include platform info
2021-09-13 00:37:06 +02:00
this . emit ( 'create' ) ;
2020-10-18 18:12:09 +02:00
}
2020-10-19 17:03:48 +02:00
2021-11-17 21:45:49 +01:00
/** internal function to measure tensor leaks */
2021-08-17 14:51:17 +02:00
analyze = ( . . . msg : string [ ] ) = > {
2021-03-10 15:44:45 +01:00
if ( ! this . # analyzeMemoryLeaks ) return ;
2021-05-24 17:10:13 +02:00
const currentTensors = this . tf . engine ( ) . state . numTensors ;
const previousTensors = this . # numTensors ;
this . # numTensors = currentTensors ;
const leaked = currentTensors - previousTensors ;
2020-12-08 15:00:44 +01:00
if ( leaked !== 0 ) log ( . . . msg , leaked ) ;
2021-10-12 20:17:33 +02:00
} ;
2020-10-17 13:15:23 +02:00
2021-11-17 21:45:49 +01:00
/** internal function for quick sanity check on inputs @hidden */
2021-08-17 14:51:17 +02:00
# sanity = ( input : Input ) : null | string = > {
2021-03-10 15:44:45 +01:00
if ( ! this . # checkSanity ) return null ;
2020-11-03 16:55:33 +01:00
if ( ! input ) return 'input is not defined' ;
2021-09-12 19:17:33 +02:00
if ( this . env . 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-10-12 20:17:33 +02:00
} ;
2020-11-03 16:55:33 +01:00
2021-09-19 20:07:53 +02:00
/** Reset configuration to default values */
2021-09-30 20:28:16 +02:00
reset ( ) : void {
2021-09-21 03:59:49 +02:00
const currentBackend = this . config . backend ; // save backend;
this . config = JSON . parse ( JSON . stringify ( defaults ) ) ;
this . config . backend = currentBackend ;
}
2021-09-19 20:07:53 +02:00
/** Validate current configuration schema */
2021-10-26 21:08:05 +02:00
validate ( userConfig? : Partial < Config > ) {
2021-09-25 17:51:15 +02:00
return validate ( defaults , userConfig || this . config ) ;
}
2021-09-19 20:07:53 +02:00
2021-11-17 21:45:49 +01:00
/** Exports face matching methods {@link match#similarity} */
2021-09-30 20:28:16 +02:00
public similarity = match . similarity ;
2021-11-17 21:45:49 +01:00
/** Exports face matching methods {@link match#distance} */
2021-09-30 20:28:16 +02:00
public distance = match . distance ;
2021-11-17 21:45:49 +01:00
/** Exports face matching methods {@link match#match} */
2021-09-30 20:28:16 +02:00
public match = match . match ;
2021-10-19 17:28:59 +02:00
/** Utility wrapper for performance.now() */
now ( ) : number {
return now ( ) ;
}
2021-09-13 00:37:06 +02:00
/ * * P r o c e s s i n p u t a s r e t u r n c a n v a s a n d t e n s o r
*
2021-11-17 21:45:49 +01:00
* @param input - any input { @link Input }
* @param getTensor - should image processing also return tensor or just canvas
* Returns object with ` tensor ` and ` canvas `
2021-09-13 00:37:06 +02:00
* /
2021-10-10 23:52:43 +02:00
image ( input : Input , getTensor : boolean = true ) {
return image . process ( input , this . config , getTensor ) ;
2021-09-25 17:51:15 +02:00
}
2021-09-13 00:37:06 +02:00
2021-09-22 21:16:14 +02:00
/ * * S e g m e n t a t i o n m e t h o d t a k e s a n y i n p u t a n d r e t u r n s p r o c e s s e d c a n v a s w i t h b o d y s e g m e n t a t i o n
* - Segmentation is not triggered as part of detect process
2021-11-17 21:45:49 +01:00
* @param input - { @link Input }
* @param background - { @link Input }
2021-10-25 19:09:00 +02:00
* - Optional parameter background is used to fill the background with specific input
2021-11-17 21:45:49 +01:00
* Returns :
2021-10-25 19:09:00 +02:00
* - ` data ` as raw data array with per - pixel segmentation values
* - ` canvas ` as canvas which is input image filtered with segementation data and optionally merged with background image . canvas alpha values are set to segmentation values for easy merging
* - ` alpha ` as grayscale canvas that represents segmentation alpha values
2021-06-05 17:54:49 +02:00
* /
2021-11-14 17:22:52 +01:00
async segmentation ( input : Input , background? : Input ) : Promise < { data : number [ ] | Tensor , canvas : AnyCanvas | null , alpha : AnyCanvas | null } > {
2021-09-22 21:16:14 +02:00
return segmentation . process ( input , background , this . config ) ;
2021-06-05 17:54:49 +02:00
}
2021-09-17 20:07:44 +02:00
/ * * E n h a n c e m e t h o d p e r f o r m s a d d i t i o n a l e n h a c e m e n t s t o f a c e i m a g e p r e v i o u s l y d e t e c t e d f o r f u t h e r p r o c e s s i n g
*
2021-11-17 21:45:49 +01:00
* @param input - Tensor as provided in human . result . face [ n ] . tensor
2021-04-13 17:05:52 +02:00
* @returns Tensor
* /
2021-03-15 17:14:48 +01:00
// 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 ) ;
2021-03-15 17:14:48 +01:00
}
2021-11-07 16:03:33 +01:00
/ * * C o m p a r e t w o i n p u t t e n s o r s f o r p i x e l s i m m i l a r i t y
* - use ` human.image ` to process any valid input and get a tensor that can be used for compare
* - when passing manually generated tensors :
* - both input tensors must be in format [ 1 , height , width , 3 ]
* - if resolution of tensors does not match , second tensor will be resized to match resolution of the first tensor
* - return value is pixel similarity score normalized by input resolution and rgb channels
* /
compare ( firstImageTensor : Tensor , secondImageTensor : Tensor ) : Promise < number > {
return image . compare ( this . config , firstImageTensor , secondImageTensor ) ;
}
2021-09-17 20:07:44 +02:00
/ * * E x p l i c i t b a c k e n d i n i t i a l i z a t i o n
* - Normally done implicitly during initial load phase
* - Call to explictly register and initialize TFJS backend without any other operations
2021-09-21 03:59:49 +02:00
* - Use when changing backend during runtime
2021-09-17 20:07:44 +02:00
* /
2021-09-30 20:28:16 +02:00
async init ( ) : Promise < void > {
2021-09-21 03:59:49 +02:00
await backend . check ( this , true ) ;
await this . tf . ready ( ) ;
2021-09-17 20:07:44 +02:00
}
2021-04-13 17:05:52 +02:00
/ * * L o a d m e t h o d p r e l o a d s a l l c o n f i g u r e d m o d e l s o n - d e m a n d
* - Not explicitly required as any required model is load implicitly on it ' s first run
2021-09-17 20:07:44 +02:00
*
2021-11-17 21:45:49 +01:00
* @param userConfig - { @link Config }
2021-04-13 17:05:52 +02:00
* /
2021-09-30 20:28:16 +02:00
async load ( userConfig? : Partial < Config > ) : Promise < void > {
2020-11-06 17:39:39 +01:00
this . state = 'load' ;
const timeStamp = now ( ) ;
2021-09-11 22:00:16 +02:00
const count = Object . values ( this . models ) . filter ( ( model ) = > model ) . length ;
2021-06-03 15:41:53 +02:00
if ( userConfig ) this . config = mergeDeep ( this . config , userConfig ) as Config ;
2020-11-03 15:34:36 +01:00
2021-10-23 15:38:52 +02:00
if ( this . env . initial ) { // print version info on first run and check for correct backend setup
2021-09-05 22:42:11 +02:00
if ( this . config . debug ) log ( ` version: ${ this . version } ` ) ;
2021-03-06 16:38:04 +01:00
if ( this . config . debug ) log ( ` tfjs version: ${ this . tf . version_core } ` ) ;
2021-09-17 20:07:44 +02:00
if ( ! await backend . check ( this ) ) log ( 'error: backend check failed' ) ;
2021-09-13 00:37:06 +02:00
await tf . ready ( ) ;
2021-09-12 19:17:33 +02:00
if ( this . env . browser ) {
2021-03-02 17:27:42 +01:00
if ( this . config . debug ) log ( 'configuration:' , this . config ) ;
2021-10-27 14:16:06 +02:00
if ( this . config . debug ) log ( 'environment:' , this . env ) ;
2021-10-26 21:08:05 +02:00
if ( this . config . debug ) log ( 'tf flags:' , this . tf . ENV [ 'flags' ] ) ;
2020-11-21 18:21:47 +01:00
}
2020-11-03 15:34:36 +01:00
}
2021-06-18 15:16:21 +02:00
await models . load ( this ) ; // actually loads models
2021-10-23 15:38:52 +02:00
if ( this . env . initial && this . config . debug ) log ( 'tf engine state:' , this . tf . engine ( ) . state . numBytes , 'bytes' , this . tf . engine ( ) . state . numTensors , 'tensors' ) ; // print memory stats on first run
this . env . initial = false ;
2021-01-12 15:55:08 +01:00
2021-09-11 22:00:16 +02:00
const loaded = Object . values ( this . models ) . filter ( ( model ) = > model ) . length ;
2021-09-13 00:37:06 +02:00
if ( loaded !== count ) { // number of loaded models changed
await models . validate ( this ) ; // validate kernel ops used by model against current backend
this . emit ( 'load' ) ;
}
2020-11-06 17:39:39 +01:00
const current = Math . trunc ( now ( ) - timeStamp ) ;
2021-10-27 15:45:38 +02:00
if ( current > ( this . performance . loadModels as number || 0 ) ) this . performance . loadModels = this . env . perfadd ? ( this . performance . loadModels || 0 ) + current : current ;
2020-10-18 15:21:53 +02:00
}
2020-10-17 17:38:24 +02:00
2021-11-17 21:45:49 +01:00
/** emit event */
2021-09-30 20:28:16 +02:00
emit = ( event : string ) = > {
if ( this . events && this . events . dispatchEvent ) this . events ? . dispatchEvent ( new Event ( event ) ) ;
2021-10-12 20:17:33 +02:00
} ;
2020-10-30 15:23:49 +01:00
2021-09-17 20:07:44 +02:00
/ * * R u n s i n t e r p o l a t i o n u s i n g l a s t k n o w n r e s u l t a n d r e t u r n s s m o o t h e n e d r e s u l t
2021-05-31 16:40:07 +02:00
* Interpolation is based on time since last known result so can be called independently
2021-06-05 17:54:49 +02:00
*
2021-11-17 21:45:49 +01:00
* @param result - { @link Result } optional use specific result set to run interpolation on
* @returns result - { @link Result }
2021-05-31 16:40:07 +02:00
* /
2021-09-30 20:28:16 +02:00
next ( result : Result = this . result ) : Result {
2021-10-09 00:39:04 +02:00
return interpolate . calc ( result , this . config ) as Result ;
2021-09-15 19:59:18 +02:00
}
2021-05-31 16:40:07 +02:00
2021-09-13 00:37:06 +02:00
/ * * W a r m u p m e t h o d p r e - i n i t i a l i z e s a l l c o n f i g u r e d m o d e l s f o r f a s t e r i n f e r e n c e
* - can take significant time on startup
* - only used for ` webgl ` and ` humangl ` backends
2021-11-17 21:45:49 +01:00
* @param userConfig - { @link Config }
* @returns result - { @link Result }
2021-09-13 00:37:06 +02:00
* /
2021-10-27 15:45:38 +02:00
async warmup ( userConfig? : Partial < Config > ) {
const t0 = now ( ) ;
const res = await warmups . warmup ( this , userConfig ) ;
const t1 = now ( ) ;
this . performance . warmup = Math . trunc ( t1 - t0 ) ;
return res ;
2021-09-15 19:59:18 +02:00
}
2021-05-18 17:26:16 +02:00
2021-11-05 16:28:06 +01:00
/ * * R u n d e t e c t w i t h t e n s o r f l o w p r o f i l i n g
* - result object will contain total exeuction time information for top - 20 kernels
* - actual detection object can be accessed via ` human.result `
* /
async profile ( input : Input , userConfig? : Partial < Config > ) : Promise < Record < string , number > > {
const profile = await this . tf . profile ( ( ) = > this . detect ( input , userConfig ) ) ;
const kernels = { } ;
for ( const kernel of profile . kernels ) { // sum kernel time values per kernel
if ( kernels [ kernel . name ] ) kernels [ kernel . name ] += kernel . kernelTimeMs ;
else kernels [ kernel . name ] = kernel . kernelTimeMs ;
}
2021-11-17 22:50:21 +01:00
const kernelArr : Array < { name : string , ms : number } > = [ ] ;
Object . entries ( kernels ) . forEach ( ( key ) = > kernelArr . push ( { name : key [ 0 ] , ms : key [ 1 ] as unknown as number } ) ) ; // convert to array
2021-11-05 16:28:06 +01:00
kernelArr . sort ( ( a , b ) = > b . ms - a . ms ) ; // sort
kernelArr . length = 20 ; // crop
const res : Record < string , number > = { } ;
for ( const kernel of kernelArr ) res [ kernel . name ] = kernel . ms ; // create perf objects
return res ;
}
2021-04-13 17:05:52 +02:00
/ * * M a i n d e t e c t i o n m e t h o d
* - Analyze configuration : { @link Config }
2021-09-22 21:16:14 +02:00
* - Pre - process input : { @link Input }
2021-04-13 17:05:52 +02:00
* - Run inference for all configured models
2021-09-13 00:37:06 +02:00
* - Process and return result : { @link Result }
2021-06-05 17:54:49 +02:00
*
2021-11-17 21:45:49 +01:00
* @param input - { @link Input }
* @param userConfig - { @link Config }
* @returns result - { @link Result }
2021-04-13 17:05:52 +02:00
* /
2021-11-14 17:22:52 +01:00
async detect ( input : Input , userConfig? : Partial < Config > ) : Promise < Result > {
2020-11-04 16:18:22 +01:00
// detection happens inside a promise
2021-09-21 03:59:49 +02:00
this . state = 'detect' ;
2020-10-19 17:03:48 +02:00
return new Promise ( async ( resolve ) = > {
2020-11-13 22:13:35 +01:00
this . state = 'config' ;
let timeStamp ;
// update configuration
2021-06-03 15:41:53 +02:00
this . config = mergeDeep ( this . config , userConfig ) as Config ;
2020-11-13 22:13:35 +01:00
// sanity checks
this . state = 'check' ;
2021-03-10 15:44:45 +01:00
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 ) ;
2021-11-14 17:22:52 +01:00
this . emit ( 'error' ) ;
resolve ( { face : [ ] , body : [ ] , hand : [ ] , gesture : [ ] , object : [ ] , performance : this.performance , timestamp : now ( ) , persons : [ ] , error } ) ;
2020-11-13 22:13:35 +01:00
}
2020-10-19 17:03:48 +02:00
const timeStart = now ( ) ;
2020-10-14 19:23:02 +02:00
2021-09-13 00:37:06 +02:00
// configure backend if needed
await backend . check ( this ) ;
2020-10-17 16:06:02 +02:00
2020-10-19 17:03:48 +02:00
// load models if enabled
await this . load ( ) ;
2020-10-18 15:21:53 +02:00
2020-10-16 16:12:12 +02:00
timeStamp = now ( ) ;
2021-09-21 03:59:49 +02:00
this . state = 'image' ;
2021-11-14 17:22:52 +01:00
const img = await image . process ( input , this . config ) as { canvas : AnyCanvas , tensor : Tensor } ;
2021-09-13 19:28:35 +02:00
this . process = img ;
2021-10-27 15:45:38 +02:00
this . performance . inputProcess = this . env . perfadd ? ( this . performance . inputProcess || 0 ) + Math . trunc ( now ( ) - timeStamp ) : Math . trunc ( now ( ) - timeStamp ) ;
2021-06-05 23:51:46 +02:00
this . analyze ( 'Get Image:' ) ;
2021-09-13 19:28:35 +02:00
if ( ! img . tensor ) {
2021-09-20 15:42:34 +02:00
if ( this . config . debug ) log ( 'could not convert input to tensor' ) ;
2021-11-14 17:22:52 +01:00
this . emit ( 'error' ) ;
resolve ( { face : [ ] , body : [ ] , hand : [ ] , gesture : [ ] , object : [ ] , performance : this.performance , timestamp : now ( ) , persons : [ ] , error : 'could not convert input to tensor' } ) ;
2020-11-20 13:52:50 +01:00
return ;
}
2021-09-13 00:37:06 +02:00
this . emit ( 'image' ) ;
2020-10-19 17:03:48 +02:00
2021-05-18 17:26:16 +02:00
timeStamp = now ( ) ;
2021-10-23 15:38:52 +02:00
this . config . skipAllowed = await image . skip ( this . config , img . tensor ) ;
2021-10-27 15:45:38 +02:00
if ( ! this . performance . totalFrames ) this . performance . totalFrames = 0 ;
if ( ! this . performance . cachedFrames ) this . performance . cachedFrames = 0 ;
( this . performance . totalFrames as number ) ++ ;
if ( this . config . skipAllowed ) this . performance . cachedFrames ++ ;
2021-11-06 15:21:51 +01:00
this . performance . cacheCheck = this . env . perfadd ? ( this . performance . cacheCheck || 0 ) + Math . trunc ( now ( ) - timeStamp ) : Math . trunc ( now ( ) - timeStamp ) ;
2021-05-18 17:26:16 +02:00
this . analyze ( 'Check Changed:' ) ;
2021-03-06 23:22:47 +01:00
// prepare where to store model results
2021-05-22 20:53:51 +02:00
// keep them with weak typing as it can be promise or not
2021-09-12 05:54:35 +02:00
let faceRes : FaceResult [ ] | Promise < FaceResult [ ] > | never [ ] = [ ] ;
let bodyRes : BodyResult [ ] | Promise < BodyResult [ ] > | never [ ] = [ ] ;
let handRes : HandResult [ ] | Promise < HandResult [ ] > | never [ ] = [ ] ;
let objectRes : ObjectResult [ ] | Promise < ObjectResult [ ] > | never [ ] = [ ] ;
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
2021-09-21 03:59:49 +02:00
this . state = 'detect:face' ;
2020-11-06 17:39:39 +01:00
if ( this . config . async ) {
2021-09-13 19:28:35 +02:00
faceRes = this . config . face . enabled ? face . detectFace ( this , img . tensor ) : [ ] ;
2021-05-31 16:40:07 +02:00
if ( this . performance . face ) delete this . performance . face ;
2020-11-06 17:39:39 +01:00
} else {
2020-10-16 16:12:12 +02:00
timeStamp = now ( ) ;
2021-09-13 19:28:35 +02:00
faceRes = this . config . face . enabled ? await face . detectFace ( this , img . tensor ) : [ ] ;
2021-10-23 15:38:52 +02:00
this . performance . face = this . env . perfadd ? ( this . performance . face || 0 ) + Math . trunc ( now ( ) - timeStamp ) : Math . trunc ( now ( ) - timeStamp ) ;
2020-10-14 02:52:30 +02:00
}
2020-10-13 04:01:35 +02:00
2021-09-26 01:14:03 +02:00
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
2021-05-29 15:20:01 +02:00
// run body: can be posenet, blazepose, efficientpose, movenet
2021-03-21 12:49:55 +01:00
this . analyze ( 'Start Body:' ) ;
2021-09-21 03:59:49 +02:00
this . state = 'detect:body' ;
2021-09-27 14:53:41 +02:00
const bodyConfig = this . config . body . maxDetected === - 1 ? mergeDeep ( this . config , { body : { maxDetected : this.config.face.enabled ? 1 * ( faceRes as FaceResult [ ] ) . length : 1 } } ) : this . config ; // autodetect number of bodies
2020-11-04 07:11:24 +01:00
if ( this . config . async ) {
2021-09-26 01:14:03 +02:00
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 ) : [ ] ;
2021-05-31 16:40:07 +02:00
if ( this . performance . body ) delete this . performance . body ;
2020-11-04 07:11:24 +01:00
} else {
timeStamp = now ( ) ;
2021-09-26 01:14:03 +02:00
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 ) : [ ] ;
2021-10-23 15:38:52 +02:00
this . performance . body = this . env . perfadd ? ( this . performance . body || 0 ) + Math . trunc ( now ( ) - timeStamp ) : Math . trunc ( now ( ) - timeStamp ) ;
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:' ) ;
2021-09-21 03:59:49 +02:00
this . state = 'detect:hand' ;
2021-09-27 14:53:41 +02:00
const handConfig = this . config . hand . maxDetected === - 1 ? mergeDeep ( this . config , { hand : { maxDetected : this.config.face.enabled ? 2 * ( faceRes as FaceResult [ ] ) . length : 1 } } ) : this . config ; // autodetect number of hands
2020-11-04 07:11:24 +01:00
if ( this . config . async ) {
2021-09-26 01:14:03 +02:00
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 ) : [ ] ;
2021-05-31 16:40:07 +02:00
if ( this . performance . hand ) delete this . performance . hand ;
2020-11-04 07:11:24 +01:00
} else {
timeStamp = now ( ) ;
2021-09-26 01:14:03 +02:00
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 ) : [ ] ;
2021-10-23 15:38:52 +02:00
this . performance . hand = this . env . perfadd ? ( this . performance . hand || 0 ) + Math . trunc ( now ( ) - timeStamp ) : Math . trunc ( now ( ) - timeStamp ) ;
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
2021-10-27 15:45:38 +02:00
// run object detection
2021-03-21 12:49:55 +01:00
this . analyze ( 'Start Object:' ) ;
2021-09-21 03:59:49 +02:00
this . state = 'detect:object' ;
2021-03-17 16:32:37 +01:00
if ( this . config . async ) {
2021-09-13 19:28:35 +02:00
if ( this . config . object . modelPath ? . includes ( 'nanodet' ) ) objectRes = this . config . object . enabled ? nanodet . predict ( img . tensor , this . config ) : [ ] ;
else if ( this . config . object . modelPath ? . includes ( 'centernet' ) ) objectRes = this . config . object . enabled ? centernet . predict ( img . tensor , this . config ) : [ ] ;
2021-05-31 16:40:07 +02:00
if ( this . performance . object ) delete this . performance . object ;
2021-03-17 16:32:37 +01:00
} else {
timeStamp = now ( ) ;
2021-09-13 19:28:35 +02:00
if ( this . config . object . modelPath ? . includes ( 'nanodet' ) ) objectRes = this . config . object . enabled ? await nanodet . predict ( img . tensor , this . config ) : [ ] ;
else if ( this . config . object . modelPath ? . includes ( 'centernet' ) ) objectRes = this . config . object . enabled ? await centernet . predict ( img . tensor , this . config ) : [ ] ;
2021-10-23 15:38:52 +02:00
this . performance . object = this . env . perfadd ? ( this . performance . object || 0 ) + Math . trunc ( now ( ) - timeStamp ) : Math . trunc ( now ( ) - timeStamp ) ;
2021-03-17 16:32:37 +01:00
}
2021-03-21 12:49:55 +01:00
this . analyze ( 'End Object:' ) ;
2021-03-17 16:32:37 +01:00
2020-11-06 17:39:39 +01:00
// if async wait for results
2021-09-21 03:59:49 +02:00
this . state = 'detect:await' ;
2021-05-25 14:58:20 +02:00
if ( this . config . async ) [ faceRes , bodyRes , handRes , objectRes ] = await Promise . all ( [ faceRes , bodyRes , handRes , objectRes ] ) ;
2020-10-14 17:43:33 +02:00
2021-04-12 14:29:52 +02:00
// run gesture analysis last
2021-09-21 03:59:49 +02:00
this . state = 'detect:gesture' ;
2021-09-12 05:54:35 +02:00
let gestureRes : GestureResult [ ] = [ ] ;
2020-11-04 16:18:22 +01:00
if ( this . config . gesture . enabled ) {
timeStamp = now ( ) ;
2021-11-17 22:50:21 +01:00
gestureRes = [ . . . gesture . face ( faceRes as FaceResult [ ] ) , . . . gesture . body ( bodyRes as BodyResult [ ] ) , . . . gesture . hand ( handRes as HandResult [ ] ) , . . . gesture . iris ( faceRes as FaceResult [ ] ) ] ;
2021-10-23 15:38:52 +02:00
if ( ! this . config . async ) this . performance . gesture = this . env . perfadd ? ( this . performance . gesture || 0 ) + Math . trunc ( now ( ) - timeStamp ) : Math . trunc ( now ( ) - timeStamp ) ;
2021-05-31 16:40:07 +02:00
else if ( this . performance . gesture ) delete this . performance . gesture ;
2020-11-04 16:18:22 +01:00
}
2021-10-27 15:45:38 +02:00
this . performance . total = this . env . perfadd ? ( this . performance . total || 0 ) + Math . trunc ( now ( ) - timeStart ) : Math . trunc ( now ( ) - timeStart ) ;
2021-09-11 22:00:16 +02:00
const shape = this . process ? . tensor ? . shape || [ ] ;
2021-05-30 00:29:57 +02:00
this . result = {
2021-09-12 05:54:35 +02:00
face : faceRes as FaceResult [ ] ,
body : bodyRes as BodyResult [ ] ,
hand : handRes as HandResult [ ] ,
2021-03-17 19:35:11 +01:00
gesture : gestureRes ,
2021-09-12 05:54:35 +02:00
object : objectRes as ObjectResult [ ] ,
2021-05-31 16:40:07 +02:00
performance : this.performance ,
2021-09-11 22:00:16 +02:00
canvas : this.process.canvas ,
2021-05-22 18:41:29 +02:00
timestamp : Date.now ( ) ,
2021-11-14 17:22:52 +01:00
error : null ,
2021-09-12 05:54:35 +02:00
get persons() { return persons . join ( faceRes as FaceResult [ ] , bodyRes as BodyResult [ ] , handRes as HandResult [ ] , gestureRes , shape ) ; } ,
2021-03-17 19:35:11 +01:00
} ;
2021-05-25 14:58:20 +02:00
// finally dispose input tensor
2021-09-13 19:28:35 +02:00
tf . dispose ( img . tensor ) ;
2021-05-25 14:58:20 +02:00
2021-03-17 19:35:11 +01:00
// log('Result:', result);
2021-09-13 00:37:06 +02:00
this . emit ( 'detect' ) ;
2021-09-20 23:17:13 +02:00
this . state = 'idle' ;
2021-05-30 00:29:57 +02:00
resolve ( this . result ) ;
2020-10-19 17:03:48 +02:00
} ) ;
}
2020-10-12 01:22:43 +02:00
}
2021-09-17 20:07:44 +02:00
/** Class Human as default export */
2021-11-10 18:21:45 +01:00
/* eslint no-restricted-exports: ["off", { "restrictedNamedExports": ["default"] }] */
2020-10-19 17:03:48 +02:00
export { Human as default } ;