mirror of https://github.com/vladmandic/human
enhanced age, gender, emotion detection
parent
b25e7fd459
commit
26f8f76a7b
5
TODO.md
5
TODO.md
|
@ -1,7 +1,6 @@
|
||||||
# To-Do list for Human library
|
# To-Do list for Human library
|
||||||
|
|
||||||
- Fix BlazeFace NodeJS missing ops
|
- Strong typing
|
||||||
- Prune pre-packaged models
|
|
||||||
- Build Face embedding database
|
- Build Face embedding database
|
||||||
- Dynamic sample processing
|
- Dynamic sample processing
|
||||||
- Optimize for v1 release
|
- Explore EfficientPose: <https://github.com/daniegr/EfficientPose> <https://github.com/PINTO0309/PINTO_model_zoo/tree/main/084_EfficientPose>
|
||||||
|
|
|
@ -80,10 +80,10 @@ export default {
|
||||||
// in short time (10 * 1/25 = 0.25 sec)
|
// in short time (10 * 1/25 = 0.25 sec)
|
||||||
skipInitial: false, // if previous detection resulted in no faces detected,
|
skipInitial: false, // if previous detection resulted in no faces detected,
|
||||||
// should skipFrames be reset immediately
|
// should skipFrames be reset immediately
|
||||||
minConfidence: 0.1, // threshold for discarding a prediction
|
minConfidence: 0.2, // threshold for discarding a prediction
|
||||||
iouThreshold: 0.1, // threshold for deciding whether boxes overlap too much in
|
iouThreshold: 0.1, // threshold for deciding whether boxes overlap too much in
|
||||||
// non-maximum suppression (0.1 means drop if overlap 10%)
|
// non-maximum suppression (0.1 means drop if overlap 10%)
|
||||||
scoreThreshold: 0.1, // threshold for deciding when to remove boxes based on score
|
scoreThreshold: 0.2, // threshold for deciding when to remove boxes based on score
|
||||||
// in non-maximum suppression,
|
// in non-maximum suppression,
|
||||||
// this is applied on detection objects only and before minConfidence
|
// this is applied on detection objects only and before minConfidence
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Human from '../dist/human.esm.js'; // equivalent of @vladmandic/human
|
// import Human from '../dist/human.esm.js'; // equivalent of @vladmandic/human
|
||||||
|
import Human from '../src/human';
|
||||||
import Menu from './menu.js';
|
import Menu from './menu.js';
|
||||||
import GLBench from './gl-bench.js';
|
import GLBench from './gl-bench.js';
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ const userConfig = { backend: 'webgl' }; // add any user configuration overrides
|
||||||
const userConfig = {
|
const userConfig = {
|
||||||
backend: 'wasm',
|
backend: 'wasm',
|
||||||
async: false,
|
async: false,
|
||||||
warmup: 'face',
|
warmup: 'none',
|
||||||
videoOptimized: false,
|
videoOptimized: false,
|
||||||
face: { enabled: true, mesh: { enabled: false }, iris: { enabled: false }, age: { enabled: false }, gender: { enabled: false }, emotion: { enabled: false }, embedding: { enabled: false } },
|
face: { enabled: true, mesh: { enabled: false }, iris: { enabled: false }, age: { enabled: false }, gender: { enabled: false }, emotion: { enabled: false }, embedding: { enabled: false } },
|
||||||
hand: { enabled: false },
|
hand: { enabled: false },
|
||||||
|
@ -360,7 +361,6 @@ async function processImage(input) {
|
||||||
|
|
||||||
// just initialize everything and call main function
|
// just initialize everything and call main function
|
||||||
async function detectVideo() {
|
async function detectVideo() {
|
||||||
userConfig.videoOptimized = true;
|
|
||||||
document.getElementById('samples-container').style.display = 'none';
|
document.getElementById('samples-container').style.display = 'none';
|
||||||
document.getElementById('canvas').style.display = 'block';
|
document.getElementById('canvas').style.display = 'block';
|
||||||
const video = document.getElementById('video');
|
const video = document.getElementById('video');
|
||||||
|
@ -389,8 +389,8 @@ async function detectVideo() {
|
||||||
|
|
||||||
// just initialize everything and call main function
|
// just initialize everything and call main function
|
||||||
async function detectSampleImages() {
|
async function detectSampleImages() {
|
||||||
|
userConfig.videoOptimized = false; // force disable video optimizations
|
||||||
document.getElementById('play').style.display = 'none';
|
document.getElementById('play').style.display = 'none';
|
||||||
userConfig.videoOptimized = false;
|
|
||||||
document.getElementById('canvas').style.display = 'none';
|
document.getElementById('canvas').style.display = 'none';
|
||||||
document.getElementById('samples-container').style.display = 'block';
|
document.getElementById('samples-container').style.display = 'block';
|
||||||
log('Running detection of sample images');
|
log('Running detection of sample images');
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -75,26 +75,26 @@ export class BlazeFaceModel {
|
||||||
async getBoundingBoxes(inputImage) {
|
async getBoundingBoxes(inputImage) {
|
||||||
// sanity check on input
|
// sanity check on input
|
||||||
if ((!inputImage) || (inputImage.isDisposedInternal) || (inputImage.shape.length !== 4) || (inputImage.shape[1] < 1) || (inputImage.shape[2] < 1)) return null;
|
if ((!inputImage) || (inputImage.isDisposedInternal) || (inputImage.shape.length !== 4) || (inputImage.shape[1] < 1) || (inputImage.shape[2] < 1)) return null;
|
||||||
const [detectedOutputs, boxes, scores] = tf.tidy(() => {
|
const [batch, boxes, scores] = tf.tidy(() => {
|
||||||
const resizedImage = inputImage.resizeBilinear([this.width, this.height]);
|
const resizedImage = inputImage.resizeBilinear([this.width, this.height]);
|
||||||
// const normalizedImage = tf.mul(tf.sub(resizedImage.div(255), 0.5), 2);
|
// const normalizedImage = tf.mul(tf.sub(resizedImage.div(255), 0.5), 2);
|
||||||
const normalizedImage = resizedImage.div(127.5).sub(0.5);
|
const normalizedImage = resizedImage.div(127.5).sub(0.5);
|
||||||
const batchedPrediction = this.blazeFaceModel.predict(normalizedImage);
|
const batchedPrediction = this.blazeFaceModel.predict(normalizedImage);
|
||||||
let prediction;
|
let batchOut;
|
||||||
// are we using tfhub or pinto converted model?
|
// are we using tfhub or pinto converted model?
|
||||||
if (Array.isArray(batchedPrediction)) {
|
if (Array.isArray(batchedPrediction)) {
|
||||||
const sorted = batchedPrediction.sort((a, b) => a.size - b.size);
|
const sorted = batchedPrediction.sort((a, b) => a.size - b.size);
|
||||||
const concat384 = tf.concat([sorted[0], sorted[2]], 2); // dim: 384, 1 + 16
|
const concat384 = tf.concat([sorted[0], sorted[2]], 2); // dim: 384, 1 + 16
|
||||||
const concat512 = tf.concat([sorted[1], sorted[3]], 2); // dim: 512, 1 + 16
|
const concat512 = tf.concat([sorted[1], sorted[3]], 2); // dim: 512, 1 + 16
|
||||||
const concat = tf.concat([concat512, concat384], 1);
|
const concat = tf.concat([concat512, concat384], 1);
|
||||||
prediction = concat.squeeze(0);
|
batchOut = concat.squeeze(0);
|
||||||
} else {
|
} else {
|
||||||
prediction = batchedPrediction.squeeze(); // when using tfhub model
|
batchOut = batchedPrediction.squeeze(); // when using tfhub model
|
||||||
}
|
}
|
||||||
const decodedBounds = decodeBounds(prediction, this.anchors, this.inputSize);
|
const boxesOut = decodeBounds(batchOut, this.anchors, this.inputSize);
|
||||||
const logits = tf.slice(prediction, [0, 0], [-1, 1]);
|
const logits = tf.slice(batchOut, [0, 0], [-1, 1]);
|
||||||
const scoresOut = tf.sigmoid(logits).squeeze();
|
const scoresOut = tf.sigmoid(logits).squeeze();
|
||||||
return [prediction, decodedBounds, scoresOut];
|
return [batchOut, boxesOut, scoresOut];
|
||||||
});
|
});
|
||||||
const boxIndicesTensor = await tf.image.nonMaxSuppressionAsync(boxes, scores, this.config.face.detector.maxFaces, this.config.face.detector.iouThreshold, this.config.face.detector.scoreThreshold);
|
const boxIndicesTensor = await tf.image.nonMaxSuppressionAsync(boxes, scores, this.config.face.detector.maxFaces, this.config.face.detector.iouThreshold, this.config.face.detector.scoreThreshold);
|
||||||
const boxIndices = boxIndicesTensor.arraySync();
|
const boxIndices = boxIndicesTensor.arraySync();
|
||||||
|
@ -114,14 +114,13 @@ export class BlazeFaceModel {
|
||||||
if (confidence > this.config.face.detector.minConfidence) {
|
if (confidence > this.config.face.detector.minConfidence) {
|
||||||
const box = createBox(boundingBoxes[i]);
|
const box = createBox(boundingBoxes[i]);
|
||||||
const anchor = this.anchorsData[boxIndex];
|
const anchor = this.anchorsData[boxIndex];
|
||||||
const landmarks = tf.tidy(() => tf.slice(detectedOutputs, [boxIndex, NUM_LANDMARKS - 1], [1, -1]).squeeze().reshape([NUM_LANDMARKS, -1]));
|
const landmarks = tf.tidy(() => tf.slice(batch, [boxIndex, NUM_LANDMARKS - 1], [1, -1]).squeeze().reshape([NUM_LANDMARKS, -1]));
|
||||||
annotatedBoxes.push({ box, landmarks, anchor, confidence });
|
annotatedBoxes.push({ box, landmarks, anchor, confidence });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
detectedOutputs.dispose();
|
batch.dispose();
|
||||||
boxes.dispose();
|
boxes.dispose();
|
||||||
scores.dispose();
|
scores.dispose();
|
||||||
detectedOutputs.dispose();
|
|
||||||
return {
|
return {
|
||||||
boxes: annotatedBoxes,
|
boxes: annotatedBoxes,
|
||||||
scaleFactor: [inputImage.shape[2] / this.width, inputImage.shape[1] / this.height],
|
scaleFactor: [inputImage.shape[2] / this.width, inputImage.shape[1] / this.height],
|
||||||
|
|
|
@ -9,7 +9,6 @@ let skipped = Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
// tuning values
|
// tuning values
|
||||||
const rgb = [0.2989, 0.5870, 0.1140]; // factors for red/green/blue colors when converting to grayscale
|
const rgb = [0.2989, 0.5870, 0.1140]; // factors for red/green/blue colors when converting to grayscale
|
||||||
const scale = 1; // score multiplication factor
|
|
||||||
|
|
||||||
export async function load(config) {
|
export async function load(config) {
|
||||||
if (!model) {
|
if (!model) {
|
||||||
|
@ -58,7 +57,7 @@ export async function predict(image, config) {
|
||||||
if (config.face.emotion.enabled) {
|
if (config.face.emotion.enabled) {
|
||||||
let data;
|
let data;
|
||||||
if (!config.profile) {
|
if (!config.profile) {
|
||||||
const emotionT = await model.predict(normalize);
|
const emotionT = await model.predict(normalize); // result is already in range 0..1, no need for additional activation
|
||||||
data = emotionT.dataSync();
|
data = emotionT.dataSync();
|
||||||
tf.dispose(emotionT);
|
tf.dispose(emotionT);
|
||||||
} else {
|
} else {
|
||||||
|
@ -68,7 +67,7 @@ export async function predict(image, config) {
|
||||||
profile.run('emotion', profileData);
|
profile.run('emotion', profileData);
|
||||||
}
|
}
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
if (scale * data[i] > config.face.emotion.minConfidence) obj.push({ score: Math.min(0.99, Math.trunc(100 * scale * data[i]) / 100), emotion: annotations[i] });
|
if (data[i] > config.face.emotion.minConfidence) obj.push({ score: Math.min(0.99, Math.trunc(100 * data[i]) / 100), emotion: annotations[i] });
|
||||||
}
|
}
|
||||||
obj.sort((a, b) => b.score - a.score);
|
obj.sort((a, b) => b.score - a.score);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,10 +37,11 @@ export async function predict(image, config) {
|
||||||
const greenNorm = tf.mul(green, rgb[1]);
|
const greenNorm = tf.mul(green, rgb[1]);
|
||||||
const blueNorm = tf.mul(blue, rgb[2]);
|
const blueNorm = tf.mul(blue, rgb[2]);
|
||||||
const grayscale = tf.addN([redNorm, greenNorm, blueNorm]);
|
const grayscale = tf.addN([redNorm, greenNorm, blueNorm]);
|
||||||
return grayscale.sub(0.5).mul(2);
|
const normalize = grayscale.sub(0.5).mul(2); // range grayscale:-1..1
|
||||||
|
return normalize;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
enhance = tf.mul(resize, [255.0]);
|
enhance = tf.mul(resize, [255.0]); // range RGB:0..255
|
||||||
}
|
}
|
||||||
tf.dispose(resize);
|
tf.dispose(resize);
|
||||||
|
|
||||||
|
@ -61,10 +62,9 @@ export async function predict(image, config) {
|
||||||
const data = genderT.dataSync();
|
const data = genderT.dataSync();
|
||||||
if (alternative) {
|
if (alternative) {
|
||||||
// returns two values 0..1, bigger one is prediction
|
// returns two values 0..1, bigger one is prediction
|
||||||
const confidence = Math.trunc(100 * Math.abs(data[0] - data[1])) / 100;
|
if (data[0] > config.face.gender.minConfidence || data[1] > config.face.gender.minConfidence) {
|
||||||
if (confidence > config.face.gender.minConfidence) {
|
|
||||||
obj.gender = data[0] > data[1] ? 'female' : 'male';
|
obj.gender = data[0] > data[1] ? 'female' : 'male';
|
||||||
obj.confidence = confidence;
|
obj.confidence = data[0] > data[1] ? (Math.trunc(100 * data[0]) / 100) : (Math.trunc(100 * data[1]) / 100);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// returns one value 0..1, .5 is prediction threshold
|
// returns one value 0..1, .5 is prediction threshold
|
||||||
|
|
280
src/human.ts
280
src/human.ts
|
@ -40,45 +40,57 @@ function mergeDeep(...objects) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Human {
|
class Human {
|
||||||
tf: any;
|
|
||||||
draw: any;
|
|
||||||
package: any;
|
|
||||||
version: string;
|
version: string;
|
||||||
config: any;
|
config: typeof config.default;
|
||||||
fx: any;
|
|
||||||
state: string;
|
state: string;
|
||||||
numTensors: number;
|
image: { tensor, canvas };
|
||||||
analyzeMemoryLeaks: boolean;
|
// classes
|
||||||
checkSanity: boolean;
|
tf: typeof tf;
|
||||||
firstRun: boolean;
|
draw: typeof draw;
|
||||||
perf: any;
|
|
||||||
image: any;
|
|
||||||
models: any;
|
|
||||||
// models
|
// models
|
||||||
facemesh: any;
|
models: {
|
||||||
age: any;
|
face,
|
||||||
gender: any;
|
posenet,
|
||||||
emotion: any;
|
blazepose,
|
||||||
body: any;
|
handpose,
|
||||||
hand: any;
|
iris,
|
||||||
sysinfo: any;
|
age,
|
||||||
|
gender,
|
||||||
|
emotion,
|
||||||
|
embedding,
|
||||||
|
};
|
||||||
|
classes: {
|
||||||
|
facemesh: typeof facemesh;
|
||||||
|
age: typeof age;
|
||||||
|
gender: typeof gender;
|
||||||
|
emotion: typeof emotion;
|
||||||
|
body: typeof posenet | typeof blazepose;
|
||||||
|
hand: typeof handpose;
|
||||||
|
};
|
||||||
|
sysinfo: { platform, agent };
|
||||||
|
#package: any;
|
||||||
|
#perf: any;
|
||||||
|
#numTensors: number;
|
||||||
|
#analyzeMemoryLeaks: boolean;
|
||||||
|
#checkSanity: boolean;
|
||||||
|
#firstRun: boolean;
|
||||||
|
// definition end
|
||||||
|
|
||||||
constructor(userConfig = {}) {
|
constructor(userConfig = {}) {
|
||||||
this.tf = tf;
|
this.tf = tf;
|
||||||
this.draw = draw;
|
this.draw = draw;
|
||||||
this.package = app;
|
this.#package = app;
|
||||||
this.version = app.version;
|
this.version = app.version;
|
||||||
this.config = mergeDeep(config.default, userConfig);
|
this.config = mergeDeep(config.default, userConfig);
|
||||||
this.fx = null;
|
|
||||||
this.state = 'idle';
|
this.state = 'idle';
|
||||||
this.numTensors = 0;
|
this.#numTensors = 0;
|
||||||
this.analyzeMemoryLeaks = false;
|
this.#analyzeMemoryLeaks = false;
|
||||||
this.checkSanity = false;
|
this.#checkSanity = false;
|
||||||
this.firstRun = true;
|
this.#firstRun = true;
|
||||||
this.perf = {};
|
this.#perf = {};
|
||||||
// object that contains all initialized models
|
// object that contains all initialized models
|
||||||
this.models = {
|
this.models = {
|
||||||
facemesh: null,
|
face: null,
|
||||||
posenet: null,
|
posenet: null,
|
||||||
blazepose: null,
|
blazepose: null,
|
||||||
handpose: null,
|
handpose: null,
|
||||||
|
@ -86,38 +98,42 @@ class Human {
|
||||||
age: null,
|
age: null,
|
||||||
gender: null,
|
gender: null,
|
||||||
emotion: null,
|
emotion: null,
|
||||||
|
embedding: null,
|
||||||
};
|
};
|
||||||
// export access to image processing
|
// export access to image processing
|
||||||
this.image = (input) => image.process(input, this.config);
|
// @ts-ignore
|
||||||
|
this.image = (input: any) => image.process(input, this.config);
|
||||||
// export raw access to underlying models
|
// export raw access to underlying models
|
||||||
this.facemesh = facemesh;
|
this.classes = {
|
||||||
this.age = age;
|
facemesh,
|
||||||
this.gender = gender;
|
age,
|
||||||
this.emotion = emotion;
|
gender,
|
||||||
this.body = this.config.body.modelType.startsWith('posenet') ? posenet : blazepose;
|
emotion,
|
||||||
this.hand = handpose;
|
body: this.config.body.modelType.startsWith('posenet') ? posenet : blazepose,
|
||||||
|
hand: handpose,
|
||||||
|
};
|
||||||
// include platform info
|
// include platform info
|
||||||
this.sysinfo = sysinfo.info();
|
this.sysinfo = sysinfo.info();
|
||||||
}
|
}
|
||||||
|
|
||||||
profile() {
|
profileData(): { newBytes, newTensors, peakBytes, numKernelOps, timeKernelOps, slowestKernelOps, largestKernelOps } | {} {
|
||||||
if (this.config.profile) return profile.data;
|
if (this.config.profile) return profile.data;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function: measure tensor leak
|
// helper function: measure tensor leak
|
||||||
analyze(...msg) {
|
#analyze = (...msg) => {
|
||||||
if (!this.analyzeMemoryLeaks) return;
|
if (!this.#analyzeMemoryLeaks) return;
|
||||||
const current = this.tf.engine().state.numTensors;
|
const current = this.tf.engine().state.numTensors;
|
||||||
const previous = this.numTensors;
|
const previous = this.#numTensors;
|
||||||
this.numTensors = current;
|
this.#numTensors = current;
|
||||||
const leaked = current - previous;
|
const leaked = current - previous;
|
||||||
if (leaked !== 0) log(...msg, leaked);
|
if (leaked !== 0) log(...msg, leaked);
|
||||||
}
|
}
|
||||||
|
|
||||||
// quick sanity check on inputs
|
// quick sanity check on inputs
|
||||||
sanity(input) {
|
#sanity = (input) => {
|
||||||
if (!this.checkSanity) return null;
|
if (!this.#checkSanity) return null;
|
||||||
if (!input) return 'input is not defined';
|
if (!input) return 'input is not defined';
|
||||||
if (this.tf.ENV.flags.IS_NODE && !(input instanceof this.tf.Tensor)) {
|
if (this.tf.ENV.flags.IS_NODE && !(input instanceof this.tf.Tensor)) {
|
||||||
return 'input must be a tensor';
|
return 'input must be a tensor';
|
||||||
|
@ -130,7 +146,7 @@ class Human {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
simmilarity(embedding1, embedding2) {
|
simmilarity(embedding1, embedding2): number {
|
||||||
if (this.config.face.embedding.enabled) return embedding.simmilarity(embedding1, embedding2);
|
if (this.config.face.embedding.enabled) return embedding.simmilarity(embedding1, embedding2);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -141,13 +157,13 @@ class Human {
|
||||||
const timeStamp = now();
|
const timeStamp = now();
|
||||||
if (userConfig) this.config = mergeDeep(this.config, userConfig);
|
if (userConfig) this.config = mergeDeep(this.config, userConfig);
|
||||||
|
|
||||||
if (this.firstRun) {
|
if (this.#firstRun) {
|
||||||
if (this.config.debug) log(`version: ${this.version}`);
|
if (this.config.debug) log(`version: ${this.version}`);
|
||||||
if (this.config.debug) log(`tfjs version: ${this.tf.version_core}`);
|
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('platform:', this.sysinfo.platform);
|
||||||
if (this.config.debug) log('agent:', this.sysinfo.agent);
|
if (this.config.debug) log('agent:', this.sysinfo.agent);
|
||||||
|
|
||||||
await this.checkBackend(true);
|
await this.#checkBackend(true);
|
||||||
if (this.tf.ENV.flags.IS_BROWSER) {
|
if (this.tf.ENV.flags.IS_BROWSER) {
|
||||||
if (this.config.debug) log('configuration:', this.config);
|
if (this.config.debug) log('configuration:', this.config);
|
||||||
if (this.config.debug) log('tf flags:', this.tf.ENV.flags);
|
if (this.config.debug) log('tf flags:', this.tf.ENV.flags);
|
||||||
|
@ -184,17 +200,17 @@ class Human {
|
||||||
if (this.config.body.enabled && !this.models.blazepose && this.config.body.modelType.startsWith('blazepose')) this.models.blazepose = await blazepose.load(this.config);
|
if (this.config.body.enabled && !this.models.blazepose && this.config.body.modelType.startsWith('blazepose')) this.models.blazepose = await blazepose.load(this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.firstRun) {
|
if (this.#firstRun) {
|
||||||
if (this.config.debug) log('tf engine state:', this.tf.engine().state.numBytes, 'bytes', this.tf.engine().state.numTensors, 'tensors');
|
if (this.config.debug) log('tf engine state:', this.tf.engine().state.numBytes, 'bytes', this.tf.engine().state.numTensors, 'tensors');
|
||||||
this.firstRun = false;
|
this.#firstRun = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = Math.trunc(now() - timeStamp);
|
const current = Math.trunc(now() - timeStamp);
|
||||||
if (current > (this.perf.load || 0)) this.perf.load = current;
|
if (current > (this.#perf.load || 0)) this.#perf.load = current;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if backend needs initialization if it changed
|
// check if backend needs initialization if it changed
|
||||||
async checkBackend(force = false) {
|
#checkBackend = async (force = false) => {
|
||||||
if (this.config.backend && (this.config.backend !== '') && force || (this.tf.getBackend() !== this.config.backend)) {
|
if (this.config.backend && (this.config.backend !== '') && force || (this.tf.getBackend() !== this.config.backend)) {
|
||||||
const timeStamp = now();
|
const timeStamp = now();
|
||||||
this.state = 'backend';
|
this.state = 'backend';
|
||||||
|
@ -242,11 +258,11 @@ class Human {
|
||||||
if (this.config.debug) log(`gl version:${gl.getParameter(gl.VERSION)} renderer:${gl.getParameter(gl.RENDERER)}`);
|
if (this.config.debug) log(`gl version:${gl.getParameter(gl.VERSION)} renderer:${gl.getParameter(gl.RENDERER)}`);
|
||||||
}
|
}
|
||||||
await this.tf.ready();
|
await this.tf.ready();
|
||||||
this.perf.backend = Math.trunc(now() - timeStamp);
|
this.#perf.backend = Math.trunc(now() - timeStamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateFaceAngle = (mesh) => {
|
#calculateFaceAngle = (mesh) => {
|
||||||
if (!mesh || mesh.length < 300) return {};
|
if (!mesh || mesh.length < 300) return {};
|
||||||
const radians = (a1, a2, b1, b2) => Math.atan2(b2 - a2, b1 - a1);
|
const radians = (a1, a2, b1, b2) => Math.atan2(b2 - a2, b1 - a1);
|
||||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||||
|
@ -264,7 +280,7 @@ class Human {
|
||||||
return angle;
|
return angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
async detectFace(input) {
|
#detectFace = async (input) => {
|
||||||
// run facemesh, includes blazeface and iris
|
// run facemesh, includes blazeface and iris
|
||||||
// eslint-disable-next-line no-async-promise-executor
|
// eslint-disable-next-line no-async-promise-executor
|
||||||
let timeStamp;
|
let timeStamp;
|
||||||
|
@ -293,9 +309,9 @@ class Human {
|
||||||
this.state = 'run:face';
|
this.state = 'run:face';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
const faces = await this.models.face?.estimateFaces(input, this.config);
|
const faces = await this.models.face?.estimateFaces(input, this.config);
|
||||||
this.perf.face = Math.trunc(now() - timeStamp);
|
this.#perf.face = Math.trunc(now() - timeStamp);
|
||||||
for (const face of faces) {
|
for (const face of faces) {
|
||||||
this.analyze('Get Face');
|
this.#analyze('Get Face');
|
||||||
|
|
||||||
// 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) {
|
||||||
|
@ -303,60 +319,60 @@ class Human {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const angle = this.calculateFaceAngle(face.mesh);
|
const angle = this.#calculateFaceAngle(face.mesh);
|
||||||
|
|
||||||
// run age, inherits face from blazeface
|
// run age, inherits face from blazeface
|
||||||
this.analyze('Start Age:');
|
this.#analyze('Start Age:');
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
ageRes = this.config.face.age.enabled ? age.predict(face.image, this.config) : {};
|
ageRes = this.config.face.age.enabled ? age.predict(face.image, this.config) : {};
|
||||||
} else {
|
} else {
|
||||||
this.state = 'run:age';
|
this.state = 'run:age';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
ageRes = this.config.face.age.enabled ? await age.predict(face.image, this.config) : {};
|
ageRes = this.config.face.age.enabled ? await age.predict(face.image, this.config) : {};
|
||||||
this.perf.age = Math.trunc(now() - timeStamp);
|
this.#perf.age = Math.trunc(now() - timeStamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run gender, inherits face from blazeface
|
// run gender, inherits face from blazeface
|
||||||
this.analyze('Start Gender:');
|
this.#analyze('Start Gender:');
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
genderRes = this.config.face.gender.enabled ? gender.predict(face.image, this.config) : {};
|
genderRes = this.config.face.gender.enabled ? gender.predict(face.image, this.config) : {};
|
||||||
} else {
|
} else {
|
||||||
this.state = 'run:gender';
|
this.state = 'run:gender';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
genderRes = this.config.face.gender.enabled ? await gender.predict(face.image, this.config) : {};
|
genderRes = this.config.face.gender.enabled ? await gender.predict(face.image, this.config) : {};
|
||||||
this.perf.gender = Math.trunc(now() - timeStamp);
|
this.#perf.gender = Math.trunc(now() - timeStamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run emotion, inherits face from blazeface
|
// run emotion, inherits face from blazeface
|
||||||
this.analyze('Start Emotion:');
|
this.#analyze('Start Emotion:');
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
emotionRes = this.config.face.emotion.enabled ? emotion.predict(face.image, this.config) : {};
|
emotionRes = this.config.face.emotion.enabled ? emotion.predict(face.image, this.config) : {};
|
||||||
} else {
|
} else {
|
||||||
this.state = 'run:emotion';
|
this.state = 'run:emotion';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
emotionRes = this.config.face.emotion.enabled ? await emotion.predict(face.image, this.config) : {};
|
emotionRes = this.config.face.emotion.enabled ? await emotion.predict(face.image, this.config) : {};
|
||||||
this.perf.emotion = Math.trunc(now() - timeStamp);
|
this.#perf.emotion = Math.trunc(now() - timeStamp);
|
||||||
}
|
}
|
||||||
this.analyze('End Emotion:');
|
this.#analyze('End Emotion:');
|
||||||
|
|
||||||
// run emotion, inherits face from blazeface
|
// run emotion, inherits face from blazeface
|
||||||
this.analyze('Start Embedding:');
|
this.#analyze('Start Embedding:');
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
embeddingRes = this.config.face.embedding.enabled ? embedding.predict(face.image, this.config) : [];
|
embeddingRes = this.config.face.embedding.enabled ? embedding.predict(face.image, this.config) : [];
|
||||||
} else {
|
} else {
|
||||||
this.state = 'run:embedding';
|
this.state = 'run:embedding';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
embeddingRes = this.config.face.embedding.enabled ? await embedding.predict(face.image, this.config) : [];
|
embeddingRes = this.config.face.embedding.enabled ? await embedding.predict(face.image, this.config) : [];
|
||||||
this.perf.embedding = Math.trunc(now() - timeStamp);
|
this.#perf.embedding = Math.trunc(now() - timeStamp);
|
||||||
}
|
}
|
||||||
this.analyze('End Emotion:');
|
this.#analyze('End Emotion:');
|
||||||
|
|
||||||
// if async wait for results
|
// if async wait for results
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
[ageRes, genderRes, emotionRes, embeddingRes] = await Promise.all([ageRes, genderRes, emotionRes, embeddingRes]);
|
[ageRes, genderRes, emotionRes, embeddingRes] = await Promise.all([ageRes, genderRes, emotionRes, embeddingRes]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.analyze('Finish Face:');
|
this.#analyze('Finish Face:');
|
||||||
|
|
||||||
// calculate iris distance
|
// calculate iris distance
|
||||||
// iris: array[ center, left, top, right, bottom]
|
// iris: array[ center, left, top, right, bottom]
|
||||||
|
@ -391,20 +407,20 @@ class Human {
|
||||||
|
|
||||||
// dont need face anymore
|
// dont need face anymore
|
||||||
face.image?.dispose();
|
face.image?.dispose();
|
||||||
this.analyze('End Face');
|
this.#analyze('End Face');
|
||||||
}
|
}
|
||||||
this.analyze('End FaceMesh:');
|
this.#analyze('End FaceMesh:');
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
if (this.perf.face) delete this.perf.face;
|
if (this.#perf.face) delete this.#perf.face;
|
||||||
if (this.perf.age) delete this.perf.age;
|
if (this.#perf.age) delete this.#perf.age;
|
||||||
if (this.perf.gender) delete this.perf.gender;
|
if (this.#perf.gender) delete this.#perf.gender;
|
||||||
if (this.perf.emotion) delete this.perf.emotion;
|
if (this.#perf.emotion) delete this.#perf.emotion;
|
||||||
}
|
}
|
||||||
return faceRes;
|
return faceRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// main detect function
|
// main detect function
|
||||||
async detect(input, userConfig = {}) {
|
async detect(input, userConfig = {}): Promise<{ face, body, hand, gesture, performance, canvas } | { error }> {
|
||||||
// detection happens inside a promise
|
// detection happens inside a promise
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
this.state = 'config';
|
this.state = 'config';
|
||||||
|
@ -415,7 +431,7 @@ class Human {
|
||||||
|
|
||||||
// sanity checks
|
// sanity checks
|
||||||
this.state = 'check';
|
this.state = 'check';
|
||||||
const error = this.sanity(input);
|
const error = this.#sanity(input);
|
||||||
if (error) {
|
if (error) {
|
||||||
log(error, input);
|
log(error, input);
|
||||||
resolve({ error });
|
resolve({ error });
|
||||||
|
@ -424,13 +440,13 @@ class Human {
|
||||||
const timeStart = now();
|
const timeStart = now();
|
||||||
|
|
||||||
// configure backend
|
// configure backend
|
||||||
await this.checkBackend();
|
await this.#checkBackend();
|
||||||
|
|
||||||
// load models if enabled
|
// load models if enabled
|
||||||
await this.load();
|
await this.load();
|
||||||
|
|
||||||
if (this.config.scoped) this.tf.engine().startScope();
|
if (this.config.scoped) this.tf.engine().startScope();
|
||||||
this.analyze('Start Scope:');
|
this.#analyze('Start Scope:');
|
||||||
|
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
const process = image.process(input, this.config);
|
const process = image.process(input, this.config);
|
||||||
|
@ -439,8 +455,8 @@ class Human {
|
||||||
resolve({ error: 'could not convert input to tensor' });
|
resolve({ error: 'could not convert input to tensor' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.perf.image = Math.trunc(now() - timeStamp);
|
this.#perf.image = Math.trunc(now() - timeStamp);
|
||||||
this.analyze('Get Image:');
|
this.#analyze('Get Image:');
|
||||||
|
|
||||||
// prepare where to store model results
|
// prepare where to store model results
|
||||||
let bodyRes;
|
let bodyRes;
|
||||||
|
@ -449,42 +465,42 @@ class Human {
|
||||||
|
|
||||||
// run face detection followed by all models that rely on face bounding box: face mesh, age, gender, emotion
|
// run face detection followed by all models that rely on face bounding box: face mesh, age, gender, emotion
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
faceRes = this.config.face.enabled ? this.detectFace(process.tensor) : [];
|
faceRes = this.config.face.enabled ? this.#detectFace(process.tensor) : [];
|
||||||
if (this.perf.face) delete this.perf.face;
|
if (this.#perf.face) delete this.#perf.face;
|
||||||
} else {
|
} else {
|
||||||
this.state = 'run:face';
|
this.state = 'run:face';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
faceRes = this.config.face.enabled ? await this.detectFace(process.tensor) : [];
|
faceRes = this.config.face.enabled ? await this.#detectFace(process.tensor) : [];
|
||||||
this.perf.face = Math.trunc(now() - timeStamp);
|
this.#perf.face = Math.trunc(now() - timeStamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run body: can be posenet or blazepose
|
// run body: can be posenet or blazepose
|
||||||
this.analyze('Start Body:');
|
this.#analyze('Start Body:');
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
if (this.config.body.modelType.startsWith('posenet')) bodyRes = this.config.body.enabled ? this.models.posenet?.estimatePoses(process.tensor, this.config) : [];
|
if (this.config.body.modelType.startsWith('posenet')) bodyRes = this.config.body.enabled ? this.models.posenet?.estimatePoses(process.tensor, this.config) : [];
|
||||||
else bodyRes = this.config.body.enabled ? blazepose.predict(process.tensor, this.config) : [];
|
else bodyRes = this.config.body.enabled ? blazepose.predict(process.tensor, this.config) : [];
|
||||||
if (this.perf.body) delete this.perf.body;
|
if (this.#perf.body) delete this.#perf.body;
|
||||||
} else {
|
} else {
|
||||||
this.state = 'run:body';
|
this.state = 'run:body';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
if (this.config.body.modelType.startsWith('posenet')) bodyRes = this.config.body.enabled ? await this.models.posenet?.estimatePoses(process.tensor, this.config) : [];
|
if (this.config.body.modelType.startsWith('posenet')) bodyRes = this.config.body.enabled ? await this.models.posenet?.estimatePoses(process.tensor, this.config) : [];
|
||||||
else bodyRes = this.config.body.enabled ? await blazepose.predict(process.tensor, this.config) : [];
|
else bodyRes = this.config.body.enabled ? await blazepose.predict(process.tensor, this.config) : [];
|
||||||
this.perf.body = Math.trunc(now() - timeStamp);
|
this.#perf.body = Math.trunc(now() - timeStamp);
|
||||||
}
|
}
|
||||||
this.analyze('End Body:');
|
this.#analyze('End Body:');
|
||||||
|
|
||||||
// run handpose
|
// run handpose
|
||||||
this.analyze('Start Hand:');
|
this.#analyze('Start Hand:');
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
handRes = this.config.hand.enabled ? this.models.handpose?.estimateHands(process.tensor, this.config) : [];
|
handRes = this.config.hand.enabled ? this.models.handpose?.estimateHands(process.tensor, this.config) : [];
|
||||||
if (this.perf.hand) delete this.perf.hand;
|
if (this.#perf.hand) delete this.#perf.hand;
|
||||||
} else {
|
} else {
|
||||||
this.state = 'run:hand';
|
this.state = 'run:hand';
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
handRes = this.config.hand.enabled ? await this.models.handpose?.estimateHands(process.tensor, this.config) : [];
|
handRes = this.config.hand.enabled ? await this.models.handpose?.estimateHands(process.tensor, this.config) : [];
|
||||||
this.perf.hand = Math.trunc(now() - timeStamp);
|
this.#perf.hand = Math.trunc(now() - timeStamp);
|
||||||
}
|
}
|
||||||
this.analyze('End Hand:');
|
this.#analyze('End Hand:');
|
||||||
|
|
||||||
// if async wait for results
|
// if async wait for results
|
||||||
if (this.config.async) {
|
if (this.config.async) {
|
||||||
|
@ -493,24 +509,24 @@ class Human {
|
||||||
process.tensor.dispose();
|
process.tensor.dispose();
|
||||||
|
|
||||||
if (this.config.scoped) this.tf.engine().endScope();
|
if (this.config.scoped) this.tf.engine().endScope();
|
||||||
this.analyze('End Scope:');
|
this.#analyze('End Scope:');
|
||||||
|
|
||||||
let gestureRes = [];
|
let gestureRes = [];
|
||||||
if (this.config.gesture.enabled) {
|
if (this.config.gesture.enabled) {
|
||||||
timeStamp = now();
|
timeStamp = now();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
gestureRes = [...gesture.face(faceRes), ...gesture.body(bodyRes), ...gesture.hand(handRes), ...gesture.iris(faceRes)];
|
gestureRes = [...gesture.face(faceRes), ...gesture.body(bodyRes), ...gesture.hand(handRes), ...gesture.iris(faceRes)];
|
||||||
if (!this.config.async) this.perf.gesture = Math.trunc(now() - timeStamp);
|
if (!this.config.async) this.#perf.gesture = Math.trunc(now() - timeStamp);
|
||||||
else if (this.perf.gesture) delete this.perf.gesture;
|
else if (this.#perf.gesture) delete this.#perf.gesture;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.perf.total = Math.trunc(now() - timeStart);
|
this.#perf.total = Math.trunc(now() - timeStart);
|
||||||
this.state = 'idle';
|
this.state = 'idle';
|
||||||
resolve({ face: faceRes, body: bodyRes, hand: handRes, gesture: gestureRes, performance: this.perf, canvas: process.canvas });
|
resolve({ face: faceRes, body: bodyRes, hand: handRes, gesture: gestureRes, performance: this.#perf, canvas: process.canvas });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async warmupBitmap() {
|
#warmupBitmap = async () => {
|
||||||
const b64toBlob = (base64, type = 'application/octet-stream') => fetch(`data:${type};base64,${base64}`).then((res) => res.blob());
|
const b64toBlob = (base64, type = 'application/octet-stream') => fetch(`data:${type};base64,${base64}`).then((res) => res.blob());
|
||||||
let blob;
|
let blob;
|
||||||
let res;
|
let res;
|
||||||
|
@ -527,41 +543,39 @@ class Human {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async warmupCanvas() {
|
#warmupCanvas = async () => new Promise((resolve) => {
|
||||||
return new Promise((resolve) => {
|
let src;
|
||||||
let src;
|
let size = 0;
|
||||||
let size = 0;
|
switch (this.config.warmup) {
|
||||||
switch (this.config.warmup) {
|
case 'face':
|
||||||
case 'face':
|
size = 256;
|
||||||
size = 256;
|
src = 'data:image/jpeg;base64,' + sample.face;
|
||||||
src = 'data:image/jpeg;base64,' + sample.face;
|
break;
|
||||||
break;
|
case 'full':
|
||||||
case 'full':
|
case 'body':
|
||||||
case 'body':
|
size = 1200;
|
||||||
size = 1200;
|
src = 'data:image/jpeg;base64,' + sample.body;
|
||||||
src = 'data:image/jpeg;base64,' + sample.body;
|
break;
|
||||||
break;
|
default:
|
||||||
default:
|
src = null;
|
||||||
src = null;
|
}
|
||||||
}
|
// src = encodeURI('../assets/human-sample-upper.jpg');
|
||||||
// src = encodeURI('../assets/human-sample-upper.jpg');
|
const img = new Image();
|
||||||
const img = new Image();
|
img.onload = async () => {
|
||||||
img.onload = async () => {
|
const canvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(size, size) : document.createElement('canvas');
|
||||||
const canvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(size, size) : document.createElement('canvas');
|
canvas.width = img.naturalWidth;
|
||||||
canvas.width = img.naturalWidth;
|
canvas.height = img.naturalHeight;
|
||||||
canvas.height = img.naturalHeight;
|
const ctx = canvas.getContext('2d');
|
||||||
const ctx = canvas.getContext('2d');
|
ctx?.drawImage(img, 0, 0);
|
||||||
ctx?.drawImage(img, 0, 0);
|
// const data = ctx?.getImageData(0, 0, canvas.height, canvas.width);
|
||||||
// const data = ctx?.getImageData(0, 0, canvas.height, canvas.width);
|
const res = await this.detect(canvas, this.config);
|
||||||
const res = await this.detect(canvas, this.config);
|
resolve(res);
|
||||||
resolve(res);
|
};
|
||||||
};
|
if (src) img.src = src;
|
||||||
if (src) img.src = src;
|
else resolve(null);
|
||||||
else resolve(null);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async warmupNode() {
|
#warmupNode = async () => {
|
||||||
const atob = (str) => Buffer.from(str, 'base64');
|
const atob = (str) => Buffer.from(str, 'base64');
|
||||||
const img = this.config.warmup === 'face' ? atob(sample.face) : atob(sample.body);
|
const img = this.config.warmup === 'face' ? atob(sample.face) : atob(sample.body);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -574,15 +588,15 @@ class Human {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async warmup(userConfig) {
|
async warmup(userConfig): Promise<{ face, body, hand, gesture, performance, canvas } | { error }> {
|
||||||
const t0 = now();
|
const t0 = now();
|
||||||
if (userConfig) this.config = mergeDeep(this.config, userConfig);
|
if (userConfig) this.config = mergeDeep(this.config, userConfig);
|
||||||
const video = this.config.videoOptimized;
|
const video = this.config.videoOptimized;
|
||||||
this.config.videoOptimized = false;
|
this.config.videoOptimized = false;
|
||||||
let res;
|
let res;
|
||||||
if (typeof createImageBitmap === 'function') res = await this.warmupBitmap();
|
if (typeof createImageBitmap === 'function') res = await this.#warmupBitmap();
|
||||||
else if (typeof Image !== 'undefined') res = await this.warmupCanvas();
|
else if (typeof Image !== 'undefined') res = await this.#warmupCanvas();
|
||||||
else res = await this.warmupNode();
|
else res = await this.#warmupNode();
|
||||||
this.config.videoOptimized = video;
|
this.config.videoOptimized = video;
|
||||||
const t1 = now();
|
const t1 = now();
|
||||||
if (this.config.debug) log('Warmup', this.config.warmup, Math.round(t1 - t0), 'ms', res);
|
if (this.config.debug) log('Warmup', this.config.warmup, Math.round(t1 - t0), 'ms', res);
|
||||||
|
|
|
@ -13,7 +13,7 @@ let fx = null;
|
||||||
// process input image and return tensor
|
// process input image and return tensor
|
||||||
// input can be tensor, imagedata, htmlimageelement, htmlvideoelement
|
// input can be tensor, imagedata, htmlimageelement, htmlvideoelement
|
||||||
// input is resized and run through imagefx filter
|
// input is resized and run through imagefx filter
|
||||||
export function process(input, config) {
|
export function process(input, config): { tensor, canvas } {
|
||||||
let tensor;
|
let tensor;
|
||||||
if (input instanceof tf.Tensor) {
|
if (input instanceof tf.Tensor) {
|
||||||
tensor = tf.clone(input);
|
tensor = tf.clone(input);
|
||||||
|
@ -28,7 +28,7 @@ export function process(input, config) {
|
||||||
else if (config.filter.width > 0) targetHeight = originalHeight * (config.filter.width / originalWidth);
|
else if (config.filter.width > 0) targetHeight = originalHeight * (config.filter.width / originalWidth);
|
||||||
if (!targetWidth || !targetHeight) {
|
if (!targetWidth || !targetHeight) {
|
||||||
log('Human: invalid input', input);
|
log('Human: invalid input', input);
|
||||||
return null;
|
return { tensor: null, canvas: null };
|
||||||
}
|
}
|
||||||
if (!inCanvas || (inCanvas.width !== targetWidth) || (inCanvas.height !== targetHeight)) {
|
if (!inCanvas || (inCanvas.width !== targetWidth) || (inCanvas.height !== targetHeight)) {
|
||||||
inCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement('canvas');
|
inCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement('canvas');
|
||||||
|
@ -46,7 +46,7 @@ export function process(input, config) {
|
||||||
// log('created FX filter');
|
// log('created FX filter');
|
||||||
fx = tf.ENV.flags.IS_BROWSER ? new fxImage.GLImageFilter({ canvas: outCanvas }) : null; // && (typeof document !== 'undefined')
|
fx = tf.ENV.flags.IS_BROWSER ? new fxImage.GLImageFilter({ canvas: outCanvas }) : null; // && (typeof document !== 'undefined')
|
||||||
}
|
}
|
||||||
if (!fx) return inCanvas;
|
if (!fx) return { tensor: null, canvas: inCanvas };
|
||||||
fx.reset();
|
fx.reset();
|
||||||
fx.addFilter('brightness', config.filter.brightness); // must have at least one filter enabled
|
fx.addFilter('brightness', config.filter.brightness); // must have at least one filter enabled
|
||||||
if (config.filter.contrast !== 0) fx.addFilter('contrast', config.filter.contrast);
|
if (config.filter.contrast !== 0) fx.addFilter('contrast', config.filter.contrast);
|
||||||
|
@ -110,5 +110,6 @@ export function process(input, config) {
|
||||||
pixels.dispose();
|
pixels.dispose();
|
||||||
casted.dispose();
|
casted.dispose();
|
||||||
}
|
}
|
||||||
return { tensor, canvas: config.filter.return ? outCanvas : null };
|
const canvas = config.filter.return ? outCanvas : null;
|
||||||
|
return { tensor, canvas };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,78 @@
|
||||||
|
import * as tf from '../dist/tfjs.esm.js';
|
||||||
|
import * as facemesh from './blazeface/facemesh';
|
||||||
|
import * as age from './age/age';
|
||||||
|
import * as gender from './gender/gender';
|
||||||
|
import * as emotion from './emotion/emotion';
|
||||||
|
import * as posenet from './posenet/posenet';
|
||||||
|
import * as handpose from './handpose/handpose';
|
||||||
|
import * as blazepose from './blazepose/blazepose';
|
||||||
|
import * as config from '../config';
|
||||||
|
import * as draw from './draw';
|
||||||
declare class Human {
|
declare class Human {
|
||||||
tf: any;
|
#private;
|
||||||
draw: any;
|
|
||||||
package: any;
|
|
||||||
version: string;
|
version: string;
|
||||||
config: any;
|
config: typeof config.default;
|
||||||
fx: any;
|
|
||||||
state: string;
|
state: string;
|
||||||
numTensors: number;
|
image: {
|
||||||
analyzeMemoryLeaks: boolean;
|
tensor: any;
|
||||||
checkSanity: boolean;
|
canvas: any;
|
||||||
firstRun: boolean;
|
};
|
||||||
perf: any;
|
tf: typeof tf;
|
||||||
image: any;
|
draw: typeof draw;
|
||||||
models: any;
|
models: {
|
||||||
facemesh: any;
|
face: any;
|
||||||
age: any;
|
posenet: any;
|
||||||
gender: any;
|
blazepose: any;
|
||||||
emotion: any;
|
handpose: any;
|
||||||
body: any;
|
iris: any;
|
||||||
hand: any;
|
age: any;
|
||||||
sysinfo: any;
|
gender: any;
|
||||||
|
emotion: any;
|
||||||
|
embedding: any;
|
||||||
|
};
|
||||||
|
classes: {
|
||||||
|
facemesh: typeof facemesh;
|
||||||
|
age: typeof age;
|
||||||
|
gender: typeof gender;
|
||||||
|
emotion: typeof emotion;
|
||||||
|
body: typeof posenet | typeof blazepose;
|
||||||
|
hand: typeof handpose;
|
||||||
|
};
|
||||||
|
sysinfo: {
|
||||||
|
platform: any;
|
||||||
|
agent: any;
|
||||||
|
};
|
||||||
constructor(userConfig?: {});
|
constructor(userConfig?: {});
|
||||||
profile(): {};
|
profileData(): {
|
||||||
analyze(...msg: any[]): void;
|
newBytes: any;
|
||||||
sanity(input: any): "input is not defined" | "input must be a tensor" | "backend not loaded" | null;
|
newTensors: any;
|
||||||
|
peakBytes: any;
|
||||||
|
numKernelOps: any;
|
||||||
|
timeKernelOps: any;
|
||||||
|
slowestKernelOps: any;
|
||||||
|
largestKernelOps: any;
|
||||||
|
} | {};
|
||||||
simmilarity(embedding1: any, embedding2: any): number;
|
simmilarity(embedding1: any, embedding2: any): number;
|
||||||
load(userConfig?: null): Promise<void>;
|
load(userConfig?: null): Promise<void>;
|
||||||
checkBackend(force?: boolean): Promise<void>;
|
detect(input: any, userConfig?: {}): Promise<{
|
||||||
calculateFaceAngle: (mesh: any) => {};
|
face: any;
|
||||||
detectFace(input: any): Promise<{
|
body: any;
|
||||||
confidence: number;
|
hand: any;
|
||||||
boxConfidence: number;
|
gesture: any;
|
||||||
faceConfidence: number;
|
performance: any;
|
||||||
box: any;
|
canvas: any;
|
||||||
mesh: any;
|
} | {
|
||||||
meshRaw: any;
|
error: any;
|
||||||
boxRaw: any;
|
}>;
|
||||||
annotations: any;
|
warmup(userConfig: any): Promise<{
|
||||||
age: number;
|
face: any;
|
||||||
gender: string;
|
body: any;
|
||||||
genderConfidence: number;
|
hand: any;
|
||||||
emotion: string;
|
gesture: any;
|
||||||
embedding: any;
|
performance: any;
|
||||||
iris: number;
|
canvas: any;
|
||||||
angle: any;
|
} | {
|
||||||
}[]>;
|
error: any;
|
||||||
detect(input: any, userConfig?: {}): Promise<unknown>;
|
}>;
|
||||||
warmupBitmap(): Promise<any>;
|
|
||||||
warmupCanvas(): Promise<unknown>;
|
|
||||||
warmupNode(): Promise<unknown>;
|
|
||||||
warmup(userConfig: any): Promise<any>;
|
|
||||||
}
|
}
|
||||||
export { Human as default };
|
export { Human as default };
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export declare function process(input: any, config: any): {
|
export declare function process(input: any, config: any): {
|
||||||
tensor: any;
|
tensor: any;
|
||||||
canvas: null;
|
canvas: any;
|
||||||
} | null;
|
};
|
||||||
|
|
Loading…
Reference in New Issue