face-api.js
face-api.js copied to clipboard
Saving and Loading Descriptors for future use
With ref https://github.com/justadudewhohacks/face-api.js#face-recognition-by-matching-descriptors
I've managed to train the faces and and saving the dataset with JSON.stringify(labelledDescriptors)
to a static JSON.
Is there a way to quickly load this dataset, or do I have to load the raw JSON and reinit the dataset with each new faceapi.LabeledFaceDescriptors(name, descriptors)
?
Is there method such as
const labelledDescriptors = await faceapi.fetchDescriptors('/files/labeledDescriptors.json');
const faceMatcher = new faceapi.FaceMatcher(labelledDescriptors);
Pardon, if I didn't explain myself well. But the objective is to quickly loaded a saved labeledDescriptors for usage.
I see what you mean, currently there is no way other than what you suggested. The FaceMatcher class could be extended with a toJSON method for serializing and a fromJSON method for deserializing the state. Contributions are highly appreciated. :)
This is not the bestway but it's working. Set faceMatcher as global variable then you can call faceMatcher.findBestMatch(newdata) to compare new data with your saved dataset.
// Global variable
var faceMatcher;
// Create Face Matcher
async function createFaceMatcher(data) {
const labeledFaceDescriptors = await Promise.all(data._labeledDescriptors.map(className => {
const descriptors = [];
for (var i = 0; i < className._descriptors.length; i++) {
descriptors.push(className._descriptors[i]);
}
return new faceapi.LabeledFaceDescriptors(className._label, descriptors);
}))
return new faceapi.FaceMatcher(labeledFaceDescriptors);
}
// Load json to backend
fs.readFile('demo.json', async function(err, data) {
if (err) {
console.log(err);
}
var content = JSON.parse(data);
for (var x = 0; x < content['_labeledDescriptors'].length; x++) {
for (var y = 0; y < content['_labeledDescriptors'][x]['_descriptors'].length; y++) {
var results = Object.values(content['_labeledDescriptors'][x]['_descriptors'][y]);
content._labeledDescriptors[x]._descriptors[y] = new Float32Array(results);
}
}
faceMatcher = await createFaceMatcher(content);
});
I will try to create it, but I am thinking to serialize the resultsRef : resultsRef = await faceapi.detectAllFaces(referenceImage, faceDetectionOptions)
So we can detect faces in a batch process, store the information in a database (as json) and after recreate the objects from the information stored to run a specific comparation and so on.
It is working for the descriptor object.
- descriptor - OK
- detection
- landmarks
- unshiftedLandMarks
- alignedRect
code I used to test the serialization to JSon and back
` const jsonStr = JSON.stringify(resultsRef.map(res => res.descriptor ))
fs.writeFileSync('./descriptor.json',jsonStr) const str = fs.readFileSync('./descriptor.json')
let obj = new Array(Object.values(JSON.parse(str.toString())))
let arrayDescriptor = new Array(obj[0].length)
let i = 0 obj[0].forEach(function(entry) { arrayDescriptor[i++] = new Float32Array(Object.values(entry)) });
//const faceMatcher = new faceapi.FaceMatcher(resultsRef) const faceMatcher = new faceapi.FaceMatcher(arrayDescriptor)) `
I also got it working with this https://gist.github.com/jonathanlurie/04fa6343e64f750d03072ac92584b5df
const labeledFaceDescriptors = await loadLabeledImages() var json_str = "{"parent":" + JSON.stringify(labeledFaceDescriptors) + "}" // save the json_str to json file
// Load json file and parse var content = JSON.parse(json_str)
for (var x = 0; x < Object.keys(content.parent).length; x++) { for (var y = 0; y < Object.keys(content.parent[x]._descriptors).length; y++) { var results = Object.values(content.parent[x]._descriptors[y]); content.parent[x]._descriptors[y] = new Float32Array(results); } } const faceMatcher = await createFaceMatcher(content);
function loadLabeledImages() {
const labels = ['Black Widow', 'Captain America', 'Captain Marvel', 'Hawkeye', 'Jim Rhodes', 'Thor', 'Tony Stark']
return Promise.all(
labels.map(async label => {
const descriptions = []
for (let i = 1; i <= 2; i++) {
const img = await faceapi.fetchImage(https://raw.githubusercontent.com/WebDevSimplified/Face-Recognition-JavaScript/master/labeled_images/${label}/${i}.jpg
)
const detections = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor()
descriptions.push(detections.descriptor)
}
return new faceapi.LabeledFaceDescriptors(label, descriptions)
})
)
}
// Create Face Matcher async function createFaceMatcher(data) { const labeledFaceDescriptors = await Promise.all(data.parent.map(className => { const descriptors = []; for (var i = 0; i < className._descriptors.length; i++) { descriptors.push(className._descriptors[i]); } return new faceapi.LabeledFaceDescriptors(className._label, descriptors); })) return new faceapi.FaceMatcher(labeledFaceDescriptors); }
Check out #397.
I added FaceMatcher.fromJSON()
/ .fromPOJO()
and LabeledFaceDescriptors.fromJSON()
/ .fromPOJO()
.
.fromJSON()
implementations take a JSON string and return a fully instantiated object.
.fromPOJO()
implementations take a Plain Old JavaScript Object and return a fully instantiated object.
// Parsing matchers to json
for(let key of Object.keys(updatedFile)) {
let matcher = updatedFile[key].matcher as FaceMatcher;
updatedFile[key].matcher = matcher.toJSON();
}
fs.writeFileSync(this.paths.facesMatchers, JSON.stringify(updatedFile));
This code throwing me that exception:
Unhandled Promise rejection: matcher.toJSON is not a function ; Zone: <root> ; Task: Promise.then ; Value: TypeError: matcher.toJSON is not a function
Someone can explain to me why ?
why not save it to a file as raw text? I use react so I cant (simply) save files to my disk, so I found a bit nasty walkaround: instead of using JSON, you could use indexeddb to upload all the descriptors as they are, and then load them directly as an array, and just use: const faceMatcher = new faceapi.FaceMatcher(labeledDescriptors); and if you really need to get the descriptors on a file: - a bit old but a way to access indexeddb local database: https://www.aaron-powell.com/posts/2012-10-05-indexeddb-storage/
const labeledFaceDescriptors = await loadLabeledImages() var json_str = "{"parent":" + JSON.stringify(labeledFaceDescriptors) + "}" // save the json_str to json file
// Load json file and parse var content = JSON.parse(json_str)
for (var x = 0; x < Object.keys(content.parent).length; x++) { for (var y = 0; y < Object.keys(content.parent[x]._descriptors).length; y++) { var results = Object.values(content.parent[x]._descriptors[y]); content.parent[x]._descriptors[y] = new Float32Array(results); } } const faceMatcher = await createFaceMatcher(content);
function loadLabeledImages() { const labels = ['Black Widow', 'Captain America', 'Captain Marvel', 'Hawkeye', 'Jim Rhodes', 'Thor', 'Tony Stark'] return Promise.all( labels.map(async label => { const descriptions = [] for (let i = 1; i <= 2; i++) { const img = await faceapi.fetchImage(
https://raw.githubusercontent.com/WebDevSimplified/Face-Recognition-JavaScript/master/labeled_images/${label}/${i}.jpg
) const detections = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor() descriptions.push(detections.descriptor) } return new faceapi.LabeledFaceDescriptors(label, descriptions) }) ) }// Create Face Matcher async function createFaceMatcher(data) { const labeledFaceDescriptors = await Promise.all(data.parent.map(className => { const descriptors = []; for (var i = 0; i < className._descriptors.length; i++) { descriptors.push(className._descriptors[i]); } return new faceapi.LabeledFaceDescriptors(className._label, descriptors); })) return new faceapi.FaceMatcher(labeledFaceDescriptors); }
how can I implement that solution in my code, I don't understand your solution, thank you very much.
The main problem in saving detections Is that you have to convert it back from string to Array32 or Array64 and others in different parts of the string. What I found is to use a function which can convert a string back to a detection object.
function stringifyForEveryThing(descriptions){ let jsonStr = JSON.stringify(descriptions, function (key, value) { // the replacer function is looking for some typed arrays. // If found, it replaces it by a trio if ( value instanceof Int8Array || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Int16Array || value instanceof Uint16Array || value instanceof Int32Array || value instanceof Uint32Array || value instanceof Float32Array || value instanceof Float64Array ) { var replacement = { constructor: value.constructor.name, data: Array.apply([], value), flag: FLAG_TYPED_ARRAY, }; return replacement; } return value; });
return jsonStr;
}
// This will return the Stingify String which you can use to store. // Now lets convert it back with different function.
function unDoStringify(retrivedObj){ let decodedJson = JSON.parse(retrivedObj, function (key, value) { // the reviver function looks for the typed array flag try { if ("flag" in value && value.flag === FLAG_TYPED_ARRAY) { // if found, we convert it back to a typed array return new contextvalue.constructor; } } catch (e) {}
// if flag not found no conversion is done
return value;
}); return decodedJson; }
// Here you go .