human/demo/webgpu/index.js

286 lines
8.6 KiB
JavaScript
Raw Normal View History

2021-08-14 19:39:26 +02:00
/**
* Human demo for browsers
*
* @description Experimental Demo app for Human using WebGPU
*
*/
2021-10-26 21:08:05 +02:00
/** @type {Human} */
2021-10-22 15:48:27 +02:00
import Human from '../../dist/human.custom.esm.js';
2021-08-15 14:09:40 +02:00
import GLBench from '../helpers/gl-bench.js';
const workerJS = './worker.js';
const backend = 'webgpu';
const config = {
main: { // processes input and runs gesture analysis
warmup: 'none',
backend,
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,
modelBasePath: '../../models/',
async: false,
filter: { enabled: false },
face: { enabled: true,
detector: { return: false, rotation: false },
mesh: { enabled: true },
iris: { enabled: false },
description: { enabled: true },
emotion: { enabled: false },
},
object: { enabled: false },
gesture: { enabled: false },
hand: { enabled: false },
body: { enabled: false },
segmentation: { enabled: false },
},
body: { // runs body model
warmup: 'none',
backend,
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,
modelBasePath: '../../models/',
async: false,
filter: { enabled: false },
face: { enabled: false },
object: { enabled: false },
gesture: { enabled: false },
hand: { enabled: true, rotation: false },
body: { enabled: false },
segmentation: { enabled: false },
},
object: { // runs object model
warmup: 'none',
backend,
modelBasePath: '../../models/',
async: false,
filter: { enabled: false },
face: { enabled: false },
object: { enabled: false },
gesture: { enabled: false },
hand: { enabled: false },
body: { enabled: false },
segmentation: { enabled: false },
},
};
2021-08-14 19:39:26 +02:00
let human;
let canvas;
let video;
2021-08-15 14:09:40 +02:00
let bench;
const busy = {
face: false,
hand: false,
body: false,
object: false,
};
const workers = {
2021-10-26 21:08:05 +02:00
/** @type {Worker | null} */
2021-08-15 14:09:40 +02:00
face: null,
2021-10-26 21:08:05 +02:00
/** @type {Worker | null} */
2021-08-15 14:09:40 +02:00
body: null,
2021-10-26 21:08:05 +02:00
/** @type {Worker | null} */
2021-08-15 14:09:40 +02:00
hand: null,
2021-10-26 21:08:05 +02:00
/** @type {Worker | null} */
2021-08-15 14:09:40 +02:00
object: null,
2021-08-14 19:39:26 +02:00
};
2021-08-15 14:09:40 +02:00
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: [],
};
2021-08-14 19:39:26 +02:00
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() {
2021-10-19 17:28:59 +02:00
start.draw = human.now();
2021-08-14 19:39:26 +02:00
const interpolated = human.next(result);
await human.draw.all(canvas, interpolated);
2021-10-19 17:28:59 +02:00
time.draw = Math.round(1 + human.now() - start.draw);
2021-08-15 14:09:40 +02:00
const fps = Math.round(10 * 1000 / time.main) / 10;
const draw = Math.round(10 * 1000 / time.draw) / 10;
2021-10-26 21:08:05 +02:00
const div = document.getElementById('log');
if (div) div.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}`;
2021-08-14 19:39:26 +02:00
requestAnimationFrame(drawResults);
}
2021-08-15 14:09:40 +02:00
async function receiveMessage(msg) {
result[msg.data.type] = msg.data.result;
busy[msg.data.type] = false;
2021-10-19 17:28:59 +02:00
time[msg.data.type] = Math.round(human.now() - start[msg.data.type]);
2021-08-15 14:09:40 +02:00
}
2021-08-14 19:39:26 +02:00
async function runDetection() {
2021-10-19 17:28:59 +02:00
start.main = human.now();
2021-08-15 14:09:40 +02:00
if (!bench) {
bench = new GLBench(null, { trackGPU: false, chartHz: 20, chartLen: 20 });
2021-10-26 21:08:05 +02:00
bench.begin('human');
2021-08-15 14:09:40 +02:00
}
const ctx = canvas.getContext('2d');
// const image = await human.image(video);
// ctx.drawImage(image.canvas, 0, 0, canvas.width, canvas.height);
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;
2021-10-19 17:28:59 +02:00
start.face = human.now();
2021-10-26 21:08:05 +02:00
if (workers.face) workers.face.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.face, type: 'face' }, [imageData.data.buffer.slice(0)]);
2021-08-15 14:09:40 +02:00
}
if (!busy.body) {
busy.body = true;
2021-10-19 17:28:59 +02:00
start.body = human.now();
2021-10-26 21:08:05 +02:00
if (workers.body) workers.body.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.body, type: 'body' }, [imageData.data.buffer.slice(0)]);
2021-08-15 14:09:40 +02:00
}
if (!busy.hand) {
busy.hand = true;
2021-10-19 17:28:59 +02:00
start.hand = human.now();
2021-10-26 21:08:05 +02:00
if (workers.hand) workers.hand.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.hand, type: 'hand' }, [imageData.data.buffer.slice(0)]);
2021-08-15 14:09:40 +02:00
}
if (!busy.object) {
busy.object = true;
2021-10-19 17:28:59 +02:00
start.object = human.now();
2021-10-26 21:08:05 +02:00
if (workers.object) workers.object.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, config: config.object, type: 'object' }, [imageData.data.buffer.slice(0)]);
2021-08-15 14:09:40 +02:00
}
2021-10-19 17:28:59 +02:00
time.main = Math.round(human.now() - start.main);
2021-08-15 14:09:40 +02:00
bench.nextFrame();
2021-08-14 19:39:26 +02:00
requestAnimationFrame(runDetection);
}
async function setupCamera() {
2021-10-26 21:08:05 +02:00
video = document.getElementById('video') || document.createElement('video');
canvas = document.getElementById('canvas') || document.createElement('canvas');
2021-08-14 19:39:26 +02:00
const output = document.getElementById('log');
let stream;
const constraints = {
audio: false,
video: {
facingMode: 'user',
resizeMode: 'crop-and-scale',
width: { ideal: document.body.clientWidth },
// height: { ideal: document.body.clientHeight }, // not set as we're using aspectRation to get height instead
aspectRatio: document.body.clientWidth / document.body.clientHeight,
},
};
// enumerate devices for diag purposes
2021-08-15 14:09:40 +02:00
navigator.mediaDevices.enumerateDevices().then((devices) => log('enumerated devices:', devices));
2021-08-14 19:39:26 +02:00
log('camera constraints', constraints);
try {
stream = await navigator.mediaDevices.getUserMedia(constraints);
} catch (err) {
2021-10-26 21:08:05 +02:00
if (output) output.innerText += `\n${err.name}: ${err.message}`;
2021-08-14 19:39:26 +02:00
log('camera error:', err);
}
2021-10-26 21:08:05 +02:00
if (stream) {
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 = new Promise((resolve) => {
video.onloadeddata = () => {
if (settings.width && settings.height && settings.width > settings.height) canvas.style.width = '100vw';
else canvas.style.height = '100vh';
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
video.play();
resolve(true);
};
});
// attach input to video element
video['srcObject'] = stream;
return promise;
}
return false;
2021-08-14 19:39:26 +02:00
}
2021-08-15 14:09:40 +02:00
async function startWorkers() {
2021-10-22 15:48:27 +02:00
if (!workers.face) workers.face = new Worker(workerJS, { type: 'module' });
if (!workers.body) workers.body = new Worker(workerJS, { type: 'module' });
if (!workers.hand) workers.hand = new Worker(workerJS, { type: 'module' });
if (!workers.object) workers.object = new Worker(workerJS, { type: 'module' });
2021-08-15 14:09:40 +02:00
workers.face.onmessage = receiveMessage;
workers.body.onmessage = receiveMessage;
workers.hand.onmessage = receiveMessage;
workers.object.onmessage = receiveMessage;
}
2021-08-14 19:39:26 +02:00
async function main() {
2021-10-22 15:48:27 +02:00
/*
2021-08-15 14:09:40 +02:00
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();
});
2021-10-22 15:48:27 +02:00
*/
2021-08-15 14:09:40 +02:00
if (typeof Worker === 'undefined' || typeof OffscreenCanvas === 'undefined') {
return;
}
human = new Human(config.main);
2021-10-26 21:08:05 +02:00
const div = document.getElementById('log');
if (div) div.innerText = `Human: version ${human.version}`;
2021-08-15 14:09:40 +02:00
await startWorkers();
2021-08-14 19:39:26 +02:00
await setupCamera();
2021-10-22 15:48:27 +02:00
await human.init();
2021-08-14 19:39:26 +02:00
runDetection();
drawResults();
}
window.onload = main;