reduced web worker latency

pull/293/head
Vladimir Mandic 2020-10-15 18:16:05 -04:00
parent c4f04a4904
commit 69ee764cc1
2 changed files with 31 additions and 20 deletions

View File

@ -8,10 +8,13 @@ const log = (...msg) => {
}; };
onmessage = async (msg) => { onmessage = async (msg) => {
// worker.postMessage({ image: image.data.buffer, width: canvas.width, height: canvas.height, config }, [image.data.buffer]);
const image = new ImageData(new Uint8ClampedArray(msg.data.image), msg.data.width, msg.data.height);
config = msg.data.config; config = msg.data.config;
let result = {}; let result = {};
try { try {
result = await human.detect(msg.data.image, config); // result = await human.detect(image, config);
result = {};
} catch (err) { } catch (err) {
result.error = err.message; result.error = err.message;
log('Worker thread error:', err.message); log('Worker thread error:', err.message);

View File

@ -17,16 +17,17 @@ const config = {
detector: { maxFaces: 10, skipFrames: 10, minConfidence: 0.5, iouThreshold: 0.3, scoreThreshold: 0.7 }, detector: { maxFaces: 10, skipFrames: 10, minConfidence: 0.5, iouThreshold: 0.3, scoreThreshold: 0.7 },
mesh: { enabled: true }, mesh: { enabled: true },
iris: { enabled: true }, iris: { enabled: true },
age: { enabled: true, skipFrames: 10 }, age: { enabled: false, skipFrames: 10 },
gender: { enabled: true }, gender: { enabled: false },
emotion: { enabled: true, minConfidence: 0.5, useGrayscale: true }, emotion: { enabled: false, minConfidence: 0.5, useGrayscale: true },
}, },
body: { enabled: true, maxDetections: 10, scoreThreshold: 0.7, nmsRadius: 20 }, body: { enabled: false, maxDetections: 10, scoreThreshold: 0.7, nmsRadius: 20 },
hand: { enabled: true, skipFrames: 10, minConfidence: 0.5, iouThreshold: 0.3, scoreThreshold: 0.7 }, hand: { enabled: false, skipFrames: 10, minConfidence: 0.5, iouThreshold: 0.3, scoreThreshold: 0.7 },
}; };
let settings; let settings;
let worker; let worker;
let timeStamp; let timeStamp;
const fps = [];
function str(...msg) { function str(...msg) {
if (!Array.isArray(msg)) return msg; if (!Array.isArray(msg)) return msg;
@ -44,6 +45,7 @@ const log = (...msg) => {
}; };
async function drawFace(result, canvas) { async function drawFace(result, canvas) {
if (!result) return;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.strokeStyle = ui.baseColor; ctx.strokeStyle = ui.baseColor;
ctx.font = ui.baseFont; ctx.font = ui.baseFont;
@ -96,6 +98,7 @@ async function drawFace(result, canvas) {
} }
async function drawBody(result, canvas) { async function drawBody(result, canvas) {
if (!result) return;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.fillStyle = ui.baseColor; ctx.fillStyle = ui.baseColor;
ctx.strokeStyle = ui.baseColor; ctx.strokeStyle = ui.baseColor;
@ -157,6 +160,7 @@ async function drawBody(result, canvas) {
} }
async function drawHand(result, canvas) { async function drawHand(result, canvas) {
if (!result) return;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.font = ui.baseFont; ctx.font = ui.baseFont;
ctx.lineWidth = ui.baseLineWidth; ctx.lineWidth = ui.baseLineWidth;
@ -203,6 +207,13 @@ async function drawHand(result, canvas) {
async function drawResults(input, result, canvas) { async function drawResults(input, result, canvas) {
// update fps // update fps
settings.setValue('FPS', Math.round(1000 / (performance.now() - timeStamp))); settings.setValue('FPS', Math.round(1000 / (performance.now() - timeStamp)));
fps.push(1000 / (performance.now() - timeStamp));
if (fps.length > 20) fps.shift();
settings.setValue('FPS', Math.round(10 * fps.reduce((a, b) => a + b) / fps.length) / 10);
// eslint-disable-next-line no-use-before-define
requestAnimationFrame(() => runHumanDetect(input, canvas)); // immediate loop
// draw image from video // draw image from video
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.drawImage(input, 0, 0, input.width, input.height, 0, 0, canvas.width, canvas.height); ctx.drawImage(input, 0, 0, input.width, input.height, 0, 0, canvas.width, canvas.height);
@ -213,27 +224,24 @@ async function drawResults(input, result, canvas) {
// update log // update log
const engine = await human.tf.engine(); const engine = await human.tf.engine();
const memory = `${engine.state.numBytes.toLocaleString()} bytes ${engine.state.numDataBuffers.toLocaleString()} buffers ${engine.state.numTensors.toLocaleString()} tensors`; const memory = `${engine.state.numBytes.toLocaleString()} bytes ${engine.state.numDataBuffers.toLocaleString()} buffers ${engine.state.numTensors.toLocaleString()} tensors`;
const gpu = engine.backendInstance.numBytesInGPU ? `GPU: ${engine.backendInstance.numBytesInGPU.toLocaleString()} bytes` : ''; const gpu = engine.backendInstance ? `GPU: ${engine.backendInstance.numBytesInGPU.toLocaleString()} bytes` : '';
document.getElementById('log').innerText = ` document.getElementById('log').innerText = `
TFJS Version: ${human.tf.version_core} | Backend: ${human.tf.getBackend()} | Memory: ${memory} ${gpu} TFJS Version: ${human.tf.version_core} | Backend: ${human.tf.getBackend()} | Memory: ${memory} ${gpu}
Performance: ${str(result.performance)} | Object size: ${(str(result)).length.toLocaleString()} bytes Performance: ${str(result.performance)} | Object size: ${(str(result)).length.toLocaleString()} bytes
`; `;
} }
async function webWorker(input, image, canvas) { // simple wrapper for worker.postmessage that creates worker if one does not exist
function webWorker(input, image, canvas) {
if (!worker) { if (!worker) {
// create new webworker and add event handler only once
log('Creating worker thread'); log('Creating worker thread');
// create new webworker
worker = new Worker('demo-esm-webworker.js', { type: 'module' }); worker = new Worker('demo-esm-webworker.js', { type: 'module' });
// after receiving message from webworker, parse&draw results and send new frame for processing // after receiving message from webworker, parse&draw results and send new frame for processing
worker.addEventListener('message', async (msg) => { worker.addEventListener('message', async (msg) => drawResults(input, msg.data, canvas));
await drawResults(input, msg.data, canvas);
// eslint-disable-next-line no-use-before-define
requestAnimationFrame(() => runHumanDetect(input, canvas)); // immediate loop
});
} }
// const offscreen = image.transferControlToOffscreen(); // pass image data as arraybuffer to worker by reference to avoid copy
worker.postMessage({ image, config }); worker.postMessage({ image: image.data.buffer, width: canvas.width, height: canvas.height, config }, [image.data.buffer]);
} }
async function runHumanDetect(input, canvas) { async function runHumanDetect(input, canvas) {
@ -247,17 +255,17 @@ async function runHumanDetect(input, canvas) {
const ctx = offscreen.getContext('2d'); const ctx = offscreen.getContext('2d');
ctx.drawImage(input, 0, 0, input.width, input.height, 0, 0, canvas.width, canvas.height); ctx.drawImage(input, 0, 0, input.width, input.height, 0, 0, canvas.width, canvas.height);
const data = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = ctx.getImageData(0, 0, canvas.width, canvas.height);
// perform detection // perform detection in worker
await webWorker(input, data, canvas); webWorker(input, data, canvas);
} else { } else {
let result = {}; let result = {};
try { try {
// perform detection
result = await human.detect(input, config); result = await human.detect(input, config);
} catch (err) { } catch (err) {
log('Error during execution:', err.message); log('Error during execution:', err.message);
} }
await drawResults(input, result, canvas); drawResults(input, result, canvas);
if (input.readyState) requestAnimationFrame(() => runHumanDetect(input, canvas)); // immediate loop
} }
} }
} }