add node-canvas demo
parent
9ccaf781ab
commit
2ad4fc24db
|
@ -69,10 +69,17 @@ Example can be accessed directly using Git pages using URL:
|
|||
|
||||
### NodeJS
|
||||
|
||||
Two NodeJS examples are:
|
||||
Three NodeJS examples are:
|
||||
|
||||
- `/demo/node-singleprocess.js`:
|
||||
Regular usage of `FaceAPI` from `NodeJS`
|
||||
- `/demo/node-singleprocess.js`:
|
||||
Regular usage of `FaceAPI` from `NodeJS`
|
||||
Using `TFJS` native methods to load images
|
||||
- `/demo/node-canvas.js`:
|
||||
Regular usage of `FaceAPI` from `NodeJS`
|
||||
Using external `canvas` module to load images
|
||||
Which also allows for image drawing and saving inside `NodeJS`
|
||||
- `/demo/node-multiprocess.js`:
|
||||
Multiprocessing showcase that uses pool of worker processes
|
||||
(`node-multiprocess-worker.js`)
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
// @ts-nocheck
|
||||
|
||||
const fs = require('fs');
|
||||
const process = require('process');
|
||||
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 tf = require('@tensorflow/tfjs-node');
|
||||
const faceapi = require('../dist/face-api.node.js'); // this is equivalent to '@vladmandic/faceapi'
|
||||
|
||||
const modelPathRoot = '../model';
|
||||
const imgPathRoot = './demo'; // modify to include your sample images
|
||||
const minScore = 0.1;
|
||||
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 detect(tensor) {
|
||||
const result = await faceapi
|
||||
.detectAllFaces(tensor, optionsSSDMobileNet)
|
||||
.withFaceLandmarks()
|
||||
.withFaceExpressions()
|
||||
.withFaceDescriptors()
|
||||
.withAgeAndGender();
|
||||
return result;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
log.header();
|
||||
log.info('FaceAPI single-process test');
|
||||
|
||||
await faceapi.tf.setBackend('tensorflow');
|
||||
await faceapi.tf.enableProdMode();
|
||||
await faceapi.tf.ENV.set('DEBUG', false);
|
||||
await faceapi.tf.ready();
|
||||
|
||||
log.state(`Version: TensorFlow/JS ${faceapi.tf?.version_core} FaceAPI ${faceapi.version.faceapi} Backend: ${faceapi.tf?.getBackend()}`);
|
||||
|
||||
log.info('Loading FaceAPI models');
|
||||
const modelPath = path.join(__dirname, modelPathRoot);
|
||||
await faceapi.nets.ssdMobilenetv1.loadFromDisk(modelPath);
|
||||
await faceapi.nets.ageGenderNet.loadFromDisk(modelPath);
|
||||
await faceapi.nets.faceLandmark68Net.loadFromDisk(modelPath);
|
||||
await faceapi.nets.faceRecognitionNet.loadFromDisk(modelPath);
|
||||
await faceapi.nets.faceExpressionNet.loadFromDisk(modelPath);
|
||||
optionsSSDMobileNet = new faceapi.SsdMobilenetv1Options({ minConfidence: minScore, maxResults });
|
||||
|
||||
if (process.argv.length !== 3) {
|
||||
const t0 = process.hrtime.bigint();
|
||||
const dir = fs.readdirSync(imgPathRoot);
|
||||
for (const img of dir) {
|
||||
if (!img.toLocaleLowerCase().endsWith('.jpg')) continue;
|
||||
const tensor = await image(path.join(imgPathRoot, img));
|
||||
const result = await detect(tensor);
|
||||
log.data('Image:', img, 'Detected faces:', result.length);
|
||||
for (const i of result) {
|
||||
log.data('Gender:', Math.round(100 * i.genderProbability), 'probability', i.gender, 'with age', Math.round(10 * i.age) / 10);
|
||||
}
|
||||
tensor.dispose();
|
||||
}
|
||||
const t1 = process.hrtime.bigint();
|
||||
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)) {
|
||||
const tensor = await image(param);
|
||||
const result = await detect(tensor);
|
||||
log.data('Image:', param, 'Detected faces:', result.length);
|
||||
for (const i of result) {
|
||||
log.data('Gender:', Math.round(100 * i.genderProbability), 'probability', i.gender, 'with age', Math.round(10 * i.age) / 10);
|
||||
}
|
||||
tensor.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -5,8 +5,10 @@ const process = require('process');
|
|||
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
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies, node/no-unpublished-require, no-unused-vars
|
||||
const tf = require('@tensorflow/tfjs-node');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies, node/no-unpublished-require
|
||||
const canvas = require('canvas');
|
||||
const faceapi = require('../dist/face-api.node.js'); // this is equivalent to '@vladmandic/faceapi'
|
||||
|
||||
const modelPathRoot = '../model';
|
||||
|
@ -15,14 +17,12 @@ const minScore = 0.1;
|
|||
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) {
|
||||
const img = canvas.loadImage(input);
|
||||
const c = canvas.createCanvas(img.width, img.height);
|
||||
const ctx = c.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height);
|
||||
return c;
|
||||
}
|
||||
|
||||
async function detect(tensor) {
|
||||
|
@ -39,6 +39,8 @@ async function main() {
|
|||
log.header();
|
||||
log.info('FaceAPI single-process test');
|
||||
|
||||
faceapi.env.monkeyPatch({ Canvas: canvas.Canvas, Image: canvas.Image, ImageData: canvas.ImageData });
|
||||
|
||||
await faceapi.tf.setBackend('tensorflow');
|
||||
await faceapi.tf.enableProdMode();
|
||||
await faceapi.tf.ENV.set('DEBUG', false);
|
||||
|
@ -63,6 +65,9 @@ async function main() {
|
|||
const tensor = await image(path.join(imgPathRoot, img));
|
||||
const result = await detect(tensor);
|
||||
log.data('Image:', img, 'Detected faces:', result.length);
|
||||
for (const i of result) {
|
||||
log.data('Gender:', Math.round(100 * i.genderProbability), 'probability', i.gender, 'with age', Math.round(10 * i.age) / 10);
|
||||
}
|
||||
tensor.dispose();
|
||||
}
|
||||
const t1 = process.hrtime.bigint();
|
||||
|
@ -74,7 +79,7 @@ async function main() {
|
|||
const result = await detect(tensor);
|
||||
log.data('Image:', param, 'Detected faces:', result.length);
|
||||
for (const i of result) {
|
||||
log.data('Gender:', i.genderProbability, i.gender, 'Age:', i.age);
|
||||
log.data('Gender:', Math.round(100 * i.genderProbability), 'probability', i.gender, 'with age', Math.round(10 * i.age) / 10);
|
||||
}
|
||||
tensor.dispose();
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -44,12 +44,13 @@
|
|||
"@tensorflow/tfjs-node": "^3.3.0",
|
||||
"@tensorflow/tfjs-node-gpu": "^3.3.0",
|
||||
"@types/node": "^14.14.35",
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.19.0",
|
||||
"@typescript-eslint/parser": "^4.19.0",
|
||||
"@vladmandic/pilogger": "^0.2.15",
|
||||
"canvas": "^2.7.0",
|
||||
"chokidar": "^3.5.1",
|
||||
"dayjs": "^1.10.4",
|
||||
"esbuild": "^0.9.5",
|
||||
"esbuild": "^0.9.6",
|
||||
"eslint": "^7.22.0",
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
|
@ -60,7 +61,7 @@
|
|||
"seedrandom": "^3.0.5",
|
||||
"simple-git": "^2.37.0",
|
||||
"tslib": "^2.1.0",
|
||||
"typedoc": "^0.20.32",
|
||||
"typedoc": "^0.20.33",
|
||||
"typescript": "^4.2.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ export function pointwiseConvLayer(x: tf.Tensor4D, params: PointwiseConvParams,
|
|||
let out = tf.conv2d(x, params.filters, strides, 'same');
|
||||
/*
|
||||
if (x.shape[1] === 512 && x.shape[3] === 3) {
|
||||
console.log('Input:', x.shape, x.size); // input does not change (checked values)
|
||||
console.log('Filter:', params.filters.shape, params.filters.size); // params do not change (checked values)
|
||||
console.log('Input:', x.shape, x.size, 'sum:', x.reshape([786432]).sum().dataSync()[0]); // input does not change (checked values)
|
||||
console.log('Filter:', params.filters.shape, params.filters.size, 'sum:', params.filters.reshape([864]).sum().dataSync()[0]); // params do not change (checked values)
|
||||
console.log('Strides', strides);
|
||||
console.log('Conv2d Output:', out.shape, out.size, out.dataSync()[0]); // output has different values!
|
||||
console.log('Sum of all Conv2D values:', tf.reshape(out, [2097152]).sum().dataSync()[0]); // silly sum just to see how much results diverged
|
||||
console.log('Conv2d 1st 5 values:', out.shape, out.size, out.dataSync().slice(0, 5)); // output has different values!
|
||||
console.log('Conv2D sum of all values:', tf.reshape(out, [2097152]).sum().dataSync()[0]); // silly sum just to see how much results diverged
|
||||
}
|
||||
*/
|
||||
out = tf.add(out, params.batch_norm_offset);
|
||||
|
|
Loading…
Reference in New Issue