implemented buffered processing

pull/50/head
Vladimir Mandic 2020-11-12 09:21:26 -05:00
parent 8a8e943a72
commit e78cbe7064
10 changed files with 222 additions and 132 deletions

View File

@ -27,20 +27,25 @@ const ui = {
fillPolygons: false, fillPolygons: false,
useDepth: true, useDepth: true,
console: true, console: true,
maxFrames: 10, maxFPSframes: 10,
modelsPreload: true, modelsPreload: true,
modelsWarmup: true, modelsWarmup: true,
menuWidth: 0, menuWidth: 0,
menuHeight: 0, menuHeight: 0,
camera: {}, camera: {},
fps: [], fps: [],
buffered: true,
bufferedFPSTarget: 24,
drawThread: null,
framesDraw: 0,
framesDetect: 0,
}; };
// global variables // global variables
let menu; let menu;
let menuFX; let menuFX;
let worker; let worker;
let timeStamp; let lastDetectedResult = {};
// helper function: translates json to human readable string // helper function: translates json to human readable string
function str(...msg) { function str(...msg) {
@ -65,24 +70,24 @@ const status = (msg) => {
}; };
// draws processed results and starts processing of a next frame // draws processed results and starts processing of a next frame
function drawResults(input, result, canvas) { async function drawResults(input) {
const result = lastDetectedResult;
const canvas = document.getElementById('canvas');
// update fps data // update fps data
const elapsed = performance.now() - timeStamp; // const elapsed = performance.now() - timeStamp;
ui.fps.push(1000 / elapsed); ui.fps.push(1000 / result.performance.total);
if (ui.fps.length > ui.maxFrames) ui.fps.shift(); if (ui.fps.length > ui.maxFPSframes) ui.fps.shift();
// enable for continous performance monitoring // enable for continous performance monitoring
// console.log(result.performance); // console.log(result.performance);
// immediate loop before we even draw results, but limit frame rate to 30
if (input.srcObject) {
// eslint-disable-next-line no-use-before-define
if (elapsed > 33) requestAnimationFrame(() => runHumanDetect(input, canvas));
// eslint-disable-next-line no-use-before-define
else setTimeout(() => runHumanDetect(input, canvas), 33 - elapsed);
}
// draw fps chart // draw fps chart
menu.updateChart('FPS', ui.fps); await menu.updateChart('FPS', ui.fps);
// get updated canvas
result.canvas = await human.image(input, userConfig);
// draw image from video // draw image from video
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.fillStyle = ui.baseBackground; ctx.fillStyle = ui.baseBackground;
@ -95,10 +100,10 @@ function drawResults(input, result, canvas) {
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);
} }
// draw all results // draw all results
draw.face(result.face, canvas, ui, human.facemesh.triangulation); await draw.face(result.face, canvas, ui, human.facemesh.triangulation);
draw.body(result.body, canvas, ui); await draw.body(result.body, canvas, ui);
draw.hand(result.hand, canvas, ui); await draw.hand(result.hand, canvas, ui);
draw.gesture(result.gesture, canvas, ui); await draw.gesture(result.gesture, canvas, ui);
// update log // update log
const engine = human.tf.engine(); const engine = human.tf.engine();
const gpu = engine.backendInstance ? `gpu: ${(engine.backendInstance.numBytesInGPU ? engine.backendInstance.numBytesInGPU : 0).toLocaleString()} bytes` : ''; const gpu = engine.backendInstance ? `gpu: ${(engine.backendInstance.numBytesInGPU ? engine.backendInstance.numBytesInGPU : 0).toLocaleString()} bytes` : '';
@ -112,6 +117,16 @@ function drawResults(input, result, canvas) {
performance: ${str(result.performance)} FPS:${avg}<br> performance: ${str(result.performance)} FPS:${avg}<br>
${warning} ${warning}
`; `;
ui.framesDraw++;
ui.lastFrame = performance.now();
// if buffered, immediate loop but limit frame rate although it's going to run slower as JS is singlethreaded
if (ui.buffered && !ui.drawThread) ui.drawThread = setInterval(() => drawResults(input, canvas), 1000 / ui.bufferedFPSTarget);
// stop buffering
if (!ui.buffered && ui.drawThread) {
clearTimeout(ui.drawThread);
ui.drawThread = null;
}
} }
// setup webcam // setup webcam
@ -197,7 +212,11 @@ function webWorker(input, image, canvas) {
log('warning: image will not show filter effects'); log('warning: image will not show filter effects');
worker.warned = true; worker.warned = true;
} }
drawResults(input, msg.data.result, canvas); lastDetectedResult = msg.data.result;
ui.framesDetect++;
if (!ui.drawThread) drawResults(input);
// eslint-disable-next-line no-use-before-define
requestAnimationFrame(() => runHumanDetect(input, canvas));
}); });
} }
// pass image data as arraybuffer to worker by reference to avoid copy // pass image data as arraybuffer to worker by reference to avoid copy
@ -206,14 +225,19 @@ function webWorker(input, image, canvas) {
// main processing function when input is webcam, can use direct invocation or web worker // main processing function when input is webcam, can use direct invocation or web worker
function runHumanDetect(input, canvas) { function runHumanDetect(input, canvas) {
timeStamp = performance.now();
// if live video // if live video
const live = input.srcObject && (input.srcObject.getVideoTracks()[0].readyState === 'live') && (input.readyState > 2) && (!input.paused); const live = input.srcObject && (input.srcObject.getVideoTracks()[0].readyState === 'live') && (input.readyState > 2) && (!input.paused);
if (!live && input.srcObject) { if (!live && input.srcObject) {
// stop ui refresh
if (ui.drawThread) clearTimeout(ui.drawThread);
ui.drawThread = null;
// if we want to continue and camera not ready, retry in 0.5sec, else just give up // if we want to continue and camera not ready, retry in 0.5sec, else just give up
if (input.paused) log('camera paused'); if (input.paused) log('camera paused');
else if ((input.srcObject.getVideoTracks()[0].readyState === 'live') && (input.readyState <= 2)) setTimeout(() => runHumanDetect(input, canvas), 500); else if ((input.srcObject.getVideoTracks()[0].readyState === 'live') && (input.readyState <= 2)) setTimeout(() => runHumanDetect(input, canvas), 500);
else log(`camera not ready: track state: ${input.srcObject?.getVideoTracks()[0].readyState} stream state: ${input.readyState}`); else log(`camera not ready: track state: ${input.srcObject?.getVideoTracks()[0].readyState} stream state: ${input.readyState}`);
clearTimeout(ui.drawThread);
ui.drawThread = null;
log('frame statistics: drawn:', ui.framesDraw, 'detected:', ui.framesDetect);
return; return;
} }
status(''); status('');
@ -228,14 +252,18 @@ function runHumanDetect(input, canvas) {
} else { } else {
human.detect(input, userConfig).then((result) => { human.detect(input, userConfig).then((result) => {
if (result.error) log(result.error); if (result.error) log(result.error);
else drawResults(input, result, canvas); else {
lastDetectedResult = result;
if (!ui.drawThread) drawResults(input);
ui.framesDetect++;
requestAnimationFrame(() => runHumanDetect(input, canvas));
}
}); });
} }
} }
// main processing function when input is image, can use direct invocation or web worker // main processing function when input is image, can use direct invocation or web worker
async function processImage(input) { async function processImage(input) {
timeStamp = performance.now();
return new Promise((resolve) => { return new Promise((resolve) => {
const image = new Image(); const image = new Image();
image.onload = async () => { image.onload = async () => {
@ -356,6 +384,7 @@ function setupMenu() {
menuFX = new Menu(document.body, '', { top: '1rem', right: '18rem' }); menuFX = new Menu(document.body, '', { top: '1rem', right: '18rem' });
menuFX.addLabel('ui options'); menuFX.addLabel('ui options');
menuFX.addBool('buffered output', ui, 'buffered', (val) => ui.buffered = val);
menuFX.addBool('crop & scale', ui, 'crop', () => setupCamera()); menuFX.addBool('crop & scale', ui, 'crop', () => setupCamera());
menuFX.addBool('camera front/back', ui, 'facing', () => setupCamera()); menuFX.addBool('camera front/back', ui, 'facing', () => setupCamera());
menuFX.addBool('use 3D depth', ui, 'useDepth'); menuFX.addBool('use 3D depth', ui, 'useDepth');
@ -387,7 +416,7 @@ async function main() {
log('Human: demo starting ...'); log('Human: demo starting ...');
setupMenu(); setupMenu();
document.getElementById('log').innerText = `Human: version ${human.version} TensorFlow/JS: version ${human.tf.version_core}`; document.getElementById('log').innerText = `Human: version ${human.version} TensorFlow/JS: version ${human.tf.version_core}`;
human.tf.ENV.set('WEBGL_FORCE_F16_TEXTURES', true); // human.tf.ENV.set('WEBGL_FORCE_F16_TEXTURES', true);
// this is not required, just pre-loads all models // this is not required, just pre-loads all models
if (ui.modelsPreload) { if (ui.modelsPreload) {
status('loading'); status('loading');

View File

@ -98,19 +98,27 @@ async function drawFace(result, canvas, ui, triangulation) {
} }
} }
const lastDrawnPose = [];
async function drawBody(result, canvas, ui) { async function drawBody(result, canvas, ui) {
if (!result) return; if (!result) return;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.lineJoin = 'round'; ctx.lineJoin = 'round';
for (const pose of result) { for (const i in result) {
if (!lastDrawnPose[i] && ui.buffered) lastDrawnPose[i] = { ...result[i] };
ctx.fillStyle = ui.baseColor; ctx.fillStyle = ui.baseColor;
ctx.strokeStyle = ui.baseColor; ctx.strokeStyle = ui.baseColor;
ctx.font = ui.baseFont; ctx.font = ui.baseFont;
ctx.lineWidth = ui.baseLineWidth; ctx.lineWidth = ui.baseLineWidth;
if (ui.drawPoints) { if (ui.drawPoints) {
for (const point of pose.keypoints) { for (const pt in result[i].keypoints) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(point.position.x, point.position.y, 2, 0, 2 * Math.PI); if (ui.buffered) {
lastDrawnPose[i].keypoints[pt].position.x = (lastDrawnPose[i].keypoints[pt].position.x + result[i].keypoints[pt].position.x) / 2;
lastDrawnPose[i].keypoints[pt].position.y = (lastDrawnPose[i].keypoints[pt].position.y + result[i].keypoints[pt].position.y) / 2;
ctx.arc(lastDrawnPose[i].keypoints[pt].position.x, lastDrawnPose[i].keypoints[pt].position.y, 2, 0, 2 * Math.PI);
} else {
ctx.arc(result[i].keypoints[pt].position.x, result[i].keypoints[pt].position.y, 2, 0, 2 * Math.PI);
}
ctx.fill(); ctx.fill();
} }
} }
@ -118,46 +126,46 @@ async function drawBody(result, canvas, ui) {
const path = new Path2D(); const path = new Path2D();
let part; let part;
// torso // torso
part = pose.keypoints.find((a) => a.part === 'leftShoulder'); part = result[i].keypoints.find((a) => a.part === 'leftShoulder');
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'rightShoulder'); part = result[i].keypoints.find((a) => a.part === 'rightShoulder');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'rightHip'); part = result[i].keypoints.find((a) => a.part === 'rightHip');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'leftHip'); part = result[i].keypoints.find((a) => a.part === 'leftHip');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'leftShoulder'); part = result[i].keypoints.find((a) => a.part === 'leftShoulder');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
// legs // legs
part = pose.keypoints.find((a) => a.part === 'leftHip'); part = result[i].keypoints.find((a) => a.part === 'leftHip');
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'leftKnee'); part = result[i].keypoints.find((a) => a.part === 'leftKnee');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'leftAnkle'); part = result[i].keypoints.find((a) => a.part === 'leftAnkle');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'rightHip'); part = result[i].keypoints.find((a) => a.part === 'rightHip');
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'rightKnee'); part = result[i].keypoints.find((a) => a.part === 'rightKnee');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'rightAnkle'); part = result[i].keypoints.find((a) => a.part === 'rightAnkle');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
// arms // arms
part = pose.keypoints.find((a) => a.part === 'rightShoulder'); part = result[i].keypoints.find((a) => a.part === 'rightShoulder');
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'leftShoulder'); part = result[i].keypoints.find((a) => a.part === 'leftShoulder');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'leftElbow'); part = result[i].keypoints.find((a) => a.part === 'leftElbow');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'leftWrist'); part = result[i].keypoints.find((a) => a.part === 'leftWrist');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
// arms // arms
part = pose.keypoints.find((a) => a.part === 'leftShoulder'); part = result[i].keypoints.find((a) => a.part === 'leftShoulder');
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'rightShoulder'); part = result[i].keypoints.find((a) => a.part === 'rightShoulder');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'rightElbow'); part = result[i].keypoints.find((a) => a.part === 'rightElbow');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === 'rightWrist'); part = result[i].keypoints.find((a) => a.part === 'rightWrist');
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
// draw all // draw all
ctx.stroke(path); ctx.stroke(path);

View File

@ -98001,7 +98001,7 @@ class Human {
} }
async load(userConfig2) { async load(userConfig2) {
this.state = "load"; this.state = "load";
const timeStamp2 = now2(); const timeStamp = now2();
if (userConfig2) if (userConfig2)
this.config = mergeDeep(this.config, userConfig2); this.config = mergeDeep(this.config, userConfig2);
if (this.firstRun) { if (this.firstRun) {
@ -98041,12 +98041,12 @@ class Human {
if (this.config.hand.enabled && !this.models.handpose) if (this.config.hand.enabled && !this.models.handpose)
this.models.handpose = await handpose.load(this.config.hand); this.models.handpose = await handpose.load(this.config.hand);
} }
const current = Math.trunc(now2() - timeStamp2); const current = Math.trunc(now2() - timeStamp);
if (current > (this.perf.load || 0)) if (current > (this.perf.load || 0))
this.perf.load = current; this.perf.load = current;
} }
async checkBackend(force) { async checkBackend(force) {
const timeStamp2 = now2(); const timeStamp = now2();
if (this.config.backend && this.config.backend !== "" && force || tf.getBackend() !== this.config.backend) { if (this.config.backend && this.config.backend !== "" && force || tf.getBackend() !== this.config.backend) {
this.state = "backend"; this.state = "backend";
this.log("setting backend:", this.config.backend); this.log("setting backend:", this.config.backend);
@ -98068,20 +98068,20 @@ class Human {
} }
await tf.ready(); await tf.ready();
} }
const current = Math.trunc(now2() - timeStamp2); const current = Math.trunc(now2() - timeStamp);
if (current > (this.perf.backend || 0)) if (current > (this.perf.backend || 0))
this.perf.backend = current; this.perf.backend = current;
} }
async detectFace(input) { async detectFace(input) {
let timeStamp2; let timeStamp;
let ageRes; let ageRes;
let genderRes; let genderRes;
let emotionRes; let emotionRes;
const faceRes = []; const faceRes = [];
this.state = "run:face"; this.state = "run:face";
timeStamp2 = now2(); timeStamp = now2();
const faces = await this.models.facemesh.estimateFaces(input, this.config.face); const faces = await this.models.facemesh.estimateFaces(input, this.config.face);
this.perf.face = Math.trunc(now2() - timeStamp2); this.perf.face = Math.trunc(now2() - timeStamp);
for (const face2 of faces) { for (const face2 of faces) {
this.analyze("Get Face"); this.analyze("Get Face");
if (!face2.image || face2.image.isDisposedInternal) { if (!face2.image || face2.image.isDisposedInternal) {
@ -98093,27 +98093,27 @@ class Human {
ageRes = this.config.face.age.enabled ? age.predict(face2.image, this.config) : {}; ageRes = this.config.face.age.enabled ? age.predict(face2.image, this.config) : {};
} else { } else {
this.state = "run:age"; this.state = "run:age";
timeStamp2 = now2(); timeStamp = now2();
ageRes = this.config.face.age.enabled ? await age.predict(face2.image, this.config) : {}; ageRes = this.config.face.age.enabled ? await age.predict(face2.image, this.config) : {};
this.perf.age = Math.trunc(now2() - timeStamp2); this.perf.age = Math.trunc(now2() - timeStamp);
} }
this.analyze("Start Gender:"); this.analyze("Start Gender:");
if (this.config.async) { if (this.config.async) {
genderRes = this.config.face.gender.enabled ? gender.predict(face2.image, this.config) : {}; genderRes = this.config.face.gender.enabled ? gender.predict(face2.image, this.config) : {};
} else { } else {
this.state = "run:gender"; this.state = "run:gender";
timeStamp2 = now2(); timeStamp = now2();
genderRes = this.config.face.gender.enabled ? await gender.predict(face2.image, this.config) : {}; genderRes = this.config.face.gender.enabled ? await gender.predict(face2.image, this.config) : {};
this.perf.gender = Math.trunc(now2() - timeStamp2); this.perf.gender = Math.trunc(now2() - timeStamp);
} }
this.analyze("Start Emotion:"); this.analyze("Start Emotion:");
if (this.config.async) { if (this.config.async) {
emotionRes = this.config.face.emotion.enabled ? emotion.predict(face2.image, this.config) : {}; emotionRes = this.config.face.emotion.enabled ? emotion.predict(face2.image, this.config) : {};
} else { } else {
this.state = "run:emotion"; this.state = "run:emotion";
timeStamp2 = now2(); timeStamp = now2();
emotionRes = this.config.face.emotion.enabled ? await emotion.predict(face2.image, this.config) : {}; emotionRes = this.config.face.emotion.enabled ? await emotion.predict(face2.image, this.config) : {};
this.perf.emotion = Math.trunc(now2() - timeStamp2); this.perf.emotion = Math.trunc(now2() - timeStamp);
} }
this.analyze("End Emotion:"); this.analyze("End Emotion:");
if (this.config.async) { if (this.config.async) {
@ -98148,9 +98148,16 @@ class Human {
} }
return faceRes; return faceRes;
} }
async image(input, userConfig2 = {}) {
this.state = "image";
this.config = mergeDeep(this.config, userConfig2);
const process3 = image.process(input, this.config);
process3.tensor.dispose();
return process3.canvas;
}
async detect(input, userConfig2 = {}) { async detect(input, userConfig2 = {}) {
this.state = "config"; this.state = "config";
let timeStamp2; let timeStamp;
this.config = mergeDeep(this.config, userConfig2); this.config = mergeDeep(this.config, userConfig2);
if (!this.config.videoOptimized) if (!this.config.videoOptimized)
this.config = mergeDeep(this.config, disableSkipFrames); this.config = mergeDeep(this.config, disableSkipFrames);
@ -98170,9 +98177,9 @@ class Human {
if (this.config.scoped) if (this.config.scoped)
tf.engine().startScope(); tf.engine().startScope();
this.analyze("Start Scope:"); this.analyze("Start Scope:");
timeStamp2 = now2(); timeStamp = now2();
const process3 = image.process(input, this.config); const process3 = image.process(input, this.config);
this.perf.image = Math.trunc(now2() - timeStamp2); this.perf.image = Math.trunc(now2() - timeStamp);
this.analyze("Get Image:"); this.analyze("Get Image:");
if (this.config.async) { if (this.config.async) {
faceRes = this.config.face.enabled ? this.detectFace(process3.tensor) : []; faceRes = this.config.face.enabled ? this.detectFace(process3.tensor) : [];
@ -98180,9 +98187,9 @@ class Human {
delete this.perf.face; delete this.perf.face;
} else { } else {
this.state = "run:face"; this.state = "run:face";
timeStamp2 = now2(); timeStamp = now2();
faceRes = this.config.face.enabled ? await this.detectFace(process3.tensor) : []; faceRes = this.config.face.enabled ? await this.detectFace(process3.tensor) : [];
this.perf.face = Math.trunc(now2() - timeStamp2); this.perf.face = Math.trunc(now2() - timeStamp);
} }
this.analyze("Start Body:"); this.analyze("Start Body:");
if (this.config.async) { if (this.config.async) {
@ -98191,9 +98198,9 @@ class Human {
delete this.perf.body; delete this.perf.body;
} else { } else {
this.state = "run:body"; this.state = "run:body";
timeStamp2 = now2(); timeStamp = now2();
poseRes = this.config.body.enabled ? await this.models.posenet.estimatePoses(process3.tensor, this.config) : []; poseRes = this.config.body.enabled ? await this.models.posenet.estimatePoses(process3.tensor, this.config) : [];
this.perf.body = Math.trunc(now2() - timeStamp2); this.perf.body = Math.trunc(now2() - timeStamp);
} }
this.analyze("End Body:"); this.analyze("End Body:");
this.analyze("Start Hand:"); this.analyze("Start Hand:");
@ -98203,9 +98210,9 @@ class Human {
delete this.perf.hand; delete this.perf.hand;
} else { } else {
this.state = "run:hand"; this.state = "run:hand";
timeStamp2 = now2(); timeStamp = now2();
handRes = this.config.hand.enabled ? await this.models.handpose.estimateHands(process3.tensor, this.config.hand) : []; handRes = this.config.hand.enabled ? await this.models.handpose.estimateHands(process3.tensor, this.config.hand) : [];
this.perf.hand = Math.trunc(now2() - timeStamp2); this.perf.hand = Math.trunc(now2() - timeStamp);
} }
if (this.config.async) { if (this.config.async) {
[faceRes, poseRes, handRes] = await Promise.all([faceRes, poseRes, handRes]); [faceRes, poseRes, handRes] = await Promise.all([faceRes, poseRes, handRes]);
@ -98216,10 +98223,10 @@ class Human {
this.analyze("End Scope:"); this.analyze("End Scope:");
let gestureRes = []; let gestureRes = [];
if (this.config.gesture.enabled) { if (this.config.gesture.enabled) {
timeStamp2 = now2(); timeStamp = now2();
gestureRes = {face: gesture.face(faceRes), body: gesture.body(poseRes), hand: gesture.hand(handRes)}; gestureRes = {face: gesture.face(faceRes), body: gesture.body(poseRes), hand: gesture.hand(handRes)};
if (!this.config.async) if (!this.config.async)
this.perf.gesture = Math.trunc(now2() - timeStamp2); this.perf.gesture = Math.trunc(now2() - timeStamp);
else if (this.perf.gesture) else if (this.perf.gesture)
delete this.perf.gesture; delete this.perf.gesture;
} }
@ -98337,63 +98344,72 @@ async function drawFace(result, canvas, ui2, triangulation) {
} }
} }
} }
const lastDrawnPose = [];
async function drawBody(result, canvas, ui2) { async function drawBody(result, canvas, ui2) {
if (!result) if (!result)
return; return;
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
ctx.lineJoin = "round"; ctx.lineJoin = "round";
for (const pose of result) { for (const i in result) {
if (!lastDrawnPose[i] && ui2.buffered)
lastDrawnPose[i] = {...result[i]};
ctx.fillStyle = ui2.baseColor; ctx.fillStyle = ui2.baseColor;
ctx.strokeStyle = ui2.baseColor; ctx.strokeStyle = ui2.baseColor;
ctx.font = ui2.baseFont; ctx.font = ui2.baseFont;
ctx.lineWidth = ui2.baseLineWidth; ctx.lineWidth = ui2.baseLineWidth;
if (ui2.drawPoints) { if (ui2.drawPoints) {
for (const point of pose.keypoints) { for (const pt in result[i].keypoints) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(point.position.x, point.position.y, 2, 0, 2 * Math.PI); if (ui2.buffered) {
lastDrawnPose[i].keypoints[pt].position.x = (lastDrawnPose[i].keypoints[pt].position.x + result[i].keypoints[pt].position.x) / 2;
lastDrawnPose[i].keypoints[pt].position.y = (lastDrawnPose[i].keypoints[pt].position.y + result[i].keypoints[pt].position.y) / 2;
ctx.arc(lastDrawnPose[i].keypoints[pt].position.x, lastDrawnPose[i].keypoints[pt].position.y, 2, 0, 2 * Math.PI);
} else {
ctx.arc(result[i].keypoints[pt].position.x, result[i].keypoints[pt].position.y, 2, 0, 2 * Math.PI);
}
ctx.fill(); ctx.fill();
} }
} }
if (ui2.drawPolygons) { if (ui2.drawPolygons) {
const path = new Path2D(); const path = new Path2D();
let part; let part;
part = pose.keypoints.find((a) => a.part === "leftShoulder"); part = result[i].keypoints.find((a) => a.part === "leftShoulder");
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "rightShoulder"); part = result[i].keypoints.find((a) => a.part === "rightShoulder");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "rightHip"); part = result[i].keypoints.find((a) => a.part === "rightHip");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "leftHip"); part = result[i].keypoints.find((a) => a.part === "leftHip");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "leftShoulder"); part = result[i].keypoints.find((a) => a.part === "leftShoulder");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "leftHip"); part = result[i].keypoints.find((a) => a.part === "leftHip");
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "leftKnee"); part = result[i].keypoints.find((a) => a.part === "leftKnee");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "leftAnkle"); part = result[i].keypoints.find((a) => a.part === "leftAnkle");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "rightHip"); part = result[i].keypoints.find((a) => a.part === "rightHip");
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "rightKnee"); part = result[i].keypoints.find((a) => a.part === "rightKnee");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "rightAnkle"); part = result[i].keypoints.find((a) => a.part === "rightAnkle");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "rightShoulder"); part = result[i].keypoints.find((a) => a.part === "rightShoulder");
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "leftShoulder"); part = result[i].keypoints.find((a) => a.part === "leftShoulder");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "leftElbow"); part = result[i].keypoints.find((a) => a.part === "leftElbow");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "leftWrist"); part = result[i].keypoints.find((a) => a.part === "leftWrist");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "leftShoulder"); part = result[i].keypoints.find((a) => a.part === "leftShoulder");
path.moveTo(part.position.x, part.position.y); path.moveTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "rightShoulder"); part = result[i].keypoints.find((a) => a.part === "rightShoulder");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "rightElbow"); part = result[i].keypoints.find((a) => a.part === "rightElbow");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
part = pose.keypoints.find((a) => a.part === "rightWrist"); part = result[i].keypoints.find((a) => a.part === "rightWrist");
path.lineTo(part.position.x, part.position.y); path.lineTo(part.position.x, part.position.y);
ctx.stroke(path); ctx.stroke(path);
} }
@ -98791,18 +98807,23 @@ const ui = {
fillPolygons: false, fillPolygons: false,
useDepth: true, useDepth: true,
console: true, console: true,
maxFrames: 10, maxFPSframes: 10,
modelsPreload: true, modelsPreload: true,
modelsWarmup: true, modelsWarmup: true,
menuWidth: 0, menuWidth: 0,
menuHeight: 0, menuHeight: 0,
camera: {}, camera: {},
fps: [] fps: [],
buffered: true,
bufferedFPSTarget: 24,
drawThread: null,
framesDraw: 0,
framesDetect: 0
}; };
let menu2; let menu2;
let menuFX; let menuFX;
let worker; let worker;
let timeStamp; let lastDetectedResult = {};
function str(...msg) { function str(...msg) {
if (!Array.isArray(msg)) if (!Array.isArray(msg))
return msg; return msg;
@ -98822,18 +98843,14 @@ const log2 = (...msg) => {
const status = (msg) => { const status = (msg) => {
document.getElementById("status").innerText = msg; document.getElementById("status").innerText = msg;
}; };
function drawResults(input, result, canvas) { async function drawResults(input) {
const elapsed = performance.now() - timeStamp; const result = lastDetectedResult;
ui.fps.push(1e3 / elapsed); const canvas = document.getElementById("canvas");
if (ui.fps.length > ui.maxFrames) ui.fps.push(1e3 / result.performance.total);
if (ui.fps.length > ui.maxFPSframes)
ui.fps.shift(); ui.fps.shift();
if (input.srcObject) { await menu2.updateChart("FPS", ui.fps);
if (elapsed > 33) result.canvas = await human.image(input, userConfig);
requestAnimationFrame(() => runHumanDetect(input, canvas));
else
setTimeout(() => runHumanDetect(input, canvas), 33 - elapsed);
}
menu2.updateChart("FPS", ui.fps);
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
ctx.fillStyle = ui.baseBackground; ctx.fillStyle = ui.baseBackground;
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
@ -98846,10 +98863,10 @@ function drawResults(input, result, canvas) {
} else { } else {
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);
} }
draw_default.face(result.face, canvas, ui, human.facemesh.triangulation); await draw_default.face(result.face, canvas, ui, human.facemesh.triangulation);
draw_default.body(result.body, canvas, ui); await draw_default.body(result.body, canvas, ui);
draw_default.hand(result.hand, canvas, ui); await draw_default.hand(result.hand, canvas, ui);
draw_default.gesture(result.gesture, canvas, ui); await draw_default.gesture(result.gesture, canvas, ui);
const engine = human.tf.engine(); const engine = human.tf.engine();
const gpu = engine.backendInstance ? `gpu: ${(engine.backendInstance.numBytesInGPU ? engine.backendInstance.numBytesInGPU : 0).toLocaleString()} bytes` : ""; const gpu = engine.backendInstance ? `gpu: ${(engine.backendInstance.numBytesInGPU ? engine.backendInstance.numBytesInGPU : 0).toLocaleString()} bytes` : "";
const memory = `system: ${engine.state.numBytes.toLocaleString()} bytes ${gpu} | tensors: ${engine.state.numTensors.toLocaleString()}`; const memory = `system: ${engine.state.numBytes.toLocaleString()} bytes ${gpu} | tensors: ${engine.state.numTensors.toLocaleString()}`;
@ -98862,6 +98879,14 @@ function drawResults(input, result, canvas) {
performance: ${str(result.performance)} FPS:${avg}<br> performance: ${str(result.performance)} FPS:${avg}<br>
${warning} ${warning}
`; `;
ui.framesDraw++;
ui.lastFrame = performance.now();
if (ui.buffered && !ui.drawThread)
ui.drawThread = setInterval(() => drawResults(input, canvas), 1e3 / ui.bufferedFPSTarget);
if (!ui.buffered && ui.drawThread) {
clearTimeout(ui.drawThread);
ui.drawThread = null;
}
} }
async function setupCamera() { async function setupCamera() {
var _a; var _a;
@ -98944,22 +98969,31 @@ function webWorker(input, image2, canvas) {
log2("warning: image will not show filter effects"); log2("warning: image will not show filter effects");
worker.warned = true; worker.warned = true;
} }
drawResults(input, msg.data.result, canvas); lastDetectedResult = msg.data.result;
ui.framesDetect++;
if (!ui.drawThread)
drawResults(input);
requestAnimationFrame(() => runHumanDetect(input, canvas));
}); });
} }
worker.postMessage({image: image2.data.buffer, width: canvas.width, height: canvas.height}, [image2.data.buffer]); worker.postMessage({image: image2.data.buffer, width: canvas.width, height: canvas.height}, [image2.data.buffer]);
} }
function runHumanDetect(input, canvas) { function runHumanDetect(input, canvas) {
var _a; var _a;
timeStamp = performance.now();
const live = input.srcObject && input.srcObject.getVideoTracks()[0].readyState === "live" && input.readyState > 2 && !input.paused; const live = input.srcObject && input.srcObject.getVideoTracks()[0].readyState === "live" && input.readyState > 2 && !input.paused;
if (!live && input.srcObject) { if (!live && input.srcObject) {
if (ui.drawThread)
clearTimeout(ui.drawThread);
ui.drawThread = null;
if (input.paused) if (input.paused)
log2("camera paused"); log2("camera paused");
else if (input.srcObject.getVideoTracks()[0].readyState === "live" && input.readyState <= 2) else if (input.srcObject.getVideoTracks()[0].readyState === "live" && input.readyState <= 2)
setTimeout(() => runHumanDetect(input, canvas), 500); setTimeout(() => runHumanDetect(input, canvas), 500);
else else
log2(`camera not ready: track state: ${(_a = input.srcObject) == null ? void 0 : _a.getVideoTracks()[0].readyState} stream state: ${input.readyState}`); log2(`camera not ready: track state: ${(_a = input.srcObject) == null ? void 0 : _a.getVideoTracks()[0].readyState} stream state: ${input.readyState}`);
clearTimeout(ui.drawThread);
ui.drawThread = null;
log2("frame statistics: drawn:", ui.framesDraw, "detected:", ui.framesDetect);
return; return;
} }
status(""); status("");
@ -98973,13 +99007,17 @@ function runHumanDetect(input, canvas) {
human.detect(input, userConfig).then((result) => { human.detect(input, userConfig).then((result) => {
if (result.error) if (result.error)
log2(result.error); log2(result.error);
else else {
drawResults(input, result, canvas); lastDetectedResult = result;
if (!ui.drawThread)
drawResults(input);
ui.framesDetect++;
requestAnimationFrame(() => runHumanDetect(input, canvas));
}
}); });
} }
} }
async function processImage(input) { async function processImage(input) {
timeStamp = performance.now();
return new Promise((resolve) => { return new Promise((resolve) => {
const image2 = new Image(); const image2 = new Image();
image2.onload = async () => { image2.onload = async () => {
@ -99092,6 +99130,7 @@ function setupMenu() {
menu2.addChart("FPS", "FPS"); menu2.addChart("FPS", "FPS");
menuFX = new menu_default(document.body, "", {top: "1rem", right: "18rem"}); menuFX = new menu_default(document.body, "", {top: "1rem", right: "18rem"});
menuFX.addLabel("ui options"); menuFX.addLabel("ui options");
menuFX.addBool("buffered output", ui, "buffered", (val) => ui.buffered = val);
menuFX.addBool("crop & scale", ui, "crop", () => setupCamera()); menuFX.addBool("crop & scale", ui, "crop", () => setupCamera());
menuFX.addBool("camera front/back", ui, "facing", () => setupCamera()); menuFX.addBool("camera front/back", ui, "facing", () => setupCamera());
menuFX.addBool("use 3D depth", ui, "useDepth"); menuFX.addBool("use 3D depth", ui, "useDepth");
@ -99122,7 +99161,6 @@ async function main() {
log2("Human: demo starting ..."); log2("Human: demo starting ...");
setupMenu(); setupMenu();
document.getElementById("log").innerText = `Human: version ${human.version} TensorFlow/JS: version ${human.tf.version_core}`; document.getElementById("log").innerText = `Human: version ${human.version} TensorFlow/JS: version ${human.tf.version_core}`;
human.tf.ENV.set("WEBGL_FORCE_F16_TEXTURES", true);
if (ui.modelsPreload) { if (ui.modelsPreload) {
status("loading"); status("loading");
await human.load(userConfig); await human.load(userConfig);

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
{ {
"inputs": { "inputs": {
"demo/browser.js": { "demo/browser.js": {
"bytes": 18822, "bytes": 19806,
"imports": [ "imports": [
{ {
"path": "dist/human.esm.js" "path": "dist/human.esm.js"
@ -15,7 +15,7 @@
] ]
}, },
"demo/draw.js": { "demo/draw.js": {
"bytes": 9119, "bytes": 9814,
"imports": [] "imports": []
}, },
"demo/menu.js": { "demo/menu.js": {
@ -23,7 +23,7 @@
"imports": [] "imports": []
}, },
"dist/human.esm.js": { "dist/human.esm.js": {
"bytes": 3443254, "bytes": 3443493,
"imports": [] "imports": []
} }
}, },
@ -31,25 +31,25 @@
"dist/demo-browser-index.js.map": { "dist/demo-browser-index.js.map": {
"imports": [], "imports": [],
"inputs": {}, "inputs": {},
"bytes": 5410764 "bytes": 5414325
}, },
"dist/demo-browser-index.js": { "dist/demo-browser-index.js": {
"imports": [], "imports": [],
"inputs": { "inputs": {
"dist/human.esm.js": { "dist/human.esm.js": {
"bytesInOutput": 3432571 "bytesInOutput": 3432788
}, },
"demo/draw.js": { "demo/draw.js": {
"bytesInOutput": 8898 "bytesInOutput": 9599
}, },
"demo/menu.js": { "demo/menu.js": {
"bytesInOutput": 13813 "bytesInOutput": 13813
}, },
"demo/browser.js": { "demo/browser.js": {
"bytesInOutput": 16535 "bytesInOutput": 17362
} }
}, },
"bytes": 3471939 "bytes": 3473684
} }
} }
} }

7
dist/human.esm.js vendored
View File

@ -98525,6 +98525,13 @@ class Human {
} }
return faceRes; return faceRes;
} }
async image(input, userConfig = {}) {
this.state = "image";
this.config = mergeDeep(this.config, userConfig);
const process3 = image.process(input, this.config);
process3.tensor.dispose();
return process3.canvas;
}
async detect(input, userConfig = {}) { async detect(input, userConfig = {}) {
this.state = "config"; this.state = "config";
let timeStamp; let timeStamp;

File diff suppressed because one or more lines are too long

8
dist/human.esm.json vendored
View File

@ -12600,7 +12600,7 @@
"imports": [] "imports": []
}, },
"src/human.js": { "src/human.js": {
"bytes": 14550, "bytes": 14787,
"imports": [ "imports": [
{ {
"path": "src/tf.js" "path": "src/tf.js"
@ -12695,7 +12695,7 @@
"dist/human.esm.js.map": { "dist/human.esm.js.map": {
"imports": [], "imports": [],
"inputs": {}, "inputs": {},
"bytes": 5460397 "bytes": 5461264
}, },
"dist/human.esm.js": { "dist/human.esm.js": {
"imports": [], "imports": [],
@ -13433,13 +13433,13 @@
"bytesInOutput": 24 "bytesInOutput": 24
}, },
"src/human.js": { "src/human.js": {
"bytesInOutput": 12120 "bytesInOutput": 12359
}, },
"src/human.js": { "src/human.js": {
"bytesInOutput": 0 "bytesInOutput": 0
} }
}, },
"bytes": 3443254 "bytes": 3443493
} }
} }
} }

View File

@ -284,6 +284,14 @@ class Human {
return faceRes; return faceRes;
} }
async image(input, userConfig = {}) {
this.state = 'image';
this.config = mergeDeep(this.config, userConfig);
const process = image.process(input, this.config);
process.tensor.dispose();
return process.canvas;
}
// main detect function // main detect function
async detect(input, userConfig = {}) { async detect(input, userConfig = {}) {
this.state = 'config'; this.state = 'config';

2
wiki

@ -1 +1 @@
Subproject commit 6b460e9f5252038ef7a94b044fdb789e35d610bd Subproject commit 5dcbe8ad56fc4dc21378046c225185e6203250eb