From df80506b43f8e9c9d5ce30b3f0e2069ffacf14d1 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 12 Oct 2020 10:08:00 -0400 Subject: [PATCH] updated iife and esm demos --- README.md | 90 ++++++++-- demo/demo-esm.js | 269 ++++++++++++++++++++++++++++ demo/{index.html => demo-iife.html} | 6 +- demo/index.js | 127 ------------- package.json | 2 +- src/image.js | 127 ------------- src/index.js | 4 +- 7 files changed, 355 insertions(+), 270 deletions(-) create mode 100644 demo/demo-esm.js rename demo/{index.html => demo-iife.html} (61%) delete mode 100644 demo/index.js delete mode 100644 src/image.js diff --git a/README.md b/README.md index d514a2ad..094d7e7e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ URL: *Suggestions are welcome!* +
+ ## Credits This is an amalgamation of multiple existing models: @@ -15,21 +17,80 @@ This is an amalgamation of multiple existing models: - Body Pose Detection: [**PoseNet**](https://medium.com/tensorflow/real-time-human-pose-estimation-in-the-browser-with-tensorflow-js-7dd0bc881cd5) - Age & Gender Prediction: [**SSR-Net**](https://github.com/shamangary/SSR-Net) -## Install +
-```shell -npm install @vladmandic/human +## Installation + +There are several ways to use Human: + +**Important** +*This version of `Human` includes `TensorFlow/JS (TFJS) 2.6.0` library which can be accessed via `human.tf`* +*You should not manually load another instance of `tfjs`, but if you do, be aware of possible version conflicts* + +### 1. IIFE script + +This is simplest way for usage within Browser +Simply download `dist/human.js`, include it in your `HTML` file & it's ready to use. + +```html + - + - +
+
diff --git a/demo/index.js b/demo/index.js deleted file mode 100644 index bc375e54..00000000 --- a/demo/index.js +++ /dev/null @@ -1,127 +0,0 @@ -/* eslint-disable no-return-assign */ -/* global tf, human, QuickSettings */ - -let paused = false; -let video; -let canvas; -let ctx; - -const config = { - face: { - enabled: true, - detector: { maxFaces: 10, skipFrames: 5, minConfidence: 0.8, iouThreshold: 0.3, scoreThreshold: 0.75 }, - mesh: { enabled: true }, - iris: { enabled: true }, - age: { enabled: false, skipFrames: 5 }, - gender: { enabled: false }, - }, - body: { enabled: false, maxDetections: 5, scoreThreshold: 0.75, nmsRadius: 20 }, - hand: { enabled: false, skipFrames: 5, minConfidence: 0.8, iouThreshold: 0.3, scoreThreshold: 0.75 }, -}; - -async function drawFace(faces) { - for (const face of faces) { - ctx.drawImage(video, 0, 0, video.width, video.height, 0, 0, canvas.width, canvas.height); - ctx.beginPath(); - ctx.rect(face.box[0], face.box[1], face.box[2], face.box[3]); - ctx.fillText(`face ${face.gender || ''} ${face.age || ''} ${face.iris ? 'iris: ' + face.iris : ''}`, face.box[0] + 2, face.box[1] + 16, face.box[2]); - ctx.stroke(); - if (face.mesh) { - for (const point of face.mesh) { - ctx.fillStyle = `rgba(${127.5 + (2 * point[2])}, ${127.5 - (2 * point[2])}, 255, 0.5)`; - ctx.beginPath(); - ctx.arc(point[0], point[1], 1 /* radius */, 0, 2 * Math.PI); - ctx.fill(); - } - } - } -} - -async function drawBody(people) { - // -} - -async function drawHand(hands) { - // -} - -async function runHumanDetect() { - const result = await human.detect(video, config); - drawFace(result.face); - drawBody(result.body); - drawHand(result.hand); - if (!paused) requestAnimationFrame(runHumanDetect); -} - -function setupGUI() { - const settings = QuickSettings.create(10, 10, 'Settings', document.getElementById('main')); - settings.addBoolean('Pause', paused, (val) => { paused = val; runHumanDetect(); }); - settings.addBoolean('Face Detect', config.face.enabled, (val) => config.face.enabled = val); - settings.addBoolean('Face Mesh', config.face.mesh.enabled, (val) => config.face.mesh.enabled = val); - settings.addBoolean('Face Iris', config.face.iris.enabled, (val) => config.face.iris.enabled = val); - settings.addBoolean('Face Age', config.face.age.enabled, (val) => config.face.age.enabled = val); - settings.addBoolean('Face Gender', config.face.gender.enabled, (val) => config.face.gender.enabled = val); - settings.addBoolean('Body Pose', config.body.enabled, (val) => config.body.enabled = val); - settings.addBoolean('Hand Pose', config.hand.enabled, (val) => config.hand.enabled = val); - settings.addRange('Max Objects', 1, 20, 5, 1, (val) => { - config.face.detector.maxFaces = parseInt(val); - config.body.maxDetections = parseInt(val); - }); - settings.addRange('Skip Frames', 1, 20, config.face.detector.skipFrames, 1, (val) => { - config.face.detector.skipFrames = parseInt(val); - config.face.age.skipFrames = parseInt(val); - config.hand.skipFrames = parseInt(val); - }); - settings.addRange('Min Confidence', 0.1, 1.0, config.face.detector.minConfidence, 0.05, (val) => { - config.face.detector.minConfidence = parseFloat(val); - config.hand.minConfidence = parseFloat(val); - }); - settings.addRange('Score Threshold', 0.1, 1.0, config.face.detector.scoreThreshold, 0.05, (val) => { - config.face.detector.scoreThreshold = parseFloat(val); - config.hand.scoreThreshold = parseFloat(val); - config.body.scoreThreshold = parseFloat(val); - }); - settings.addRange('IOU Threshold', 0.1, 1.0, config.face.detector.iouThreshold, 0.05, (val) => { - config.face.detector.iouThreshold = parseFloat(val); - config.hand.iouThreshold = parseFloat(val); - }); -} - -async function setupCanvas() { - canvas = document.getElementById('canvas'); - canvas.width = video.width; - canvas.height = video.height; - ctx = canvas.getContext('2d'); - ctx.fillStyle = 'lightblue'; - ctx.strokeStyle = 'lightblue'; - ctx.lineWidth = 1; - ctx.font = 'small-caps 1rem "Segoe UI"'; -} - -async function setupCamera() { - video = document.getElementById('video'); - const stream = await navigator.mediaDevices.getUserMedia({ - audio: false, - video: { facingMode: 'user', width: window.innerWidth, height: window.innerHeight }, - }); - video.srcObject = stream; - return new Promise((resolve) => { - video.onloadedmetadata = () => { - resolve(video); - video.width = video.videoWidth; - video.height = video.videoHeight; - video.play(); - }; - }); -} - -async function main() { - await tf.setBackend('webgl'); - await tf.ready(); - await setupGUI(); - await setupCamera(); - await setupCanvas(); - runHumanDetect(); -} - -window.onload = main; diff --git a/package.json b/package.json index b5e5f1be..4aad548c 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "scripts": { "build": "rimraf dist/ && npm run build-esm && npm run build-iife", - "build-esm": "esbuild --bundle --platform=browser --sourcemap --target=esnext --format=esm --external:@tensorflow --outfile=dist/human.esm.js src/index.js", + "build-esm": "esbuild --bundle --platform=browser --sourcemap --target=esnext --format=esm --minify --outfile=dist/human.esm.js src/index.js", "build-iife": "esbuild --bundle --platform=browser --sourcemap --target=esnext --format=iife --minify --global-name=human --outfile=dist/human.js src/index.js" }, "keywords": [ diff --git a/src/image.js b/src/image.js deleted file mode 100644 index e0a485c4..00000000 --- a/src/image.js +++ /dev/null @@ -1,127 +0,0 @@ -const defaultFont = 'small-caps 1rem "Segoe UI"'; - -function clear(canvas) { - if (canvas) canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); -} - -function crop(image, x, y, width, height, { color = 'white', title = null, font = null }) { - const canvas = new OffscreenCanvas(width, height); - const ctx = canvas.getContext('2d'); - ctx.drawImage(image, x, y, width, height, 0, 0, canvas.width, canvas.height); - ctx.fillStyle = color; - ctx.font = font || defaultFont; - if (title) ctx.fillText(title, 2, 16, canvas.width - 4); - return canvas; -} - -function point({ canvas = null, x = 0, y = 0, color = 'white', radius = 2, title = null, font = null }) { - if (!canvas) return; - const ctx = canvas.getContext('2d'); - ctx.fillStyle = color; - ctx.beginPath(); - ctx.arc(x, y, radius, 0, 2 * Math.PI); - ctx.fill(); - ctx.font = font || defaultFont; - if (title) ctx.fillText(title, x + 10, y + 4); -} - -function rect({ canvas = null, x = 0, y = 0, width = 0, height = 0, radius = 8, lineWidth = 2, color = 'white', title = null, font = null }) { - if (!canvas) return; - const ctx = canvas.getContext('2d'); - ctx.lineWidth = lineWidth; - ctx.beginPath(); - ctx.moveTo(x + radius, y); - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); - ctx.strokeStyle = color; - ctx.stroke(); - ctx.lineWidth = 1; - ctx.fillStyle = color; - ctx.font = font || defaultFont; - if (title) ctx.fillText(title, x + 4, y + 16); -} - -function line({ points = [], canvas = null, lineWidth = 2, color = 'white', title = null, font = null }) { - if (!canvas) return; - if (points.length < 2) return; - const ctx = canvas.getContext('2d'); - ctx.lineWidth = lineWidth; - ctx.beginPath(); - ctx.moveTo(points[0][0], points[0][1]); - for (const pt of points) ctx.lineTo(pt[0], pt[1]); - ctx.strokeStyle = color; - ctx.fillStyle = color; - ctx.stroke(); - ctx.lineWidth = 1; - ctx.font = font || defaultFont; - if (title) ctx.fillText(title, points[0][0] + 4, points[0][1] + 16); -} - -function spline({ points = [], canvas = null, tension = 0.5, lineWidth = 2, color = 'white', title = null, font = null }) { - if (!canvas) return; - if (points.length < 2) return; - const va = (arr, i, j) => [arr[2 * j] - arr[2 * i], arr[2 * j + 1] - arr[2 * i + 1]]; - const distance = (arr, i, j) => Math.sqrt(((arr[2 * i] - arr[2 * j]) ** 2) + ((arr[2 * i + 1] - arr[2 * j + 1]) ** 2)); - // eslint-disable-next-line no-unused-vars - function ctlpts(x1, y1, x2, y2, x3, y3) { - // eslint-disable-next-line prefer-rest-params - const v = va(arguments, 0, 2); - // eslint-disable-next-line prefer-rest-params - const d01 = distance(arguments, 0, 1); - // eslint-disable-next-line prefer-rest-params - const d12 = distance(arguments, 1, 2); - const d012 = d01 + d12; - return [ - x2 - v[0] * tension * d01 / d012, y2 - v[1] * tension * d01 / d012, - x2 + v[0] * tension * d12 / d012, y2 + v[1] * tension * d12 / d012, - ]; - } - const pts = []; - for (const pt of points) { - pts.push(pt[0]); - pts.push(pt[1]); - } - let cps = []; - for (let i = 0; i < pts.length - 2; i += 1) { - cps = cps.concat(ctlpts(pts[2 * i + 0], pts[2 * i + 1], pts[2 * i + 2], pts[2 * i + 3], pts[2 * i + 4], pts[2 * i + 5])); - } - const ctx = canvas.getContext('2d'); - ctx.lineWidth = lineWidth; - ctx.strokeStyle = color; - ctx.fillStyle = color; - if (points.length === 2) { - ctx.beginPath(); - ctx.moveTo(pts[0], pts[1]); - ctx.lineTo(pts[2], pts[3]); - } else { - ctx.beginPath(); - ctx.moveTo(pts[0], pts[1]); - // first segment is a quadratic - ctx.quadraticCurveTo(cps[0], cps[1], pts[2], pts[3]); - // for all middle points, connect with bezier - let i; - for (i = 2; i < ((pts.length / 2) - 1); i += 1) { - ctx.bezierCurveTo(cps[(2 * (i - 1) - 1) * 2], cps[(2 * (i - 1) - 1) * 2 + 1], cps[(2 * (i - 1)) * 2], cps[(2 * (i - 1)) * 2 + 1], pts[i * 2], pts[i * 2 + 1]); - } - // last segment is a quadratic - ctx.quadraticCurveTo(cps[(2 * (i - 1) - 1) * 2], cps[(2 * (i - 1) - 1) * 2 + 1], pts[i * 2], pts[i * 2 + 1]); - } - ctx.stroke(); - ctx.lineWidth = 1; - ctx.font = font || defaultFont; - if (title) ctx.fillText(title, points[0][0] + 4, points[0][1] + 16); -} - -exports.crop = crop; -exports.rect = rect; -exports.point = point; -exports.line = line; -exports.spline = spline; -exports.clear = clear; diff --git a/src/index.js b/src/index.js index 5b471c05..31820a29 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,8 @@ +const tf = require('@tensorflow/tfjs'); const facemesh = require('./facemesh/index.js'); const ssrnet = require('./ssrnet/index.js'); const posenet = require('./posenet/index.js'); const handpose = require('./handpose/index.js'); -// const image = require('./image.js'); -// const triangulation = require('./triangulation.js').default; const defaults = require('./config.js').default; const models = { @@ -83,3 +82,4 @@ exports.facemesh = facemesh; exports.ssrnet = ssrnet; exports.posenet = posenet; exports.handpose = handpose; +exports.tf = tf;