mirror of https://github.com/vladmandic/human
266 lines
7.8 KiB
JavaScript
266 lines
7.8 KiB
JavaScript
/**
|
|
* Human demo for browsers
|
|
*
|
|
* @description Demo app that enables all Human modules and runs them in separate worker threads
|
|
*
|
|
*/
|
|
// @ts-nocheck // typescript checks disabled as this is pure javascript
|
|
|
|
import Human from '../../dist/human.esm.js'; // equivalent of @vladmandic/human
|
|
import GLBench from '../helpers/gl-bench.js';
|
|
|
|
const workerJS = './worker.js';
|
|
|
|
const config = {
|
|
main: { // processes input and runs gesture analysis
|
|
warmup: 'none',
|
|
backend: 'humangl',
|
|
modelBasePath: '../../models/',
|
|
async: false,
|
|
filter: { enabled: true },
|
|
face: { enabled: false },
|
|
object: { enabled: false },
|
|
gesture: { enabled: true },
|
|
hand: { enabled: false },
|
|
body: { enabled: false },
|
|
segmentation: { enabled: false },
|
|
},
|
|
face: { // runs all face models
|
|
warmup: 'none',
|
|
backend: 'humangl',
|
|
modelBasePath: '../../models/',
|
|
async: false,
|
|
filter: { enabled: false },
|
|
face: { enabled: true },
|
|
object: { enabled: false },
|
|
gesture: { enabled: false },
|
|
hand: { enabled: false },
|
|
body: { enabled: false },
|
|
segmentation: { enabled: false },
|
|
},
|
|
body: { // runs body model
|
|
warmup: 'none',
|
|
backend: 'humangl',
|
|
modelBasePath: '../../models/',
|
|
async: false,
|
|
filter: { enabled: false },
|
|
face: { enabled: false },
|
|
object: { enabled: false },
|
|
gesture: { enabled: false },
|
|
hand: { enabled: false },
|
|
body: { enabled: true },
|
|
segmentation: { enabled: false },
|
|
},
|
|
hand: { // runs hands model
|
|
warmup: 'none',
|
|
backend: 'humangl',
|
|
modelBasePath: '../../models/',
|
|
async: false,
|
|
filter: { enabled: false },
|
|
face: { enabled: false },
|
|
object: { enabled: false },
|
|
gesture: { enabled: false },
|
|
hand: { enabled: true },
|
|
body: { enabled: false },
|
|
segmentation: { enabled: false },
|
|
},
|
|
object: { // runs object model
|
|
warmup: 'none',
|
|
backend: 'humangl',
|
|
modelBasePath: '../../models/',
|
|
async: false,
|
|
filter: { enabled: false },
|
|
face: { enabled: false },
|
|
object: { enabled: true },
|
|
gesture: { enabled: false },
|
|
hand: { enabled: false },
|
|
body: { enabled: false },
|
|
segmentation: { enabled: false },
|
|
},
|
|
};
|
|
|
|
let human;
|
|
let canvas;
|
|
let video;
|
|
let bench;
|
|
|
|
const busy = {
|
|
face: false,
|
|
hand: false,
|
|
body: false,
|
|
object: false,
|
|
};
|
|
|
|
const workers = {
|
|
face: null,
|
|
body: null,
|
|
hand: null,
|
|
object: null,
|
|
};
|
|
|
|
const time = {
|
|
main: 0,
|
|
draw: 0,
|
|
face: '[warmup]',
|
|
body: '[warmup]',
|
|
hand: '[warmup]',
|
|
object: '[warmup]',
|
|
};
|
|
|
|
const start = {
|
|
main: 0,
|
|
draw: 0,
|
|
face: 0,
|
|
body: 0,
|
|
hand: 0,
|
|
object: 0,
|
|
};
|
|
|
|
const result = { // initialize empty result object which will be partially filled with results from each thread
|
|
performance: {},
|
|
hand: [],
|
|
body: [],
|
|
face: [],
|
|
object: [],
|
|
};
|
|
|
|
function log(...msg) {
|
|
const dt = new Date();
|
|
const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
|
|
// eslint-disable-next-line no-console
|
|
console.log(ts, ...msg);
|
|
}
|
|
|
|
async function drawResults() {
|
|
start.draw = human.now();
|
|
const interpolated = human.next(result);
|
|
await human.draw.all(canvas, interpolated);
|
|
time.draw = Math.round(1 + human.now() - start.draw);
|
|
const fps = Math.round(10 * 1000 / time.main) / 10;
|
|
const draw = Math.round(10 * 1000 / time.draw) / 10;
|
|
document.getElementById('log').innerText = `Human: version ${human.version} | Performance: Main ${time.main}ms Face: ${time.face}ms Body: ${time.body}ms Hand: ${time.hand}ms Object ${time.object}ms | FPS: ${fps} / ${draw}`;
|
|
requestAnimationFrame(drawResults);
|
|
}
|
|
|
|
async function receiveMessage(msg) {
|
|
result[msg.data.type] = msg.data.result;
|
|
busy[msg.data.type] = false;
|
|
time[msg.data.type] = Math.round(human.now() - start[msg.data.type]);
|
|
}
|
|
|
|
async function runDetection() {
|
|
start.main = human.now();
|
|
if (!bench) {
|
|
bench = new GLBench(null, { trackGPU: false, chartHz: 20, chartLen: 20 });
|
|
bench.begin();
|
|
}
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
if (!busy.face) {
|
|
busy.face = true;
|
|
start.face = human.now();
|
|
workers.face.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.face, type: 'face' }, [imageData.data.buffer.slice(0)]);
|
|
}
|
|
if (!busy.body) {
|
|
busy.body = true;
|
|
start.body = human.now();
|
|
workers.body.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.body, type: 'body' }, [imageData.data.buffer.slice(0)]);
|
|
}
|
|
if (!busy.hand) {
|
|
busy.hand = true;
|
|
start.hand = human.now();
|
|
workers.hand.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.hand, type: 'hand' }, [imageData.data.buffer.slice(0)]);
|
|
}
|
|
if (!busy.object) {
|
|
busy.object = true;
|
|
start.object = human.now();
|
|
workers.object.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.object, type: 'object' }, [imageData.data.buffer.slice(0)]);
|
|
}
|
|
|
|
time.main = Math.round(human.now() - start.main);
|
|
|
|
bench.nextFrame();
|
|
requestAnimationFrame(runDetection);
|
|
}
|
|
|
|
async function setupCamera() {
|
|
video = document.getElementById('video');
|
|
canvas = document.getElementById('canvas');
|
|
const output = document.getElementById('log');
|
|
let stream;
|
|
const constraints = {
|
|
audio: false,
|
|
video: {
|
|
facingMode: 'user',
|
|
resizeMode: 'crop-and-scale',
|
|
width: { ideal: document.body.clientWidth },
|
|
aspectRatio: document.body.clientWidth / document.body.clientHeight,
|
|
},
|
|
};
|
|
// enumerate devices for diag purposes
|
|
navigator.mediaDevices.enumerateDevices().then((devices) => log('enumerated devices:', devices));
|
|
log('camera constraints', constraints);
|
|
try {
|
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
} catch (err) {
|
|
output.innerText += `\n${err.name}: ${err.message}`;
|
|
status(err.name);
|
|
log('camera error:', err);
|
|
}
|
|
const tracks = stream.getVideoTracks();
|
|
log('enumerated viable tracks:', tracks);
|
|
const track = stream.getVideoTracks()[0];
|
|
const settings = track.getSettings();
|
|
log('selected video source:', track, settings);
|
|
const promise = !stream || new Promise((resolve) => {
|
|
video.onloadeddata = () => {
|
|
if (settings.width > settings.height) canvas.style.width = '100vw';
|
|
else canvas.style.height = '100vh';
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
video.play();
|
|
resolve();
|
|
};
|
|
});
|
|
// attach input to video element
|
|
if (stream) video.srcObject = stream;
|
|
return promise;
|
|
}
|
|
|
|
async function startWorkers() {
|
|
if (!workers.face) workers.face = new Worker(workerJS);
|
|
if (!workers.body) workers.body = new Worker(workerJS);
|
|
if (!workers.hand) workers.hand = new Worker(workerJS);
|
|
if (!workers.object) workers.object = new Worker(workerJS);
|
|
workers.face.onmessage = receiveMessage;
|
|
workers.body.onmessage = receiveMessage;
|
|
workers.hand.onmessage = receiveMessage;
|
|
workers.object.onmessage = receiveMessage;
|
|
}
|
|
|
|
async function main() {
|
|
window.addEventListener('unhandledrejection', (evt) => {
|
|
// eslint-disable-next-line no-console
|
|
console.error(evt.reason || evt);
|
|
document.getElementById('log').innerHTML = evt.reason.message || evt.reason || evt;
|
|
status('exception error');
|
|
evt.preventDefault();
|
|
});
|
|
|
|
if (typeof Worker === 'undefined' || typeof OffscreenCanvas === 'undefined') {
|
|
status('workers are not supported');
|
|
return;
|
|
}
|
|
|
|
human = new Human(config.main);
|
|
document.getElementById('log').innerText = `Human: version ${human.version}`;
|
|
|
|
await startWorkers();
|
|
await setupCamera();
|
|
runDetection();
|
|
drawResults();
|
|
}
|
|
|
|
window.onload = main;
|