node-red-contrib-facial-recognition
Version:
Provides a node-red node for Facial Detection & Facial Recognition.
1,026 lines (904 loc) • 38.7 kB
JavaScript
//2020 David L Burrows
//Contact me @ https://github.com/meeki007
//or meeki007@gmail.com
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//Node.js LTS ✔ Node v12.19.0 Npm 6.14.8
//Node-RED core ✔ 1.2.2
////////////////////////////////////////////////////////////////////
//things todo
//put timer on the whole thing and output it in payload
//add status msg for everything
//build a html page for the image to display the detections
//bring in other nodes to do lifting as well and document that too.
module.exports = function(RED)
{
///////////////////////////////////
// Import Required(s) by this node
//////////////////////////////////
//let tf = require('tf');
let fs = require('fs'); //Node.js fs module see https://nodejs.dev/learn/the-nodejs-fs-module
let path = require('path'); //Node.js path module see https://nodejs.dev/learn/the-nodejs-path-module
let os = require('os'); //Node.js os module see https://nodejs.dev/learn/the-nodejs-os-module
var faceapi; //value becomes user selection of: require('@vladmandic/face-api/dist/face-api.node.js'); or
var tf; //value becomes user selection of: require('@tensorflow/tfjs-node'); or require('@tensorflow/tfjs-node-gpu');
///////////////////////////////////
//Global stuff used by entire node
///////////////////////////////////
// check if a module is installed and working, if not return error code;
function module_check (module_name)
{
try
{
require(module_name);
return true;
}
catch (error)
{
return error.code;
//return error;
}
}
//////////////////////////////////////////////////////////////
// Stuff to do / load before loading/constructing this node //
//////////////////////////////////////////////////////////////
// check if module @vladmandic/face-api/dist/face-api.node.js is installed and working;
let vladmandic_faceapi_node_module_check = module_check('@vladmandic/face-api/dist/face-api.node.js');
// check if module @vladmandic/face-api/dist/face-api.node-gpu.js is installed and working;
let vladmandic_faceapi_node_gpu_module_check = module_check('@vladmandic/face-api/dist/face-api.node-gpu.js');
// check if module @tensorflow/tfjs-node [GPU] is installed and working;
let tfjs_node_gpu_module_check = module_check('@tensorflow/tfjs-node-gpu');
// check if module @tensorflow/tfjs-node [CPU] is installed and working;
let tfjs_node_cpu_module_check = module_check('@tensorflow/tfjs-node');
//users system information =
let system =
{
os:
{
arch : os.arch(),
platform : os.platform(),
release : os.release(),
type : os.type()
}
};
////////////////////////
// construct the node //
////////////////////////
function facialrecognitionNode(config)
{
RED.nodes.createNode(this,config);
//Clear user notices Function, used for timmer after deploy
var status_clear = function()
{
//clear status icon
node.status({});
};
// config for this nodes html file
this.image = config.image||'payload';
this.settings = config.settings||'settings';
this.bindings = config.bindings || 'CPU';
this.FaceDetector = config.FaceDetector || 'SsdMobilenetv1';
this.FaceDetector_SsdMobilenetv1_maxResults = Number(config.FaceDetector_SsdMobilenetv1_maxResults || 3);
this.FaceDetector_SsdMobilenetv1_minConfidence = Number(config.FaceDetector_SsdMobilenetv1_minConfidence || 0.5);
this.FaceDetector_tinyFaceDetector_inputSize = Number(config.FaceDetector_tinyFaceDetector_inputSize || 416);
this.FaceDetector_tinyFaceDetector_scoreThreshold = Number(config.FaceDetector_tinyFaceDetector_scoreThreshold || 0.5);
this.Tasks = config.Tasks || 'detectAllFaces';
this.FaceLandmarks = config.FaceLandmarks || false;
this.FaceExpressions = config.FaceExpressions || false;
this.AgeAndGender = config.AgeAndGender || false;
this.FaceDescriptors = config.FaceDescriptors || false;
this.Face_Recognition = config.Face_Recognition || 'Face_Recognition_disabled';
this.Face_Recognition_enabled_path = config.Face_Recognition_enabled_path || 'FullPathToLabeledFaces';
this.Face_Recognition_distanceThreshold = Number(config.Face_Recognition_distanceThreshold || 0.7);
//require modules based on user input to node image bindings
//warn user if bindings selection is not valid for lack of module
var found_tfjs_CPU_GPU_module;
var found_vladmandic_faceapi_module;
if ( this.bindings === 'CPU' && tfjs_node_cpu_module_check === true && vladmandic_faceapi_node_module_check === true )
{
found_tfjs_CPU_GPU_module = true;
tf = require('@tensorflow/tfjs-node');
faceapi = require('@vladmandic/face-api/dist/face-api.node.js'); //JavaScript face recognition API for nodejs see https://www.npmjs.com/package/@vladmandic/face-api
}
else if ( this.bindings === 'CPU' && tfjs_node_cpu_module_check !== true || vladmandic_faceapi_node_module_check !== true )
{
if ( vladmandic_faceapi_node_module_check !== true )
{
found_vladmandic_faceapi_module = "Error: " + vladmandic_faceapi_node_module_check + " - Unable to use @vladmandic/face-api/dist/face-api.node.js; CPU binding. Check your loggs when installing. See this node's documentation and Make sure that module @tensorflow/tfjs-node is properly working & installed under your Node-RED user directory, typically ~/.node-red";
this.warn(found_vladmandic_faceapi_module);
}
if ( tfjs_node_cpu_module_check !== true )
{
found_tfjs_CPU_GPU_module = "Error: " + tfjs_node_cpu_module_check + " - Unable to use @tensorflow/tfjs-node; CPU binding. Check your loggs when installing. See this node's documentation and Make sure that module @tensorflow/tfjs-node is properly working & installed under your Node-RED user directory, typically ~/.node-red";
this.warn(found_tfjs_CPU_GPU_module);
}
}
else if ( this.bindings === 'GPU' && tfjs_node_gpu_module_check === true && vladmandic_faceapi_node_gpu_module_check === true )
{
found_tfjs_CPU_GPU_module = true;
tf = require('@tensorflow/tfjs-node-gpu');
faceapi = require('@vladmandic/face-api/dist/face-api.node-gpu.js'); //JavaScript face recognition API for nodejs see https://www.npmjs.com/package/@vladmandic/face-api
}
else if ( this.bindings === 'GPU' && tfjs_node_gpu_module_check !== true || vladmandic_faceapi_node_gpu_module_check !== true )
{
if ( vladmandic_faceapi_node_gpu_module_check !== true )
{
found_vladmandic_faceapi_module = "Error: " + vladmandic_faceapi_node_module_check + " - Unable to use @vladmandic/face-api/dist/face-api.node-gpu.js; CPU binding. Check your loggs when installing. See this node's documentation and Make sure that module @tensorflow/tfjs-node is properly working & installed under your Node-RED user directory, typically ~/.node-red";
this.warn(found_vladmandic_faceapi_module);
}
if ( tfjs_node_gpu_module_check !== true )
{
found_tfjs_CPU_GPU_module = "Error: " + tfjs_node_gpu_module_check + " - Unable to use @tensorflow/tfjs-node-gpu; GPU binding. Check your loggs when installing. See this node's documentation and Make sure that module @tensorflow/tfjs-node-gpu is properly working & installed under your Node-RED user directory, typically ~/.node-red";
this.warn(found_tfjs_CPU_GPU_module);
}
}
//clear status icon every new deploy
this.status({});
this.status(
{
fill: 'yellow',
shape: 'dot',
text: "Initialize tfjs"
});
//setup tfjs
//tfjs_backend
var tfjs_backend; //error check of tfjs_backend
Promise.resolve
(
faceapi.tf.setBackend('tensorflow')
)
.then( tfjs_backend = true )
.catch(error =>
{
tfjs_backend = ("Could not set tfjs backend" + error),
this.warn(tfjs_backend),
this.status(
{
fill: 'red',
shape: 'dot',
text: "detected error"
});
});
//enableProdMode
var tfjs_ProdMode; //error check of tfjs_ProdMode
Promise.resolve
(
faceapi.tf.enableProdMode()
)
.then( tfjs_ProdMode = true )
.catch(error =>
{
tfjs_ProdMode = ("Could not set tfjs ProdMode" + error),
this.warn(tfjs_ProdMode),
this.status(
{
fill: 'red',
shape: 'dot',
text: "detected error"
});
});
//setDEBUG
var tfjs_setDEBUG; //error check of tfjs_setDEBUG
Promise.resolve
(
faceapi.tf.ENV.set('DEBUG', false)
)
.then( tfjs_setDEBUG = true )
.catch(error =>
{
tfjs_setDEBUG = ("Could not set tfjs DEBUG" + error),
this.warn(tfjs_setDEBUG),
this.status(
{
fill: 'red',
shape: 'dot',
text: "detected error"
});
});
//is tfjs_ready
var tfjs_ready; //error check of tfjs_ready
Promise.resolve
(
faceapi.tf.ready()
)
.then( tfjs_ready = true )
.catch(error =>
{
tfjs_ready = ("tfjs is not ready" + error),
this.warn(tfjs_ready),
this.status(
{
fill: 'red',
shape: 'dot',
text: "detected error"
});
});
//get the tensorflow core version the user is using
var faceapi_tf_version_core = faceapi.tf.version_core;
//clear status icon
//this.status({});
this.status(
{
fill: 'yellow',
shape: 'dot',
text: "loading the modles"
});
//set path to models
const modelPath = `${__dirname}/models`;
//load models before sending msg to this node
var loadthemodels_no_error; //error check of loadthemodels_no_error
Promise.all
([
faceapi.nets.ageGenderNet.loadFromDisk(modelPath),
faceapi.nets.faceExpressionNet.loadFromDisk(modelPath),
faceapi.nets.faceLandmark68Net.loadFromDisk(modelPath),
faceapi.nets.faceLandmark68TinyNet.loadFromDisk(modelPath),
faceapi.nets.faceRecognitionNet.loadFromDisk(modelPath),
faceapi.nets.ssdMobilenetv1.loadFromDisk(modelPath),
faceapi.nets.tinyFaceDetector.loadFromDisk(modelPath)
])
.then(loadthemodels_no_error = true)
.catch(error =>
{
loadthemodels_no_error = ("A face-api.js model(s) did not load. "+ error),
this.warn(loadthemodels_no_error),
this.status(
{
fill: 'red',
shape: 'dot',
text: "detected error"
});
});
// Access the node's context object
var nodeContext = this.context();
//on a deploy Set context so FaceMatcher will initialize again on next img sent
nodeContext.set('FaceMatcherInitialized',false);
//clear status icon
this.status({});
//////////////////////////////////////////////
// Do Stuff when a msg is sent to this node //
//////////////////////////////////////////////
var node = this;
this.on('input', async function(msg, send, done)
{
//process.on('unhandledRejection', function(error) {
// notify_user_errors(error);
//});
//Start timmer
const start = Date.now();
// For maximum backwards compatibility, check that send exists.
// If this node is installed in Node-RED 0.x, it will need to
// fallback to using `node.send`
send = send || function() { node.send.apply(node,arguments); };
//function to work with buffered image in from msg payload
async function image(buffer)
{
try {
const decoded = tf.node.decodeImage(buffer);
const casted = decoded.toFloat();
const result = casted.expandDims(0);
decoded.dispose();
casted.dispose();
return result;
} catch (error) {
notify_user_errors(error);
}
}
async function listDirectories(rootPath)
{
const fileNames = await fs.promises.readdir(rootPath);
if ( fileNames.indexOf(".DS_Store") == 0 ) //for MacOS - if fileNames contains .DS_Store
{
fileNames.shift(); // get rid of it
}
const filePaths = fileNames.map(fileName => path.join(rootPath, fileName));
const filePathsAndIsDirectoryFlagsPromises = filePaths.map(async filePath => ({path: filePath, isDirectory: (await fs.promises.stat(filePath)).isDirectory()}));
const filePathsAndIsDirectoryFlags = await Promise.all(filePathsAndIsDirectoryFlagsPromises);
return filePathsAndIsDirectoryFlags.filter(filePathAndIsDirectoryFlag => filePathAndIsDirectoryFlag.isDirectory)
.map(filePathAndIsDirectoryFlag => filePathAndIsDirectoryFlag.path);
}
async function listFiles(rootPath)
{
const fileNames = await fs.promises.readdir(rootPath);
if ( fileNames.indexOf(".DS_Store") == 0 ) //for MacOS - if fileNames contains .DS_Store
{
fileNames.shift(); // get rid of it
}
const filePaths = fileNames.map(fileName => path.join(rootPath, fileName));
const filePathsAndIsFileFlagsPromises = filePaths.map(async filePath => ({path: filePath, isFile: (await fs.promises.stat(filePath)).isFile()}));
const filePathsAndIsFileFlags = await Promise.all(filePathsAndIsFileFlagsPromises);
return filePathsAndIsFileFlags.filter(filePathAndIsFileFlag => filePathAndIsFileFlag.isFile)
.map(filePathAndIsFileFlag => filePathAndIsFileFlag.path);
}
function LoadLabeledImages()
{
return Promise.all(
list_dirs_in_labeled_face_folder_names_only.map(async each_dir_name => {
const image_names_in_each_dir_name = await listFiles(path.join(user_path, each_dir_name)).catch(error => {
notify_user_errors(error);
});
const length = image_names_in_each_dir_name.length
const descriptions = [];
for (let i = 0; i < length; i++)
{
const image_file_size = fs.statSync(image_names_in_each_dir_name[i]);
if ( image_file_size.size > 1024000 )
{
notify_user_errors('Face_Recognition: Image File Size too big. Greater than 1024kB. Did not load this image. Please reduce file size ' + image_names_in_each_dir_name[i]);
}
else if ( image_file_size.size > 512000 )
{
notify_user_errors('Face_Recognition: Image File a bit large. Greater than 512kB. Image was loaded but suggest reduced file size ' + image_names_in_each_dir_name[i]);
const bufferd_img = await fs.readFileSync(image_names_in_each_dir_name[i]);
const face_detect_tensor = await image(bufferd_img);
var detections;
if ( face_detect_tensor )
{
detections = await faceapi.detectSingleFace(face_detect_tensor).withFaceLandmarks().withFaceDescriptor();
//make dang sure that a face was detected and a descriptor was created.
//else a undefined value sent to the descriptions array causes new faceapi.LabeledFaceDescriptors(each_dir_name, descriptions); to fail
if ( detections )
{
if ( detections.descriptor )
{
descriptions.push(detections.descriptor);
face_detect_tensor.dispose();
}
}
else
{
notify_user_errors('Unable to create FaceDescriptor. Please replace this image with a face that can be found. Did not load this image ' + image_names_in_each_dir_name[i]);
}
}
else
{
notify_user_errors('unable to decode file ' + image_names_in_each_dir_name[i]);
}
}
else
{
const bufferd_img = await fs.readFileSync(image_names_in_each_dir_name[i]);
const face_detect_tensor = await image(bufferd_img);
var detections;
if ( face_detect_tensor )
{
detections = await faceapi.detectSingleFace(face_detect_tensor).withFaceLandmarks().withFaceDescriptor();
//make dang sure that a face was detected and a descriptor was created.
//else a undefined value sent to the descriptions array causes new faceapi.LabeledFaceDescriptors(each_dir_name, descriptions); to fail
if ( detections )
{
if ( detections.descriptor )
{
descriptions.push(detections.descriptor);
face_detect_tensor.dispose();
}
}
else
{
notify_user_errors('Unable to create FaceDescriptor. Please replace this image with a face that can be found. Did not load this image ' + image_names_in_each_dir_name[i]);
}
}
else
{
notify_user_errors('unable to decode file ' + image_names_in_each_dir_name[i]);
}
}
}
return new faceapi.LabeledFaceDescriptors(each_dir_name, descriptions);
//return image_names_in_each_dir_name;
//return length;
//return descriptions;
})
);
}
//user error function
function notify_user_errors(err)
{
if (done)
{
// Node-RED 1.0 compatible
done(err);
}
else
{
// Node-RED 0.x compatible
node.error(err, msg);
}
}
//set user img to node to var
var img = msg[node.image.valueOf()];
//get the msg.name used for for img into node
var img_name = node.image.valueOf();
///////////////// CHECKS /////////////////
//check that img to this node on user defined message image or default image msg.payload
if ( !img ) //is falsy
{
notify_user_errors("message image msg." + img_name + " is falsy! no img or img value found for msg." + img_name + " , please send this node a image. like a *.png *.gif *.jpg *.bmp");
}
if ( found_vladmandic_faceapi_module !== true )
{
notify_user_errors(found_vladmandic_faceapi_module);
}
if ( found_tfjs_CPU_GPU_module !== true )
{
notify_user_errors(found_tfjs_CPU_GPU_module);
}
if ( tfjs_backend !== true )
{
notify_user_errors(tfjs_backend);
}
if ( tfjs_ProdMode !== true )
{
notify_user_errors(tfjs_ProdMode);
}
if ( tfjs_setDEBUG !== true )
{
notify_user_errors(tfjs_setDEBUG);
}
if ( tfjs_ready !== true )
{
notify_user_errors(tfjs_ready);
}
if ( loadthemodels_no_error !== true )
{
notify_user_errors(loadthemodels_no_error);
}
///////////////// facial-recognition /////////////////
if ( img && found_tfjs_CPU_GPU_module === true && tfjs_backend === true && tfjs_ProdMode === true && tfjs_setDEBUG === true && tfjs_ready === true && loadthemodels_no_error === true )
{
this.status(
{
fill: 'blue',
shape: 'dot',
text: "working"
});
//set user img to node to var
var user_settings = msg[node.settings.valueOf()];
//get the msg.name used for for img into node
var user_settings_name = node.settings.valueOf();
//overide user settings propery for node via msg
if ( user_settings ) // is not falsy
{
if ( user_settings.FaceDetector )
{
if ( user_settings.FaceDetector.SsdMobilenetv1 )
{
this.FaceDetector = 'SsdMobilenetv1';
if ( user_settings.FaceDetector.SsdMobilenetv1.maxResults )
{
this.FaceDetector_SsdMobilenetv1_maxResults = Number(user_settings.FaceDetector.SsdMobilenetv1.maxResults);
}
if ( user_settings.FaceDetector.SsdMobilenetv1.minConfidence )
{
this.FaceDetector_SsdMobilenetv1_minConfidence = Number(user_settings.FaceDetector.SsdMobilenetv1.minConfidence);
}
}
if ( user_settings.FaceDetector.tinyFaceDetector )
{
this.FaceDetector = 'tinyFaceDetector';
if ( user_settings.FaceDetector.tinyFaceDetector.inputSize )
{
this.FaceDetector_tinyFaceDetector_inputSize = Number(user_settings.FaceDetector.tinyFaceDetector.inputSize);
}
if ( user_settings.FaceDetector.tinyFaceDetector.scoreThreshold )
{
this.FaceDetector_tinyFaceDetector_scoreThreshold = Number(user_settings.FaceDetector.tinyFaceDetector.scoreThreshold);
}
}
}
if ( user_settings.Tasks )
{
if ( user_settings.Tasks.detectAllFaces )
{
this.Tasks = 'detectAllFaces';
if ( user_settings.Tasks.detectAllFaces.withFaceLandmarks )
{
this.FaceLandmarks = user_settings.Tasks.detectAllFaces.withFaceLandmarks;
}
if ( user_settings.Tasks.detectAllFaces.withFaceExpressions )
{
this.FaceExpressions = user_settings.Tasks.detectAllFaces.withFaceExpressions;
}
if ( user_settings.Tasks.detectAllFaces.withAgeAndGender )
{
this.AgeAndGender = user_settings.Tasks.detectAllFaces.withAgeAndGender;
}
if ( user_settings.Tasks.detectAllFaces.withFaceDescriptors )
{
this.FaceDescriptors = user_settings.Tasks.detectAllFaces.withFaceDescriptors;
}
}
if ( user_settings.Tasks.detectSingleFace)
{
this.Tasks = 'detectSingleFace';
if ( user_settings.Tasks.detectSingleFace.withFaceLandmarks )
{
this.FaceLandmarks = user_settings.Tasks.detectSingleFace.withFaceLandmarks;
}
if ( user_settings.Tasks.detectSingleFace.withFaceExpressions )
{
this.FaceExpressions = user_settings.Tasks.detectSingleFace.withFaceExpressions;
}
if ( user_settings.Tasks.detectSingleFace.withAgeAndGender )
{
this.AgeAndGender = user_settings.Tasks.detectSingleFace.withAgeAndGender;
}
if ( user_settings.Tasks.detectSingleFace.withFaceDescriptors )
{
this.FaceDescriptors = user_settings.Tasks.detectSingleFace.withFaceDescriptors;
}
}
}
if ( user_settings.FaceRecognition )
{
if ( user_settings.FaceRecognition.enabled )
{
this.Face_Recognition = 'Face_Recognition_enabled';
if ( user_settings.FaceRecognition.enabled.KnownFacesPath )
{
this.Face_Recognition_enabled_path = user_settings.FaceRecognition.enabled.KnownFacesPath;
}
if ( user_settings.FaceRecognition.enabled.distanceThreshold )
{
this.Face_Recognition_distanceThreshold = user_settings.FaceRecognition.enabled.distanceThreshold;
}
//Set context so FaceMatcher will initialize again on next img sent
if ( user_settings.FaceRecognition.enabled.ReInitializeFaceMatcher === true )
{
nodeContext.set('FaceMatcherInitialized',false);
//msg.userset_labeledFaceDescriptors = user_settings.FaceRecognition.enabled.labeledFaceDescriptors;
}
//Stop FaceMatcher from initalizing and check if user sent labeledFaceDescriptors
if ( user_settings.FaceRecognition.enabled.ReInitializeFaceMatcher === false )
{
if ( user_settings.FaceRecognition.enabled.labeledFaceDescriptors )
{
nodeContext.set('labeledFaceDescriptors',user_settings.FaceRecognition.enabled.labeledFaceDescriptors);
nodeContext.set('FaceMatcherInitialized',true);
}
}
}
}
}
//format user selected FaceDetector object for output message
var user_selected_FaceDetector_object;
if ( this.FaceDetector === 'SsdMobilenetv1' )
{
user_selected_FaceDetector_object = {
SsdMobilenetv1 :
{
maxResults : this.FaceDetector_SsdMobilenetv1_maxResults,
minConfidence : this.FaceDetector_SsdMobilenetv1_minConfidence
}
};
}
if ( this.FaceDetector === 'tinyFaceDetector' )
{
user_selected_FaceDetector_object = {
tinyFaceDetector :
{
inputSize : this.FaceDetector_tinyFaceDetector_inputSize,
scoreThreshold : this.FaceDetector_tinyFaceDetector_scoreThreshold
}
};
}
//format user selected FaceMatcher object for output message
var user_selected_FaceMatcher_object;
if ( this.Face_Recognition === 'Face_Recognition_disabled' )
{
user_selected_FaceMatcher_object = 'disabled';
}
if ( this.Face_Recognition === 'Face_Recognition_enabled' )
{
if ( user_settings )
{
if ( user_settings.FaceRecognition )
{
if ( user_settings.FaceRecognition.enabled )
{
if ( user_settings.FaceRecognition.enabled.ReInitializeFaceMatcher )
{
user_selected_FaceMatcher_object = {
enabled :
{
KnownFacesPath : this.Face_Recognition_enabled_path,
distanceThreshold : this.Face_Recognition_distanceThreshold,
ReInitializeFaceMatcher : user_settings.FaceRecognition.enabled.ReInitializeFaceMatcher
}
};
}
else
{
user_selected_FaceMatcher_object = {
enabled :
{
KnownFacesPath : this.Face_Recognition_enabled_path,
distanceThreshold : this.Face_Recognition_distanceThreshold
}
};
}
}
else
{
user_selected_FaceMatcher_object = {
enabled :
{
KnownFacesPath : this.Face_Recognition_enabled_path,
distanceThreshold : this.Face_Recognition_distanceThreshold
}
};
}
}
else
{
user_selected_FaceMatcher_object = {
enabled :
{
KnownFacesPath : this.Face_Recognition_enabled_path,
distanceThreshold : this.Face_Recognition_distanceThreshold
}
};
}
}
else
{
user_selected_FaceMatcher_object = {
enabled :
{
KnownFacesPath : this.Face_Recognition_enabled_path,
distanceThreshold : this.Face_Recognition_distanceThreshold
}
};
}
}
// load image from payload
const tensor = await image(img);
/////////////////////////////////////////////////////////////////
// add check image size for inputs mabye if too large??? hold off on this
/////////////////////////////////////////////////////////////////
// apply configuration options
//FaceDetector
var options;
if ( this.FaceDetector === 'SsdMobilenetv1' )
{
try {
// actual model execution for image send via msg payload
options = new faceapi.SsdMobilenetv1Options({ minConfidence: this.FaceDetector_SsdMobilenetv1_minConfidence, maxResults: this.FaceDetector_SsdMobilenetv1_maxResults});
}
catch (error)
{
notify_user_errors("SsdMobilenetv1Options set to default mode. " + error);
}
}
if ( this.FaceDetector === 'tinyFaceDetector' )
{
if (this.FaceDetector_tinyFaceDetector_inputSize % 32 !== 0)
{
notify_user_errors("TinyFaceDetectorOptions inputSize of " + this.FaceDetector_tinyFaceDetector_inputSize + " not divisible by 32. Using default value of 416");
this.FaceDetector_tinyFaceDetector_inputSize = 416;
}
try {
// actual model execution for image send via msg payload
options = new faceapi.TinyFaceDetectorOptions({ inputSize: this.FaceDetector_tinyFaceDetector_inputSize, scoreThreshold: this.FaceDetector_tinyFaceDetector_scoreThreshold});
}
catch (error)
{
notify_user_errors("TinyFaceDetectorOptions set to default mode. " + error);
}
}
//setup var for eval for model execution based on user input(s)
var model_array = [];
//withFaceLandmarks
if ( this.FaceLandmarks === true )
{
model_array.push('withFaceLandmarks');
}
//withFaceExpressions
if ( this.FaceExpressions === true )
{
model_array.push('withFaceExpressions');
}
//withAgeAndGender
if ( this.AgeAndGender === true )
{
model_array.push('withAgeAndGender');
}
//withFaceDescriptor(s)
if ( this.FaceDescriptors === true )
{
if ( this.Tasks === 'detectAllFaces' )
{
if ( this.FaceLandmarks === false )
{
notify_user_errors('Tasks Error: must enable withFaceLandmarks to use withFaceDescriptor(s)');
}
else
{
model_array.push('withFaceDescriptors');
}
}
if ( this.Tasks === 'detectSingleFace' )
{
if ( this.FaceLandmarks === false )
{
notify_user_errors('Tasks Error: must enable withFaceLandmarks to use withFaceDescriptor(s)');
}
else
{
model_array.push('withFaceDescriptor');
}
}
}
if ( tensor )
{
var model_array_length = model_array.length;
var result;
if ( model_array_length === 4 )
{
result = await faceapi[this.Tasks](tensor, options)[model_array[0]]()[model_array[1]]()[model_array[2]]()[model_array[3]]();
}
if ( model_array_length === 3 )
{
result = await faceapi[this.Tasks](tensor, options)[model_array[0]]()[model_array[1]]()[model_array[2]]();
}
if ( model_array_length === 2 )
{
result = await faceapi[this.Tasks](tensor, options)[model_array[0]]()[model_array[1]]();
}
if ( model_array_length === 1 )
{
result = await faceapi[this.Tasks](tensor, options)[model_array[0]]();
}
if ( model_array_length === 0 )
{
result = await faceapi[this.Tasks](tensor, options);
}
//msg.model_eval = model_eval;
tensor.dispose();
//check if user wants to do Face_Recognition
//FaceRecognition for node set via node properties
if ( this.Face_Recognition === 'Face_Recognition_enabled' )
{
//error check that withFaceDescriptor(s) must be enabled to use FaceRecognition
if ( this.FaceDescriptors === false || this.FaceLandmarks === false )
{
notify_user_errors('FaceRecognition Error: must enable withFaceDescriptor(s) and withFaceLandmarks to use FaceRecognition');
//send what was done
}
else
{
//see if user wants to ReInitializeFaceMatcher
const context_FaceMatcherInitialized = nodeContext.get('FaceMatcherInitialized') || false;
//see if previous labeledFaceDescriptors has been populated, it doesn't exist set to false
var context_labeledFaceDescriptors = nodeContext.get('labeledFaceDescriptors') || false;
//context_labeledFaceDescriptors false or context_FaceMatcherInitialized
if ( context_labeledFaceDescriptors === false || context_FaceMatcherInitialized === false )
{
//check if example path else use user defined dirPath
var user_path;
if ( this.Face_Recognition_enabled_path === 'FullPathToLabeledFaces' || this.Face_Recognition_enabled_path === '/example/labeled_face' )
{
user_path = path.join(__dirname, '/example/labeled_face');
}
else
{
user_path = this.Face_Recognition_enabled_path;
}
var list_dirs_in_labeled_face_folder = await listDirectories(user_path).catch(error => {
notify_user_errors(error);
});
//msg.dirs = list_dirs_in_labeled_face_folder;
// get just the names of the dirs
var list_dirs_in_labeled_face_folder_names_only = list_dirs_in_labeled_face_folder.map(x => {
var n = x.lastIndexOf('/');
var result = x.substring(n + 1);
return result;
});
//msg.dirs_names_only =list_dirs_in_labeled_face_folder_names_only;
const labeledFaceDescriptors = await LoadLabeledImages();
//use helper toJSON so users can save the FaceDescriptors
let labeledFaceDescriptors_toJson = labeledFaceDescriptors.map(x=>x.toJSON());
nodeContext.set('labeledFaceDescriptors',labeledFaceDescriptors_toJson);
nodeContext.set('FaceMatcherInitialized',true);
}
context_labeledFaceDescriptors = nodeContext.get('labeledFaceDescriptors');
let labeledFaceDescriptors_fromJson = context_labeledFaceDescriptors.map( x=>faceapi.LabeledFaceDescriptors.fromJSON(x));
const faceMatcher = await new faceapi.FaceMatcher(labeledFaceDescriptors_fromJson, this.Face_Recognition_distanceThreshold);
//sort detections single/multiple faces
if ( this.Tasks === 'detectAllFaces' )
{
result = result.map(fd => {
const the_object = fd;
//add the match to the object
the_object.match = faceMatcher.findBestMatch(fd.descriptor);
return the_object;
});
}
if ( this.Tasks === 'detectSingleFace' )
{
result.match = faceMatcher.findBestMatch(result.descriptor);
}
}
}
}
else
{
notify_user_errors("Input Error: buffered image sent is not of valid type. Please send a valid image");
}
//time to complete process
const time_elap_millis = Date.now() - start;
//msg.TimeToCompleteInSec = (time_elap_millis / 1000);
var sec_to_complete = (time_elap_millis / 1000);
//main return msg =
msg[img_name] =
{
Result : result,
sec_to_complete : sec_to_complete,
OriginalBufferedImg : img,
labeledFaceDescriptors : context_labeledFaceDescriptors,
Properties:
{
modules:
{
cpu : tfjs_node_cpu_module_check,
gpu : tfjs_node_gpu_module_check,
tf : faceapi_tf_version_core
},
bindings : node.bindings,
FaceDetector : user_selected_FaceDetector_object,
Tasks :
{
[this.Tasks] :
{
withFaceLandmarks : this.FaceLandmarks,
withFaceExpressions : this.FaceExpressions,
withAgeAndGender : this.AgeAndGender,
withFaceDescriptors : this.FaceDescriptors
}
}
}
//SystemInfo:
//{
// os:
// {
// arch : system.os.arch,
// platform : system.os.platform,
// release : system.os.release,
// type : system.os.type
// },
// modules:
// {
// cpu : tfjs_node_cpu_module_check,
// gpu : tfjs_node_gpu_module_check,
// tf : faceapi_tf_version_core
//
// }
//}
};
this.status(
{
fill: 'blue',
shape: 'dot',
text: "finished"
});
// clear/end status msg after 1 seconds
setTimeout(status_clear, 1000);
send(msg);
}
// Once finished, call 'done'.
// This call is wrapped in a check that 'done' exists
// so the node will work in earlier versions of Node-RED (<1.0)
if (done) {
done();
}
});
}
RED.nodes.registerType("facial-recognition",facialrecognitionNode);
};