I’m working on a project for university, which involves a facial recognition system integrated with an electronic gate. I created a registration for people, and now I need to compare received faces with stored faces. To do this, I created two folders with identical photos and two detection methods: one that takes the image URL to be stored in my Amazon S3 bucket, and another that takes the image from the to_compare folder. However, even if the images are identical, the system does not recognize them as the same person. One possibility is that the images have arrays of different sizes, but I already filtered the array to have only 100 elements.
const mongoose = require('mongoose');
const personSchema = new mongoose.Schema({
name: { type: String, required: true },
cpf: { type: String, required: true, unique: true },
birthDate: { type: Date, required: true },
photoUrl: { type: String },
biometrics: { type: Object }, // Dados biométricos processados da imagem
employee: { type: Boolean, required: true, default: false },
position: {
type: {
title: { type: String, required: true },
department: { type: String, required: true },
hireDate: { type: Date, required: true }
},
default: null,
validate: {
validator: function () {
return this.employee || this.position === null;
},
message: 'Position should be null if employee is false'
}
},
active: { type: Boolean, default: true } // Campo indicando se a pessoa está ativa
});
// Método para ativar a pessoa
personSchema.methods.activate = function () {
this.active = true;
return this.save();
};
// Método para desativar a pessoa
personSchema.methods.deactivate = function () {
this.active = false;
return this.save();
};
module.exports = mongoose.model('Person', personSchema);
// src/controller/personController.js
const Person = require('../model/personModel');
const { processBiometrics, compareBiometrics, compareWithRegisteredImages } = require('../util/biometricsUtils');
/**
* Creates a new person in the system with processed biometric data.
* @param {Object} req - Request object containing person data in the body.
* @param {Object} res - Response object.
*/
exports.createPerson = async (req, res) => {
try {
console.log("Creating person with data:", req.body);
const { name, cpf, birthDate, photoUrl, employee, position } = req.body;
// Processes biometrics if an image is provided
const biometrics = photoUrl ? await processBiometrics(photoUrl) : null;
console.log("Biometrics processed:", biometrics);
// Creates a new person
const person = new Person({
name,
cpf,
birthDate,
photoUrl,
biometrics,
employee,
position: employee ? position : null,
});
await person.save();
console.log("Person created successfully:", person);
res.status(201).json({ message: 'Person created successfully', person });
} catch (error) {
console.error("Error creating person:", error);
res.status(500).json({ error: 'Error creating person' });
}
};
/**
* Retrieves a person by ID.
* @param {Object} req - Request object containing the person's ID in the parameters.
* @param {Object} res - Response object.
*/
exports.getPersonById = async (req, res) => {
try {
console.log("Fetching person by ID:", req.params.id);
const person = await Person.findById(req.params.id);
if (!person) {
console.warn("Person not found with ID:", req.params.id);
return res.status(404).json({ error: 'Person not found' });
}
console.log("Person fetched:", person);
res.status(200).json(person);
} catch (error) {
console.error("Error fetching person:", error);
res.status(500).json({ error: 'Error fetching person' });
}
};
/**
* Updates a person's data by ID and reactivates them.
* @param {Object} req - Request object with the person's ID in the parameters and new data in the body.
* @param {Object} res - Response object.
*/
exports.updatePerson = async (req, res) => {
try {
console.log("Updating person with ID:", req.params.id);
const { name, cpf, birthDate, photoUrl, employee, position } = req.body;
const person = await Person.findById(req.params.id);
if (!person) {
console.warn("Person not found with ID:", req.params.id);
return res.status(404).json({ error: 'Person not found' });
}
person.name = name || person.name;
person.cpf = cpf || person.cpf;
person.birthDate = birthDate || person.birthDate;
person.photoUrl = photoUrl || person.photoUrl;
person.employee = employee !== undefined ? employee : person.employee;
person.position = employee ? position : null;
// Updates biometrics if a new photo is provided
if (photoUrl) {
console.log("Processing new biometrics for update...");
person.biometrics = await processBiometrics(photoUrl);
}
// Reactivates the person on update
await person.activate();
console.log("Person updated and activated successfully:", person);
res.status(200).json({ message: 'Person updated and activated successfully', person });
} catch (error) {
console.error("Error updating person:", error);
res.status(500).json({ error: 'Error updating person' });
}
};
/**
* Deactivates a person by ID, instead of deleting them from the database.
* @param {Object} req - Request object with the person's ID in the parameters.
* @param {Object} res - Response object.
*/
exports.deletePerson = async (req, res) => {
try {
console.log("Deactivating person with ID:", req.params.id);
const person = await Person.findById(req.params.id);
if (!person) {
console.warn("Person not found with ID:", req.params.id);
return res.status(404).json({ error: 'Person not found' });
}
await person.deactivate();
console.log("Person deactivated successfully:", person);
res.status(200).json({ message: 'Person deactivated successfully' });
} catch (error) {
console.error("Error deactivating person:", error);
res.status(500).json({ error: 'Error deactivating person' });
}
};
/**
* Lists all persons registered in the system.
* @param {Object} req - Request object.
* @param {Object} res - Response object.
*/
exports.getAllPersons = async (req, res) => {
try {
console.log("Fetching all persons...");
const persons = await Person.find();
console.log("Persons fetched:", persons);
res.status(200).json(persons);
} catch (error) {
console.error("Error fetching persons:", error);
res.status(500).json({ error: 'Error fetching persons' });
}
};
/**
* Verifies an image provided by URL and compares it with the biometrics of active persons.
* @param {Object} req - Request object containing the image URL in the body.
* @param {Object} res - Response object.
*/
exports.verifyPerson = async (req, res) => {
try {
console.log("Verifying person with photoUrl:", req.body.photoUrl);
const { photoUrl } = req.body;
// Processes the image to obtain biometric data
const biometrics = await processBiometrics(photoUrl);
if (!biometrics) {
console.warn("No biometric data found in provided image.");
return res.status(404).json({ match: false });
}
console.log("Processed biometrics for verification:", biometrics);
// Compares biometrics with registered persons
const persons = await Person.find({ active: true });
console.log("Comparing biometrics with active persons...");
for (let person of persons) {
if (person.biometrics && compareBiometrics(biometrics, person.biometrics)) {
console.log("Match found:", person);
return res.status(299).json({ match: true, person });
}
}
console.log("No match found.");
res.status(404).json({ match: false });
} catch (error) {
console.error("Error verifying person:", error);
res.status(500).json({ error: 'Error verifying person' });
}
};
/**
* Verifies a test image from the 'to_compare' folder by comparing it with registered images.
* Receives the image name and compares it with stored biometrics in the 'registered' folder.
* @param {Object} req - Request object containing the image name in the body.
* @param {Object} res - Response object.
*/
exports.verifyPersonWithImage = async (req, res) => {
try {
const { imageName } = req.body;
console.log("Verifying person with image:", imageName);
// Calls the comparison function with registered images
const result = await compareWithRegisteredImages(imageName);
// Responds with comparison results
if (result.match) {
res.status(299).json({ match: true, fileName: result.fileName });
} else {
res.status(404).json({ match: false });
}
} catch (error) {
console.error("Error verifying person:", error);
res.status(500).json({ error: 'Error verifying person' });
}
};
// src/util/biometricsUtils.js
const faceapi = require('face-api.js'); // Library for facial recognition
const tf = require('@tensorflow/tfjs-node'); // TensorFlow for data processing
const { Canvas, Image, ImageData } = require('canvas'); // Canvas for image manipulation
const canvas = require('canvas');
const path = require('path');
const fs = require('fs');
// Configures face-api.js to use canvas and manipulate images
faceapi.env.monkeyPatch({ Canvas, Image, ImageData });
/**
* Loads the necessary facial recognition models for detection,
* facial landmarks extraction, and facial descriptor.
*/
const loadModels = async () => {
const modelPath = './models'; // Path where models are stored
await faceapi.nets.ssdMobilenetv1.loadFromDisk(modelPath);
await faceapi.nets.faceLandmark68Net.loadFromDisk(modelPath);
await faceapi.nets.faceRecognitionNet.loadFromDisk(modelPath);
};
/**
* Processes a local image to extract biometric data with the first 100 elements.
* @param {string} imagePath - Path to the local image to be processed.
* @returns {Float32Array | null} Extracted biometric data (100 elements) or null if no face is detected.
*/
const processBiometrics = async (imagePath) => {
try {
console.log("Processing biometrics for image:", imagePath);
// Loads models if not already loaded
await loadModels();
// Loads the image from the specified path
const img = await canvas.loadImage(imagePath);
const detections = await faceapi.detectSingleFace(img)
.withFaceLandmarks()
.withFaceDescriptor();
if (!detections) {
console.warn("No face detected in image.");
return null;
}
// Returns only the first 100 elements of the facial descriptor
const biometrics = detections.descriptor.slice(0, 100);
if (biometrics && biometrics.length === 100) {
console.log("Biometric data loaded successfully with 100 elements.");
} else {
console.warn("Biometric data not loaded correctly or has an incorrect length.");
}
return biometrics;
} catch (error) {
console.error("Error processing biometrics:", error);
return null;
}
};
/**
* Compares two sets of biometric data to check for a match.
* @param {Float32Array} biometrics1 - First set of biometric data (100 points).
* @param {Float32Array} biometrics2 - Second set of biometric data (100 points).
* @returns {boolean} Returns true if data matches, false otherwise.
*/
const compareBiometrics = (biometrics1, biometrics2) => {
if (!biometrics1 || !biometrics2) {
console.warn("One of the biometric arrays is null.");
return false;
}
if (biometrics1.length !== 100 || biometrics2.length !== 100) {
console.warn("Biometric data length mismatch. Expected 100 elements.");
return false;
}
try {
// Calculates the Euclidean distance between the descriptors
const distance = faceapi.euclideanDistance(biometrics1, biometrics2);
const threshold = 0.7; // Adjusted threshold for more flexible matching
return distance < threshold;
} catch (error) {
console.error("Error comparing biometrics:", error);
return false;
}
};
/**
* Compares two sets of biometric data multiple times to check for match consistency.
* @param {Float32Array} biometrics1 - First set of biometric data.
* @param {Float32Array} biometrics2 - Second set of biometric data.
* @param {number} attempts - Number of verification attempts.
* @returns {boolean} Returns true if there is a match in all attempts, false otherwise.
*/
const verifyBiometricsMultipleAttempts = (biometrics1, biometrics2, attempts = 3) => {
let matches = 0;
for (let i = 0; i < attempts; i++) {
if (compareBiometrics(biometrics1, biometrics2)) {
matches++;
}
}
return matches >= attempts;
};
/**
* Loads and processes all images in the 'registered' folder for storing biometric data.
* @returns {Object} An object containing biometric data for each registered image.
*/
const loadRegisteredBiometrics = async () => {
const registeredPath = path.join(__dirname, '../../images/registered');
const registeredBiometrics = {};
const files = fs.readdirSync(registeredPath);
for (const file of files) {
const imagePath = path.join(registeredPath, file);
const biometricData = await processBiometrics(imagePath);
if (biometricData) {
registeredBiometrics[file] = biometricData;
}
}
return registeredBiometrics;
};
/**
* Compares an image from the 'to_compare' folder with all registered images in the 'registered' folder.
* Checks each comparison multiple times to ensure the match.
* @param {string} compareImageName - Name of the image to be compared.
* @returns {Object} An object indicating if there was a match and the name of the corresponding image.
*/
const compareWithRegisteredImages = async (compareImageName) => {
const registeredBiometrics = await loadRegisteredBiometrics();
const compareImagePath = path.join(__dirname, '../../images/to_compare', compareImageName);
const compareBiometricsData = await processBiometrics(compareImagePath);
if (!compareBiometricsData) {
console.warn("No biometric data found in comparison image.");
return { match: false };
}
for (const [fileName, registeredBiometricsData] of Object.entries(registeredBiometrics)) {
// Checks for a match with at least 3 attempts
if (verifyBiometricsMultipleAttempts(compareBiometricsData, registeredBiometricsData, 3)) {
console.log(`Match found with registered image: ${fileName}`);
return { match: true, fileName };
}
}
console.log("No match found.");
return { match: false };
};
module.exports = {
processBiometrics,
compareBiometrics,
loadRegisteredBiometrics,
compareWithRegisteredImages
};
// src/route/personRoutes.js
const express = require('express');
const personController = require('../controller/personController');
const router = express.Router();
/**
* Route to create a new person.
* Method: POST
* Endpoint: /persons
* Request Body: JSON with person data (name, cpf, birthDate, photoUrl, etc.)
*/
router.post('/', personController.createPerson);
/**
* Route to retrieve a person by ID.
* Method: GET
* Endpoint: /persons/:id
* Parameter: Person's ID in the URL.
*/
router.get('/:id', personController.getPersonById);
/**
* Route to update a person's data by ID.
* Method: PUT
* Endpoint: /persons/:id
* Parameter: Person's ID in the URL.
* Request Body: JSON with the data to be updated.
*/
router.put('/:id', personController.updatePerson);
/**
* Route to deactivate a person by ID.
* Method: DELETE
* Endpoint: /persons/:id
* Parameter: Person's ID in the URL.
*/
router.delete('/:id', personController.deletePerson);
/**
* Route to list all registered persons.
* Method: GET
* Endpoint: /persons
*/
router.get('/', personController.getAllPersons);
/**
* Route to verify a person's biometrics using an image URL.
* Method: POST
* Endpoint: /persons/verify
* Request Body: JSON with the image URL (photoUrl).
*/
router.post('/verify', personController.verifyPerson);
/**
* Route to verify a person's biometrics using a local image from the 'to_compare' folder.
* Method: POST
* Endpoint: /persons/verify-image
* Request Body: JSON with the name of the image to be compared (imageName).
*/
router.post('/verify-image', personController.verifyPersonWithImage);
module.exports = router;
I tried checking with ChatGPT, but I keep going in circles with it.
I used ChatGPT to add comments to the code for better understanding, and I’d like to know where I might be going wrong. If anyone could help, I’d be very grateful.