diff --git a/demo/node-canvas.js b/demo/node-canvas.js index 0b8b179..d5ffb91 100644 --- a/demo/node-canvas.js +++ b/demo/node-canvas.js @@ -22,6 +22,9 @@ async function image(input) { const c = canvas.createCanvas(img.width, img.height); const ctx = c.getContext('2d'); ctx.drawImage(img, 0, 0, img.width, img.height); + // const out = fs.createWriteStream('test.jpg'); + // const stream = c.createJPEGStream({ quality: 0.6, progressive: true, chromaSubsampling: true }); + // stream.pipe(out); return c; } @@ -80,7 +83,7 @@ async function main() { log.info('Processed', numImages, 'images in', Math.trunc(parseInt(t1 - t0) / 1000 / 1000), 'ms'); } else { const param = process.argv[2]; - if (fs.existsSync(param)) { + if (fs.existsSync(param) || param.startsWith('http:') || param.startsWith('https:')) { const c = await image(param); const result = await detect(c); log.data('Image:', param, 'Detected faces:', result.length); diff --git a/demo/node.js b/demo/node.js index af452da..28dd690 100644 --- a/demo/node.js +++ b/demo/node.js @@ -6,6 +6,8 @@ const path = require('path'); // eslint-disable-next-line import/no-extraneous-dependencies, node/no-unpublished-require const log = require('@vladmandic/pilogger'); // eslint-disable-next-line import/no-extraneous-dependencies, node/no-unpublished-require +const fetch = require('node-fetch').default; +// eslint-disable-next-line import/no-extraneous-dependencies, node/no-unpublished-require const tf = require('@tensorflow/tfjs-node'); const faceapi = require('../dist/face-api.node.js'); // this is equivalent to '@vladmandic/faceapi' @@ -15,14 +17,35 @@ const minConfidence = 0.15; const maxResults = 5; let optionsSSDMobileNet; -async function image(img) { - const buffer = fs.readFileSync(img); - const decoded = tf.node.decodeImage(buffer); - const casted = decoded.toFloat(); - const result = casted.expandDims(0); - decoded.dispose(); - casted.dispose(); - return result; +async function image(input) { + // read input image file and create tensor to be used for processing + let buffer; + log.info('Loading image:', input); + if (input.startsWith('http:') || input.startsWith('https:')) { + const res = await fetch(input); + if (res && res.ok) buffer = await res.buffer(); + else log.error('Invalid image URL:', input, res.status, res.statusText, res.headers.get('content-type')); + } else { + buffer = fs.readFileSync(input); + } + + // decode image using tfjs-node so we don't need external depenencies + // can also be done using canvas.js or some other 3rd party image library + if (!buffer) return {}; + const tensor = tf.tidy(() => { + const decode = faceapi.tf.node.decodeImage(buffer, 3); + let expand; + if (decode.shape[2] === 4) { // input is in rgba format, need to convert to rgb + const channels = faceapi.tf.split(decode, 4, 2); // tf.split(tensor, 4, 2); // split rgba to channels + const rgb = faceapi.tf.stack([channels[0], channels[1], channels[2]], 2); // stack channels back to rgb and ignore alpha + expand = faceapi.tf.reshape(rgb, [1, decode.shape[0], decode.shape[1], 3]); // move extra dim from the end of tensor and use it as batch number instead + } else { + expand = faceapi.tf.expandDims(decode, 0); + } + const cast = faceapi.tf.cast(expand, 'float32'); + return cast; + }); + return tensor; } async function detect(tensor) { @@ -97,7 +120,7 @@ async function main() { log.info('Processed', dir.length, 'images in', Math.trunc(parseInt(t1 - t0) / 1000 / 1000), 'ms'); } else { const param = process.argv[2]; - if (fs.existsSync(param)) { + if (fs.existsSync(param) || param.startsWith('http:') || param.startsWith('https:')) { const tensor = await image(param); const result = await detect(tensor); // const result = await detectPromise(null); diff --git a/package.json b/package.json index 71f179b..8779978 100644 --- a/package.json +++ b/package.json @@ -53,18 +53,19 @@ "canvas": "^2.7.0", "chokidar": "^3.5.1", "dayjs": "^1.10.4", - "esbuild": "^0.11.12", - "eslint": "^7.24.0", + "esbuild": "^0.11.14", + "eslint": "^7.25.0", "eslint-config-airbnb-base": "^14.2.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-json": "^2.1.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0", + "node-fetch": "^2.6.1", "rimraf": "^3.0.2", "seedrandom": "^3.0.5", "simple-git": "^2.38.0", "tslib": "^2.2.0", - "typedoc": "^0.20.35", + "typedoc": "^0.20.36", "typescript": "^4.2.4" } }