mirror of https://github.com/vladmandic/human
breaking change: convert to object class
parent
dfc67f0188
commit
36675c5152
74
README.md
74
README.md
|
@ -70,23 +70,46 @@ Simply download `dist/human.js`, include it in your `HTML` file & it's ready to
|
||||||
<script src="dist/human.js"><script>
|
<script src="dist/human.js"><script>
|
||||||
```
|
```
|
||||||
|
|
||||||
IIFE script auto-registers global namespace `human` within global `Window` object
|
IIFE script auto-registers global namespace `Human` within global `Window` object
|
||||||
|
Which you can use to create instance of `human` library:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const human = new Human();
|
||||||
|
```
|
||||||
|
|
||||||
This way you can also use `Human` library within embbedded `<script>` tag within your `html` page for all-in-one approach
|
This way you can also use `Human` library within embbedded `<script>` tag within your `html` page for all-in-one approach
|
||||||
|
|
||||||
### 2. [ESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) module
|
### 2. [ESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) module
|
||||||
|
|
||||||
*Recommended for usage within `Browser`*
|
*Recommended for usage within `Browser`*
|
||||||
|
|
||||||
#### 2.1 With Bundler
|
#### **2.1 Using Script Module**
|
||||||
|
You could use same syntax within your main `JS` file if it's imported with `<script type="module">`
|
||||||
|
|
||||||
If you're using bundler *(such as rollup, webpack, esbuild)* to package your client application, you can import ESM version of `Human` library which supports full tree shaking
|
```html
|
||||||
|
<script src="./index.js" type="module">
|
||||||
|
```
|
||||||
|
and then in your `index.js`
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Human from 'dist/human.esm.js'; // for direct import must use path to module, not package name
|
||||||
|
const human = new Human();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **2.2 With Bundler**
|
||||||
|
|
||||||
|
If you're using bundler *(such as rollup, webpack, parcel, browserify, esbuild)* to package your client application,
|
||||||
|
you can import ESM version of `Human` library which supports full tree shaking
|
||||||
|
|
||||||
Install with:
|
Install with:
|
||||||
```shell
|
```shell
|
||||||
npm install @vladmandic/human
|
npm install @vladmandic/human
|
||||||
```
|
```
|
||||||
```js
|
```js
|
||||||
import human from '@vladmandic/human'; // points to @vladmandic/human/dist/human.esm.js
|
import Human from '@vladmandic/human'; // points to @vladmandic/human/dist/human.esm.js
|
||||||
|
// you can also force-load specific version
|
||||||
|
// for example: `@vladmandic/human/dist/human.esm.js`
|
||||||
|
const human = new Human();
|
||||||
```
|
```
|
||||||
|
|
||||||
Or if you prefer to package your version of `tfjs`, you can use `nobundle` version
|
Or if you prefer to package your version of `tfjs`, you can use `nobundle` version
|
||||||
|
@ -97,20 +120,8 @@ Install with:
|
||||||
```
|
```
|
||||||
```js
|
```js
|
||||||
import tf from '@tensorflow/tfjs'
|
import tf from '@tensorflow/tfjs'
|
||||||
import human from '@vladmandic/human/dist/human.esm-nobundle.js'; // same functionality as default import, but without tfjs bundled
|
import Human from '@vladmandic/human/dist/human.esm-nobundle.js'; // same functionality as default import, but without tfjs bundled
|
||||||
```
|
const human = new Human();
|
||||||
|
|
||||||
#### 2.2 Using Script Module
|
|
||||||
You could use same syntax within your main `JS` file if it's imported with `<script type="module">`
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script src="./index.js" type="module">
|
|
||||||
```
|
|
||||||
and then in your `index.js`
|
|
||||||
|
|
||||||
```js
|
|
||||||
import * as tf from `https://cdnjs.cloudflare.com/ajax/libs/tensorflow/2.6.0/tf.es2017.min.js`; // load tfjs directly from CDN link
|
|
||||||
import human from 'dist/human.esm.js'; // for direct import must use path to module, not package name
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. [NPM](https://www.npmjs.com/) module
|
### 3. [NPM](https://www.npmjs.com/) module
|
||||||
|
@ -127,7 +138,8 @@ Install with:
|
||||||
And then use with:
|
And then use with:
|
||||||
```js
|
```js
|
||||||
const tf = require('@tensorflow/tfjs-node'); // can also use '@tensorflow/tfjs-node-gpu' if you have environment with CUDA extensions
|
const tf = require('@tensorflow/tfjs-node'); // can also use '@tensorflow/tfjs-node-gpu' if you have environment with CUDA extensions
|
||||||
const human = require('@vladmandic/human'); // points to @vladmandic/human/dist/human.cjs
|
const Human = require('@vladmandic/human').default; // points to @vladmandic/human/dist/human.cjs
|
||||||
|
const human = new Human();
|
||||||
```
|
```
|
||||||
|
|
||||||
Since NodeJS projects load `weights` from local filesystem instead of using `http` calls, you must modify default configuration to include correct paths with `file://` prefix
|
Since NodeJS projects load `weights` from local filesystem instead of using `http` calls, you must modify default configuration to include correct paths with `file://` prefix
|
||||||
|
@ -198,13 +210,20 @@ Additionally, `Human` library exposes several objects and methods:
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that when using `Human` library in `NodeJS`, you must load and parse the image *before* you pass it for detection and dispose it afterwards
|
Note that when using `Human` library in `NodeJS`, you must load and parse the image *before* you pass it for detection and dispose it afterwards
|
||||||
|
Input format is `Tensor4D[1, width, height, 3]` of type `float32`
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
```js
|
```js
|
||||||
const imageFile = '../assets/sample1.jpg';
|
const imageFile = '../assets/sample1.jpg';
|
||||||
const buffer = fs.readFileSync(imageFile);
|
const buffer = fs.readFileSync(imageFile);
|
||||||
const image = tf.node.decodeImage(buffer);
|
const decoded = tf.node.decodeImage(buffer);
|
||||||
const result = human.detect(image, config);
|
const casted = decoded.toFloat();
|
||||||
|
const image = casted.expandDims(0);
|
||||||
|
decoded.dispose();
|
||||||
|
casted.dispose();
|
||||||
|
logger.log('Processing:', image.shape);
|
||||||
|
const human = new Human.Human();
|
||||||
|
const result = await human.detect(image, config);
|
||||||
image.dispose();
|
image.dispose();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -414,15 +433,15 @@ Development dependencies are [eslint](https://github.com/eslint) used for code l
|
||||||
|
|
||||||
Performance will vary depending on your hardware, but also on number of resolution of input video/image, enabled modules as well as their parameters
|
Performance will vary depending on your hardware, but also on number of resolution of input video/image, enabled modules as well as their parameters
|
||||||
|
|
||||||
For example, on a desktop with a low-end nVidia GTX1050 it can perform multiple face detections at 60+ FPS, but drops to 10 FPS on a medium complex images if all modules are enabled
|
For example, on a desktop with a low-end nVidia GTX1050 it can perform multiple face detections at 60+ FPS, but drops to ~15 FPS on a medium complex images if all modules are enabled
|
||||||
|
|
||||||
Performance per module:
|
Performance per module:
|
||||||
|
|
||||||
- Enabled all: 10 FPS
|
- Enabled all: 15 FPS
|
||||||
- Image filters: 80 FPS (standalone)
|
- Image filters: 80 FPS (standalone)
|
||||||
- Face Detect: 80 FPS (standalone)
|
- Face Detect: 80 FPS (standalone)
|
||||||
- Face Geometry: 30 FPS (includes face detect)
|
- Face Geometry: 30 FPS (includes face detect)
|
||||||
- Face Iris: 25 FPS (includes face detect and face geometry)
|
- Face Iris: 30 FPS (includes face detect and face geometry)
|
||||||
- Age: 60 FPS (includes face detect)
|
- Age: 60 FPS (includes face detect)
|
||||||
- Gender: 60 FPS (includes face detect)
|
- Gender: 60 FPS (includes face detect)
|
||||||
- Emotion: 60 FPS (includes face detect)
|
- Emotion: 60 FPS (includes face detect)
|
||||||
|
@ -437,8 +456,11 @@ For performance details, see output of `result.performance` object during runtim
|
||||||
|
|
||||||
`Human` library can be used in any modern Browser or NodeJS environment, but there are several items to be aware of:
|
`Human` library can be used in any modern Browser or NodeJS environment, but there are several items to be aware of:
|
||||||
|
|
||||||
- **NodeJS**: Due to a missing feature in `tfjs-node`, only some models are available <https://github.com/tensorflow/tfjs/issues/4066>
|
- **NodeJS**: Due to a missing feature in `tfjs-node`, only some models are available
|
||||||
- **Browser**: `filters` module cannot be used when using web workers <https://github.com/phoboslab/WebGLImageFilter/issues/27>
|
For unsupported models, error is: `TypeError: forwardFunc is not a function`
|
||||||
|
<https://github.com/tensorflow/tfjs/issues/4066>
|
||||||
|
- **Browser**: Module `filters` cannot be used when using web workers
|
||||||
|
<https://github.com/phoboslab/WebGLImageFilter/issues/27>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import human from '../dist/human.esm.js';
|
import Human from '../dist/human.esm.js';
|
||||||
import draw from './draw.js';
|
import draw from './draw.js';
|
||||||
import Menu from './menu.js';
|
import Menu from './menu.js';
|
||||||
|
|
||||||
|
const human = new Human();
|
||||||
|
|
||||||
// ui options
|
// ui options
|
||||||
const ui = {
|
const ui = {
|
||||||
baseColor: 'rgba(173, 216, 230, 0.3)', // this is 'lightblue', just with alpha channel
|
baseColor: 'rgba(173, 216, 230, 0.3)', // this is 'lightblue', just with alpha channel
|
||||||
|
|
10
demo/node.js
10
demo/node.js
|
@ -2,7 +2,7 @@ const tf = require('@tensorflow/tfjs-node');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const process = require('process');
|
const process = require('process');
|
||||||
const console = require('console');
|
const console = require('console');
|
||||||
const human = require('..'); // this resolves to project root which is '@vladmandic/human'
|
const Human = require('..').default; // this resolves to project root which is '@vladmandic/human'
|
||||||
|
|
||||||
const logger = new console.Console({
|
const logger = new console.Console({
|
||||||
stdout: process.stdout,
|
stdout: process.stdout,
|
||||||
|
@ -26,6 +26,7 @@ const logger = new console.Console({
|
||||||
const config = {
|
const config = {
|
||||||
backend: 'tensorflow',
|
backend: 'tensorflow',
|
||||||
console: true,
|
console: true,
|
||||||
|
videoOptimized: false,
|
||||||
face: {
|
face: {
|
||||||
detector: { modelPath: 'file://models/blazeface/back/model.json' },
|
detector: { modelPath: 'file://models/blazeface/back/model.json' },
|
||||||
mesh: { modelPath: 'file://models/facemesh/model.json' },
|
mesh: { modelPath: 'file://models/facemesh/model.json' },
|
||||||
|
@ -47,8 +48,13 @@ async function detect(input, output) {
|
||||||
logger.info('TFJS Flags:', tf.env().features);
|
logger.info('TFJS Flags:', tf.env().features);
|
||||||
logger.log('Loading:', input);
|
logger.log('Loading:', input);
|
||||||
const buffer = fs.readFileSync(input);
|
const buffer = fs.readFileSync(input);
|
||||||
const image = tf.node.decodeImage(buffer);
|
const decoded = tf.node.decodeImage(buffer);
|
||||||
|
const casted = decoded.toFloat();
|
||||||
|
const image = casted.expandDims(0);
|
||||||
|
decoded.dispose();
|
||||||
|
casted.dispose();
|
||||||
logger.log('Processing:', image.shape);
|
logger.log('Processing:', image.shape);
|
||||||
|
const human = new Human();
|
||||||
const result = await human.detect(image, config);
|
const result = await human.detect(image, config);
|
||||||
image.dispose();
|
image.dispose();
|
||||||
logger.log(result);
|
logger.log(result);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import human from '../dist/human.esm.js';
|
import Human from '../dist/human.esm.js';
|
||||||
|
|
||||||
let config;
|
let config;
|
||||||
let busy = false;
|
let busy = false;
|
||||||
|
const human = new Human();
|
||||||
|
|
||||||
const log = (...msg) => {
|
const log = (...msg) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught --no-deprecation src/node.js",
|
"start": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught --no-deprecation src/node.js",
|
||||||
"lint": "eslint src/*.js demo/*.js",
|
"lint": "eslint src/*.js demo/*.js",
|
||||||
"build-iife": "esbuild --bundle --platform=browser --sourcemap --target=esnext --format=iife --minify --external:fs --global-name=human --metafile=dist/human.json --outfile=dist/human.js src/human.js",
|
"build-iife": "esbuild --bundle --platform=browser --sourcemap --target=esnext --format=iife --minify --external:fs --global-name=Human --metafile=dist/human.json --outfile=dist/human.js src/human.js",
|
||||||
"build-esm-bundle": "esbuild --bundle --platform=browser --sourcemap --target=esnext --format=esm --minify --external:fs --metafile=dist/human.esm.json --outfile=dist/human.esm.js src/human.js",
|
"build-esm-bundle": "esbuild --bundle --platform=browser --sourcemap --target=esnext --format=esm --minify --external:fs --metafile=dist/human.esm.json --outfile=dist/human.esm.js src/human.js",
|
||||||
"build-esm-nobundle": "esbuild --bundle --platform=browser --sourcemap --target=esnext --format=esm --minify --external:@tensorflow --external:fs --metafile=dist/human.esm-nobundle.json --outfile=dist/human.esm-nobundle.js src/human.js",
|
"build-esm-nobundle": "esbuild --bundle --platform=browser --sourcemap --target=esnext --format=esm --minify --external:@tensorflow --external:fs --metafile=dist/human.esm-nobundle.json --outfile=dist/human.esm-nobundle.js src/human.js",
|
||||||
"build-node": "esbuild --bundle --platform=node --sourcemap --target=esnext --format=cjs --external:@tensorflow --metafile=dist/human.cjs.json --outfile=dist/human.cjs src/human.js",
|
"build-node": "esbuild --bundle --platform=node --sourcemap --target=esnext --format=cjs --external:@tensorflow --metafile=dist/human.cjs.json --outfile=dist/human.cjs src/human.js",
|
||||||
|
|
276
src/human.js
276
src/human.js
|
@ -8,22 +8,7 @@ const fxImage = require('./imagefx.js');
|
||||||
const defaults = require('../config.js').default;
|
const defaults = require('../config.js').default;
|
||||||
const app = require('../package.json');
|
const app = require('../package.json');
|
||||||
|
|
||||||
let config;
|
// static config override for non-video detection
|
||||||
let fx;
|
|
||||||
let state = 'idle';
|
|
||||||
let offscreenCanvas;
|
|
||||||
|
|
||||||
// object that contains all initialized models
|
|
||||||
const models = {
|
|
||||||
facemesh: null,
|
|
||||||
posenet: null,
|
|
||||||
handpose: null,
|
|
||||||
iris: null,
|
|
||||||
age: null,
|
|
||||||
gender: null,
|
|
||||||
emotion: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const override = {
|
const override = {
|
||||||
face: { detector: { skipFrames: 0 }, age: { skipFrames: 0 }, emotion: { skipFrames: 0 } },
|
face: { detector: { skipFrames: 0 }, age: { skipFrames: 0 }, emotion: { skipFrames: 0 } },
|
||||||
hand: { skipFrames: 0 },
|
hand: { skipFrames: 0 },
|
||||||
|
@ -35,24 +20,6 @@ const now = () => {
|
||||||
return parseInt(Number(process.hrtime.bigint()) / 1000 / 1000);
|
return parseInt(Number(process.hrtime.bigint()) / 1000 / 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
// helper function: wrapper around console output
|
|
||||||
const log = (...msg) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
if (msg && config.console) console.log(...msg);
|
|
||||||
};
|
|
||||||
|
|
||||||
// helper function: measure tensor leak
|
|
||||||
let numTensors = 0;
|
|
||||||
const analyzeMemoryLeaks = false;
|
|
||||||
const analyze = (...msg) => {
|
|
||||||
if (!analyzeMemoryLeaks) return;
|
|
||||||
const current = tf.engine().state.numTensors;
|
|
||||||
const previous = numTensors;
|
|
||||||
numTensors = current;
|
|
||||||
const leaked = current - previous;
|
|
||||||
if (leaked !== 0) log(...msg, leaked);
|
|
||||||
};
|
|
||||||
|
|
||||||
// helper function: perform deep merge of multiple objects so it allows full inheriance with overrides
|
// helper function: perform deep merge of multiple objects so it allows full inheriance with overrides
|
||||||
function mergeDeep(...objects) {
|
function mergeDeep(...objects) {
|
||||||
const isObject = (obj) => obj && typeof obj === 'object';
|
const isObject = (obj) => obj && typeof obj === 'object';
|
||||||
|
@ -85,68 +52,104 @@ function sanity(input) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load(userConfig) {
|
class Human {
|
||||||
if (userConfig) config = mergeDeep(defaults, userConfig);
|
constructor() {
|
||||||
if (config.face.enabled && !models.facemesh) {
|
this.tf = tf;
|
||||||
log('Load model: Face');
|
this.version = app.version;
|
||||||
models.facemesh = await facemesh.load(config.face);
|
this.defaults = defaults;
|
||||||
|
this.config = defaults;
|
||||||
|
this.fx = (tf.ENV.flags.IS_BROWSER && (typeof document !== 'undefined')) ? new fxImage.Canvas() : null;
|
||||||
|
this.state = 'idle';
|
||||||
|
this.numTensors = 0;
|
||||||
|
this.analyzeMemoryLeaks = false;
|
||||||
|
// object that contains all initialized models
|
||||||
|
this.models = {
|
||||||
|
facemesh: null,
|
||||||
|
posenet: null,
|
||||||
|
handpose: null,
|
||||||
|
iris: null,
|
||||||
|
age: null,
|
||||||
|
gender: null,
|
||||||
|
emotion: null,
|
||||||
|
};
|
||||||
|
// export raw access to underlying models
|
||||||
|
this.facemesh = facemesh;
|
||||||
|
this.ssrnet = ssrnet;
|
||||||
|
this.emotion = emotion;
|
||||||
|
this.posenet = posenet;
|
||||||
|
this.handpose = handpose;
|
||||||
}
|
}
|
||||||
if (config.body.enabled && !models.posenet) {
|
|
||||||
log('Load model: Body');
|
|
||||||
models.posenet = await posenet.load(config.body);
|
|
||||||
}
|
|
||||||
if (config.hand.enabled && !models.handpose) {
|
|
||||||
log('Load model: Hand');
|
|
||||||
models.handpose = await handpose.load(config.hand);
|
|
||||||
}
|
|
||||||
if (config.face.enabled && config.face.age.enabled && !models.age) {
|
|
||||||
log('Load model: Age');
|
|
||||||
models.age = await ssrnet.loadAge(config);
|
|
||||||
}
|
|
||||||
if (config.face.enabled && config.face.gender.enabled && !models.gender) {
|
|
||||||
log('Load model: Gender');
|
|
||||||
models.gender = await ssrnet.loadGender(config);
|
|
||||||
}
|
|
||||||
if (config.face.enabled && config.face.emotion.enabled && !models.emotion) {
|
|
||||||
log('Load model: Emotion');
|
|
||||||
models.emotion = await emotion.load(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function tfImage(input) {
|
// helper function: wrapper around console output
|
||||||
|
log(...msg) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
if (msg && this.config.console) console.log(...msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function: measure tensor leak
|
||||||
|
analyze(...msg) {
|
||||||
|
if (!this.analyzeMemoryLeaks) return;
|
||||||
|
const current = tf.engine().state.numTensors;
|
||||||
|
const previous = this.numTensors;
|
||||||
|
this.numTensors = current;
|
||||||
|
const leaked = current - previous;
|
||||||
|
if (leaked !== 0) this.log(...msg, leaked);
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(userConfig) {
|
||||||
|
if (userConfig) this.config = mergeDeep(defaults, userConfig);
|
||||||
|
if (this.config.face.enabled && !this.models.facemesh) {
|
||||||
|
this.log('Load model: Face');
|
||||||
|
this.models.facemesh = await facemesh.load(this.config.face);
|
||||||
|
}
|
||||||
|
if (this.config.body.enabled && !this.models.posenet) {
|
||||||
|
this.log('Load model: Body');
|
||||||
|
this.models.posenet = await posenet.load(this.config.body);
|
||||||
|
}
|
||||||
|
if (this.config.hand.enabled && !this.models.handpose) {
|
||||||
|
this.log('Load model: Hand');
|
||||||
|
this.models.handpose = await handpose.load(this.config.hand);
|
||||||
|
}
|
||||||
|
if (this.config.face.enabled && this.config.face.age.enabled && !this.models.age) {
|
||||||
|
this.log('Load model: Age');
|
||||||
|
this.models.age = await ssrnet.loadAge(this.config);
|
||||||
|
}
|
||||||
|
if (this.config.face.enabled && this.config.face.gender.enabled && !this.models.gender) {
|
||||||
|
this.log('Load model: Gender');
|
||||||
|
this.models.gender = await ssrnet.loadGender(this.config);
|
||||||
|
}
|
||||||
|
if (this.config.face.enabled && this.config.face.emotion.enabled && !this.models.emotion) {
|
||||||
|
this.log('Load model: Emotion');
|
||||||
|
this.models.emotion = await emotion.load(this.config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tfImage(input) {
|
||||||
// let imageData;
|
// let imageData;
|
||||||
let filtered;
|
let filtered;
|
||||||
if (tf.ENV.flags.IS_BROWSER && config.filter.enabled && !(input instanceof tf.Tensor)) {
|
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
|
||||||
const width = input.naturalWidth || input.videoWidth || input.width || (input.shape && (input.shape[1] > 0));
|
const width = input.naturalWidth || input.videoWidth || input.width || (input.shape && (input.shape[1] > 0));
|
||||||
const height = input.naturalHeight || input.videoHeight || input.height || (input.shape && (input.shape[2] > 0));
|
const height = input.naturalHeight || input.videoHeight || input.height || (input.shape && (input.shape[2] > 0));
|
||||||
if (!offscreenCanvas) offscreenCanvas = new OffscreenCanvas(width, height);
|
const offscreenCanvas = new OffscreenCanvas(width, height);
|
||||||
/*
|
|
||||||
if (!offscreenCanvas) {
|
|
||||||
offscreenCanvas = document.createElement('canvas');
|
|
||||||
offscreenCanvas.width = width;
|
|
||||||
offscreenCanvas.height = height;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const ctx = offscreenCanvas.getContext('2d');
|
const ctx = offscreenCanvas.getContext('2d');
|
||||||
if (input instanceof ImageData) ctx.putImageData(input, 0, 0);
|
if (input instanceof ImageData) ctx.putImageData(input, 0, 0);
|
||||||
else ctx.drawImage(input, 0, 0, width, height, 0, 0, offscreenCanvas.width, offscreenCanvas.height);
|
else ctx.drawImage(input, 0, 0, width, height, 0, 0, offscreenCanvas.width, offscreenCanvas.height);
|
||||||
if (!fx) fx = new fxImage.Canvas();
|
this.fx.reset();
|
||||||
else fx.reset();
|
this.fx.addFilter('brightness', this.config.filter.brightness); // must have at least one filter enabled
|
||||||
fx.addFilter('brightness', config.filter.brightness); // must have at least one filter enabled
|
if (this.config.filter.contrast !== 0) this.fx.addFilter('contrast', this.config.filter.contrast);
|
||||||
if (config.filter.contrast !== 0) fx.addFilter('contrast', config.filter.contrast);
|
if (this.config.filter.sharpness !== 0) this.fx.addFilter('sharpen', this.config.filter.sharpness);
|
||||||
if (config.filter.sharpness !== 0) fx.addFilter('sharpen', config.filter.sharpness);
|
if (this.config.filter.blur !== 0) this.fx.addFilter('blur', this.config.filter.blur);
|
||||||
if (config.filter.blur !== 0) fx.addFilter('blur', config.filter.blur);
|
if (this.config.filter.saturation !== 0) this.fx.addFilter('saturation', this.config.filter.saturation);
|
||||||
if (config.filter.saturation !== 0) fx.addFilter('saturation', config.filter.saturation);
|
if (this.config.filter.hue !== 0) this.fx.addFilter('hue', this.config.filter.hue);
|
||||||
if (config.filter.hue !== 0) fx.addFilter('hue', config.filter.hue);
|
if (this.config.filter.negative) this.fx.addFilter('negative');
|
||||||
if (config.filter.negative) fx.addFilter('negative');
|
if (this.config.filter.sepia) this.fx.addFilter('sepia');
|
||||||
if (config.filter.sepia) fx.addFilter('sepia');
|
if (this.config.filter.vintage) this.fx.addFilter('brownie');
|
||||||
if (config.filter.vintage) fx.addFilter('brownie');
|
if (this.config.filter.sepia) this.fx.addFilter('sepia');
|
||||||
if (config.filter.sepia) fx.addFilter('sepia');
|
if (this.config.filter.kodachrome) this.fx.addFilter('kodachrome');
|
||||||
if (config.filter.kodachrome) fx.addFilter('kodachrome');
|
if (this.config.filter.technicolor) this.fx.addFilter('technicolor');
|
||||||
if (config.filter.technicolor) fx.addFilter('technicolor');
|
if (this.config.filter.polaroid) this.fx.addFilter('polaroid');
|
||||||
if (config.filter.polaroid) fx.addFilter('polaroid');
|
if (this.config.filter.pixelate !== 0) this.fx.addFilter('pixelate', this.config.filter.pixelate);
|
||||||
if (config.filter.pixelate !== 0) fx.addFilter('pixelate', config.filter.pixelate);
|
filtered = this.fx.apply(offscreenCanvas);
|
||||||
filtered = fx.apply(offscreenCanvas);
|
|
||||||
}
|
}
|
||||||
let tensor;
|
let tensor;
|
||||||
if (input instanceof tf.Tensor) {
|
if (input instanceof tf.Tensor) {
|
||||||
|
@ -158,25 +161,25 @@ function tfImage(input) {
|
||||||
pixels.dispose();
|
pixels.dispose();
|
||||||
casted.dispose();
|
casted.dispose();
|
||||||
}
|
}
|
||||||
return { tensor, canvas: config.filter.return ? filtered : null };
|
return { tensor, canvas: this.config.filter.return ? filtered : null };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detect(input, userConfig = {}) {
|
async detect(input, userConfig = {}) {
|
||||||
state = 'config';
|
this.state = 'config';
|
||||||
const perf = {};
|
const perf = {};
|
||||||
let timeStamp;
|
let timeStamp;
|
||||||
|
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
config = mergeDeep(defaults, userConfig);
|
this.config = mergeDeep(defaults, userConfig);
|
||||||
if (!config.videoOptimized) config = mergeDeep(config, override);
|
if (!this.config.videoOptimized) this.config = mergeDeep(this.config, override);
|
||||||
perf.config = Math.trunc(now() - timeStamp);
|
perf.config = Math.trunc(now() - timeStamp);
|
||||||
|
|
||||||
// sanity checks
|
// sanity checks
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
state = 'check';
|
this.state = 'check';
|
||||||
const error = sanity(input);
|
const error = sanity(input);
|
||||||
if (error) {
|
if (error) {
|
||||||
log(error, input);
|
this.log(error, input);
|
||||||
return { error };
|
return { error };
|
||||||
}
|
}
|
||||||
perf.sanity = Math.trunc(now() - timeStamp);
|
perf.sanity = Math.trunc(now() - timeStamp);
|
||||||
|
@ -187,76 +190,76 @@ async function detect(input, userConfig = {}) {
|
||||||
|
|
||||||
// configure backend
|
// configure backend
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
if (tf.getBackend() !== config.backend) {
|
if (tf.getBackend() !== this.config.backend) {
|
||||||
state = 'backend';
|
this.state = 'backend';
|
||||||
log('Human library setting backend:', config.backend);
|
this.log('Human library setting backend:', this.config.backend);
|
||||||
await tf.setBackend(config.backend);
|
await tf.setBackend(this.config.backend);
|
||||||
await tf.ready();
|
await tf.ready();
|
||||||
}
|
}
|
||||||
perf.backend = Math.trunc(now() - timeStamp);
|
perf.backend = Math.trunc(now() - timeStamp);
|
||||||
|
|
||||||
// check number of loaded models
|
// check number of loaded models
|
||||||
const loadedModels = Object.values(models).filter((a) => a).length;
|
const loadedModels = Object.values(this.models).filter((a) => a).length;
|
||||||
if (loadedModels === 0) {
|
if (loadedModels === 0) {
|
||||||
log('Human library starting');
|
this.log('Human library starting');
|
||||||
log('Configuration:', config);
|
this.log('Configuration:', this.config);
|
||||||
log('Flags:', tf.ENV.flags);
|
this.log('Flags:', tf.ENV.flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// load models if enabled
|
// load models if enabled
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
state = 'load';
|
this.state = 'load';
|
||||||
await load();
|
await this.load();
|
||||||
perf.load = Math.trunc(now() - timeStamp);
|
perf.load = Math.trunc(now() - timeStamp);
|
||||||
|
|
||||||
if (config.scoped) tf.engine().startScope();
|
if (this.config.scoped) tf.engine().startScope();
|
||||||
|
|
||||||
analyze('Start Detect:');
|
this.analyze('Start Detect:');
|
||||||
|
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
const image = tfImage(input);
|
const image = this.tfImage(input);
|
||||||
perf.image = Math.trunc(now() - timeStamp);
|
perf.image = Math.trunc(now() - timeStamp);
|
||||||
const imageTensor = image.tensor;
|
const imageTensor = image.tensor;
|
||||||
|
|
||||||
// run posenet
|
// run posenet
|
||||||
state = 'run:body';
|
this.state = 'run:body';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
analyze('Start PoseNet');
|
this.analyze('Start PoseNet');
|
||||||
const poseRes = config.body.enabled ? await models.posenet.estimatePoses(imageTensor, config.body) : [];
|
const poseRes = this.config.body.enabled ? await this.models.posenet.estimatePoses(imageTensor, this.config.body) : [];
|
||||||
analyze('End PoseNet:');
|
this.analyze('End PoseNet:');
|
||||||
perf.body = Math.trunc(now() - timeStamp);
|
perf.body = Math.trunc(now() - timeStamp);
|
||||||
|
|
||||||
// run handpose
|
// run handpose
|
||||||
state = 'run:hand';
|
this.state = 'run:hand';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
analyze('Start HandPose:');
|
this.analyze('Start HandPose:');
|
||||||
const handRes = config.hand.enabled ? await models.handpose.estimateHands(imageTensor, config.hand) : [];
|
const handRes = this.config.hand.enabled ? await this.models.handpose.estimateHands(imageTensor, this.config.hand) : [];
|
||||||
analyze('End HandPose:');
|
this.analyze('End HandPose:');
|
||||||
perf.hand = Math.trunc(now() - timeStamp);
|
perf.hand = Math.trunc(now() - timeStamp);
|
||||||
|
|
||||||
// run facemesh, includes blazeface and iris
|
// run facemesh, includes blazeface and iris
|
||||||
const faceRes = [];
|
const faceRes = [];
|
||||||
if (config.face.enabled) {
|
if (this.config.face.enabled) {
|
||||||
state = 'run:face';
|
this.state = 'run:face';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
analyze('Start FaceMesh:');
|
this.analyze('Start FaceMesh:');
|
||||||
const faces = await models.facemesh.estimateFaces(imageTensor, config.face);
|
const faces = await this.models.facemesh.estimateFaces(imageTensor, this.config.face);
|
||||||
perf.face = Math.trunc(now() - timeStamp);
|
perf.face = Math.trunc(now() - timeStamp);
|
||||||
for (const face of faces) {
|
for (const face of faces) {
|
||||||
// is something went wrong, skip the face
|
// is something went wrong, skip the face
|
||||||
if (!face.image || face.image.isDisposedInternal) {
|
if (!face.image || face.image.isDisposedInternal) {
|
||||||
log('face object is disposed:', face.image);
|
this.log('face object is disposed:', face.image);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// run ssr-net age & gender, inherits face from blazeface
|
// run ssr-net age & gender, inherits face from blazeface
|
||||||
state = 'run:agegender';
|
this.state = 'run:agegender';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
const ssrData = (config.face.age.enabled || config.face.gender.enabled) ? await ssrnet.predict(face.image, config) : {};
|
const ssrData = (this.config.face.age.enabled || this.config.face.gender.enabled) ? await ssrnet.predict(face.image, this.config) : {};
|
||||||
perf.agegender = Math.trunc(now() - timeStamp);
|
perf.agegender = Math.trunc(now() - timeStamp);
|
||||||
// run emotion, inherits face from blazeface
|
// run emotion, inherits face from blazeface
|
||||||
state = 'run:emotion';
|
this.state = 'run:emotion';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
const emotionData = config.face.emotion.enabled ? await emotion.predict(face.image, config) : {};
|
const emotionData = this.config.face.emotion.enabled ? await emotion.predict(face.image, this.config) : {};
|
||||||
perf.emotion = Math.trunc(now() - timeStamp);
|
perf.emotion = Math.trunc(now() - timeStamp);
|
||||||
|
|
||||||
// dont need face anymore
|
// dont need face anymore
|
||||||
|
@ -277,31 +280,20 @@ async function detect(input, userConfig = {}) {
|
||||||
emotion: emotionData,
|
emotion: emotionData,
|
||||||
iris: (iris !== 0) ? Math.trunc(100 * 11.7 /* human iris size in mm */ / iris) / 100 : 0,
|
iris: (iris !== 0) ? Math.trunc(100 * 11.7 /* human iris size in mm */ / iris) / 100 : 0,
|
||||||
});
|
});
|
||||||
analyze('End FaceMesh:');
|
this.analyze('End FaceMesh:');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imageTensor.dispose();
|
imageTensor.dispose();
|
||||||
state = 'idle';
|
this.state = 'idle';
|
||||||
|
|
||||||
if (config.scoped) tf.engine().endScope();
|
if (this.config.scoped) tf.engine().endScope();
|
||||||
analyze('End Scope:');
|
this.analyze('End Scope:');
|
||||||
|
|
||||||
perf.total = Math.trunc(now() - timeStart);
|
perf.total = Math.trunc(now() - timeStart);
|
||||||
resolve({ face: faceRes, body: poseRes, hand: handRes, performance: perf, canvas: image.canvas });
|
resolve({ face: faceRes, body: poseRes, hand: handRes, performance: perf, canvas: image.canvas });
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.detect = detect;
|
export { Human as default };
|
||||||
exports.defaults = defaults;
|
|
||||||
exports.config = config;
|
|
||||||
exports.models = models;
|
|
||||||
exports.facemesh = facemesh;
|
|
||||||
exports.ssrnet = ssrnet;
|
|
||||||
exports.posenet = posenet;
|
|
||||||
exports.handpose = handpose;
|
|
||||||
exports.tf = tf;
|
|
||||||
exports.version = app.version;
|
|
||||||
exports.state = state;
|
|
||||||
|
|
||||||
// Error: Failed to compile fragment shader
|
|
||||||
|
|
Loading…
Reference in New Issue