face-api.js icon indicating copy to clipboard operation
face-api.js copied to clipboard

Saving and Loading Descriptors for future use

Open edwinlimlx opened this issue 6 years ago • 11 comments

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.

edwinlimlx avatar Mar 01 '19 03:03 edwinlimlx

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. :)

justadudewhohacks avatar Mar 01 '19 08:03 justadudewhohacks

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);
  });

Bajajar avatar Mar 03 '19 09:03 Bajajar

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.

svfreitas avatar Mar 26 '19 18:03 svfreitas

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)) `

svfreitas avatar Mar 26 '19 23:03 svfreitas

I also got it working with this https://gist.github.com/jonathanlurie/04fa6343e64f750d03072ac92584b5df

jvkassi avatar May 31 '19 22:05 jvkassi

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); }

Mactacs avatar Jul 11 '19 02:07 Mactacs

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.

jondewoo avatar Aug 28 '19 12:08 jondewoo

// 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 ?

agnoam avatar Apr 19 '20 16:04 agnoam

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/

benayat avatar Apr 05 '21 20:04 benayat

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.

foag1996 avatar Sep 21 '22 14:09 foag1996

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 .

Naman503 avatar Dec 11 '22 18:12 Naman503