UNPKG

node-red-contrib-face-recognition

Version:
486 lines (417 loc) 20.9 kB
<!-- Input Node --> <script type="text/javascript"> RED.nodes.registerType('face-api-input', { category: 'Face Recognition', color: '#a6bbcf', inputs: 1, outputs: 1, icon: "file.png", defaults: { name: { value: "face-api-input", required: true }, model: { value: "SSD", required: true }, confidence: { value: 50 }, input_size: { value: 416 }, landmarks: { value: false, required: true }, expressions: { value: false, required: true }, age_gender: { value: false, required: true }, descriptors: { value: false, required: true }, match_metric: { value: "Mean Squared Error", required: true }, match_confidence: { value: 0, required: true }, recognise_nodes: { value: [] }, recognise_node_editor: { value: null, type: "face-api-recognise", required: false }, }, label: function () { return this.name || "face-api-input" }, oneditprepare: async function () { // Set the node as this for context let node = this // Add recognise nodes as an array if not there node.recognise_nodes = (node.recognise_nodes || []) // Create an ajax function to get the nodes info let get_face_api_nodes = function () { return new Promise((resolve, reject) => { $.ajax({ url: 'flows', type: 'get', dataType: 'json', async: false, success: async function (data) { let recognise_node_list = data.flows.filter(item => { return item.type === "face-api-recognise" }) recognise_node_list = recognise_node_list.map(item => { return { 'id': item.id, 'name': item.name } }) resolve(recognise_node_list) }, error: function () { reject([]) } }); }) } // Create a function to update the display of the side bar let display_side_bar_html = function (recognise_node_list) { $("#node-input-rule-container").css('min-height', '215px').css('min-width', '300px').editableList({ addItem: function (container, i, opt) { // Setup the list dropdown container.css({ overflow: 'hidden', whiteSpace: 'nowrap' }); var row = $('<div/>').appendTo(container); var recognise_node_dropdown = $('<select/>', { style: "width:95%; margin-left: 5px; text-align: center;" }).appendTo(row); var recognise_node_dropdown_group = $('<optgroup/>', { label: "recognise nodes" }).appendTo(recognise_node_dropdown); // Add the please select option to the dropdown recognise_node_dropdown_group.append( $("<option></option>") .val("null") .text("Please select...") ); // Add each recognise node to other dropdown list recognise_node_list.forEach(recognise_node => { // Add the value to the drop down list recognise_node_dropdown_group.append( $("<option></option>") .val(recognise_node.id) .text(recognise_node.name) ); // If the opt value is passed and valid change the value immediatley if ((typeof opt === 'string' || opt instanceof String) && opt === recognise_node.id) { recognise_node_dropdown.val(opt).change(); } }); }, removeItem: function (opt) { }, sortable: false, removable: true }); } // Create a function to show all previously added recognise nodes let display_previous_recognise_nodes = function () { node.recognise_nodes.forEach((recognise_node_id) => { $("#node-input-rule-container").editableList('addItem', recognise_node_id); }); } // Run the functions in sync get_face_api_nodes() .then(display_side_bar_html) .then(display_previous_recognise_nodes) .catch(); // Set the handler for model visibility $("#node-input-model").on("change", function () { ($("#node-input-model").val() === "Tiny Yolo") ? $(".yoloOptions").show() : $(".yoloOptions").hide(); }); }, oneditsave: function () { // Get the node from this for later use let node = this // Get the list of saved recognise nodes from the DOM element let recognise_nodes = $("#node-input-rule-container").editableList('items'); // Clear the current list of recognise nodes for the input node.recognise_nodes = []; // Push each of the added nodes to the input node array recognise_nodes.each(function (i) { // Get the value of the drop down list to store let value = $(this).find("select").val(); // Check if value is not null if (value === "null") return; // Check the node has not already been added for (var i = 0; i < node.recognise_nodes.length; i++) { if (node.recognise_nodes[i] === value) return; } // Push value to array if valid node.recognise_nodes.push(value); }); } }); </script> <script type="text/x-red" data-template-name="face-api-input"> <!-- General Settings --> <h4>General Settings</h4> <hr> </hr> <!-- Input Name --> <div class="form-row"> <label for="node-input-name"><i class="icon-tag"></i> Name</label> <input type="text" id="node-input-name" placeholder="Name"> </div> <!-- Detection Settings --> <h4>Detection Settings</h4> <hr> </hr> <!-- Model --> <div class="form-row"> <label for="node-input-model" style="vertical-align: middle;"><i class="icon-tag"></i> Detection Type </label> <select type="text" id="node-input-model" style="position: relative; width: 70%;"> <option selected="selected">SSD</option> <option>Tiny Yolo</option> </select> </div> <!-- Confidence Level --> <div class="form-row"> <label for="node-input-confidence" style="vertical-align: middle;"><i class="icon-bookmark"></i> Detection Confidence</label> <input type="number" max="100" min="0" id="node-input-confidence" placeholder=50> </div> <!-- Input Size --> <div class="form-row" hidden> <label for="node-input-input_size" style="vertical-align: middle;"><i class="icon-bookmark"></i> Input Size</label> <select type="number" id="node-input-input_size" style="position: relative; width: 70%;"> <option>128</option> <option>160</option> <option>128</option> <option>224</option> <option>320</option> <option selected="selected">416</option> <option>512</option> <option>608</option> </select> </div> <!-- Landmarks Boolean --> <div class="form-row"> <label>&nbsp;</label> <input type="checkbox" id="node-input-landmarks" style="display: inline-block; width: auto; vertical-align: top;"> <label for="node-input-landmarks" style="width: 70%;"><span> Add <b>Facial Landmarks</b> to each face</span></label> </div> <!-- Expressions Boolean --> <div class="form-row"> <label>&nbsp;</label> <input type="checkbox" id="node-input-expressions" style="display: inline-block; width: auto; vertical-align: top;"> <label for="node-input-expressions" style="width: 70%;"><span>Predict <b>Facial Expressions</b> for each face</span></label> </div> <!-- Age and Gender Boolean --> <div class="form-row"> <label>&nbsp;</label> <input type="checkbox" id="node-input-age_gender" style="display: inline-block; width: auto; vertical-align: top;"> <label for="node-input-age_gender" style="width: 70%;"><span>Predict <b>Age and Gender</b> for each face</span></label> </div> <!-- Descriptors --> <div class="form-row"> <label>&nbsp;</label> <input type="checkbox" id="node-input-descriptors" style="display: inline-block; width: auto; vertical-align: top;"> <label for="node-input-descriptors" style="width: 70%;"><span>Add <b>Descriptors</b> to the output</span></label> </div> <!-- Recognition Settings --> <h4>Recognition Settings</h4> <hr> </hr> <!-- Recognition Metric Field --> <div class="form-row"> <label for="node-input-match_metric" style="vertical-align: middle;"><i class="icon-bookmark"></i> Recognition Metric</label> <select type="text" id="node-input-match_metric" style="position: relative; width: 70%;"> <option>Euclidean</option> <option>Manhattan</option> <option>Chebyshev</option> <option selected="selected">Mean Squared Error</option> </select> </div> <!-- Recognition confidence Field --> <div class="form-row"> <label for="node-input-match_confidence" style="vertical-align: middle;"><i class="icon-bookmark"></i> Matched Confidence</label> <input type="number" min="0" id="node-input-match_confidence" placeholder="Enter a number between 0 and 10,000"> </div> <!-- Recognise Node List --> <div class="form-row node-input-rule-container-row"> <ol id="node-input-rule-container"></ol> </div> <!-- Recognise Node Editor --> <div class="form-row"> <label for="node-input-recognise_node_editor" style="vertical-align: middle;">Add/Edit</label> <input id="node-input-recognise_node_editor"> </div> <!-- Padding Bottom --> <hr style="padding-bottom: 18px"> </hr> </script> <script type="text/x-red" data-help-name="face-api-input"> <h3>Overview</h3> <p> Use this input node to inference on an image. Add recognise nodes to recognise faces. </p> <h3>Inputs</h3> <dl class="message-properties"> <dt>payload <span class="property-type">Buffer</span> </dt> <dd> The payload must be set to a binary buffer of an image to be accepted</dd> </dl> <h3>Options</h3> <dl class="message-properties"> <dt>Name <span class="property-type">String</span> </dt> <dd> The name of the node instance, this will also be the label for recognition </dd> <dt>Detection Type <span class="property-type">Dropdown</span> </dt> <dd> The type of algorithm to use (SSD or Yolo) </dd> <dt class=>Detection Confidence <span class="property-type">Number</span></dt> <dd> The minimum confidence value to accept as a face. Defaults to 50%</dd> <dt class="optional">Input Size <span class="property-type">Dropdown</span></dt> <dd> The input size of the Yolo algorithm. Defaults to 416</dd> <dt class="optional">Facial Landmarks <span class="property-type">Check Box</span></dt> <dd> Check this to add facial landmarks to the calculation of each face</dd> <dt class="optional">Facial Expressions <span class="property-type">Check Box</span></dt> <dd> Check this to add facial expressions to the calculation of each face</dd> <dt class="optional">Age and Gender <span class="property-type">Check Box</span></dt> <dd> Check this to calculate the age and gender of each face</dd> <dt class="optional">Descriptors <span class="property-type">Check Box</span></dt> <dd> Check this to add the facial descriptors to the output</dd> <dt class="optional">Recognition Metric <span class="property-type">Dropdown</span></dt> <dd> The type of metric to compare faces for recognition. Defaults to Mean Squared Error</dd> <dt class="optional">Matched Confidence <span class="property-type">Numeric</span></dt> <dd> The cutoff value to decide if a face is recognised or not. Defaults to 0</dd> <dt class="optional">Recognise Nodes List <span class="property-type">Check Box</span></dt> <dd> Add recognise nodes here to match detected faces to known faces</dd> <dt class="optional">Add/Edit <span class="property-type">Check Box</span></dt> <dd> Add or edit recognise nodes to add to the list</dd> </dl> <h3>Details</h3> <p> This node is a central inference node for running facial detection and recognition on. Use <code>recognise nodes</code>. to compare detected faces to known ones. </p> <h3>References</h3> <ul> <li><a href="https://github.com/justadudewhohacks/face-api.js">face-api.js</a> - Visit for more info on face-api.js</li> <li><a href="">GitHub</a> - the nodes github repository</li> </ul> </script> <!-- Recognise Node --> <script type="text/javascript"> RED.nodes.registerType('face-api-recognise', { category: 'config', icon: "file.png", defaults: { name: { value: "Unknown", required: true }, file: { value: "" } }, label: function () { return this.name || "face-api-recognise" }, oneditprepare: function () { // Get the Id of the node for functions to use const thisId = this.id // Set up the button callbacks for when clicked $("#node-node-config-file").on("change", function () { if ($("#node-node-config-file").prop("files").length) { $("#recognitionStatus").html("<b>Files added.</b> Click \"Update\" to upload. This make take a while so please be patient") } }) // Setup file button to allow uploads of images to the webpage $("#fileButton").on("click", function () { $("#recognitionStatus").html("<b>Please select one or more valid images</b>") $("#node-node-config-file").trigger("click"); }) // Setup the delete button to remove descriptors if wanted $("#deleteButton").on("click", function () { $.ajax({ url: "faceapi/" + thisId + "/delete", type: 'GET', cache: false, processData: false, contentType: false, success: function (data, textStatus) { $("#recognitionStatus").html("<b>Deleted Node Descriptors</b>") RED.notify("Successfully deleted descriptor"); }, error: function (jqXHR, textStatus, errorThrown) { if (errorThrown === "Not Found") { RED.notify("File not found for this node", "error"); } else { RED.notify("Node Red Internal Error: " + errorThrown, "error"); } } }) }) // Get the status of how many descriptors are stored $.get("faceapi/" + thisId + "/check", function (data) { if (data > 0) { $("#recognitionStatus").html("<b>" + data.toString() + "</b>" + ((data > 1) ? " descriptors exist for this node" : " descriptor exists for this node")) } else if (data == 0) { $("#recognitionStatus").html("<b>Descriptor does not exist</b>") } else { $("#recognitionStatus").html("<b>Unknown state of descriptor</b>") } }); }, oneditsave: function () { // Get the files to upload from the DOM element const filesToUpload = $("#node-node-config-file").prop("files"); // If there are 1 or more files uplaod them to the backend if (filesToUpload.length) { // Notify the user RED.notify("Attempting to create descriptors from images, please wait...", "info", false, 5000); // Get the current ID of the node and create a URL const uri = "faceapi/" + this.id + "/create"; // Create the data let data = new FormData(); $.each(filesToUpload, function (key, value) { data.append(key, value); }); // Send data using ajax to backend url $.ajax({ url: uri, type: 'POST', data: data, cache: false, processData: false, contentType: false, success: function (data, textStatus) { RED.notify("Successfully created descriptors from images", "info", false, 5000); }, error: function (jqXHR, textStatus, errorThrown) { RED.notify("Error Creating Descriptors: " + textStatus, "error", false, 5000); } }) } // Set the file field to null on exit $("#node-node-config-file").val(''); } }); </script> <script type="text/x-red" data-template-name="face-api-recognise"> <!-- Name Field --> <div class="form-row"> <label for="node-config-input-name" style="vertical-align: middle;"><i class="icon-bookmark"></i> Name </label> <input type="text" id="node-config-input-name" placeholder="Name of the face used in this node"> </div> <!-- File uploads --> <div class="form-row"> <label for="node-node-config-file" style="vertical-align: middle;"><i class="icon-bookmark"></i> Add Descriptor</label> <input type="file" multiple accept="image/*" id="node-node-config-file" style="position:absolute; top:-100px"> <div style="display: inline-block; position: relative; width: 70%; height: 20px;"> <div class="btn-group"> <a id="fileButton" class="editor-button"><b>Add Images</b></a> <a id="deleteButton" class="editor-button">Remove Descriptors</a> </div> </div> </div> <!-- Descriptor status --> <div class="form-row"> <label for="recognitionStatus"></label> <label id="recognitionStatus" style="width: 70%;"></label> </div> </script> <script type="text/x-red" data-help-name="face-api-recognise"> <h3>Overview</h3> <p> Recognises the desired face based on previous known detections. </p> <h3>Options</h3> <dl class="message-properties"> <dt>Name <span class="property-type">String</span> </dt> <dd> The name of the node instance, this will also be the label for recognition </dd> </dl> <h3>Details</h3> <p> To use the facial recognition, please select images using the <code>Add Image</code> button after checking the recognise box. After selection the image will be processed into a descriptor for this node. If an image has not been selected, the node will not compare the input for recognition and throw a warning. The label for the descriptor will be the name of the node. </p> <h3>References</h3> <ul> <li><a href="https://github.com/justadudewhohacks/face-api.js">face-api.js</a> - Visit for more info on face-api.js</li> <li><a href="https://github.com/thebigpotatoe/node-red-contrib-face-api">GitHub</a> - the nodes github repository</li> </ul> </script>