118 lines
3.8 KiB
JavaScript
118 lines
3.8 KiB
JavaScript
import * as faceapi from '@vladmandic/face-api';
|
|
import sharp from 'sharp';
|
|
import pg from 'pg';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
// Disable certificate verification for CDN download if needed
|
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
|
|
const pool = new pg.Pool({
|
|
connectionString: 'postgresql://edumanager:EduManager2026!Seguro@150.230.87.131:5432/edumanager'
|
|
});
|
|
|
|
async function run() {
|
|
try {
|
|
// 1. Initialize face-api environment for Node
|
|
console.log('Inicializando face-api...');
|
|
|
|
// Tiny Face Detector, Face Landmark 68, and Face Recognition Nets
|
|
const MODEL_URL = 'https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model/';
|
|
console.log('Carregando modelos face-api do CDN...');
|
|
await Promise.all([
|
|
faceapi.nets.ssdMobilenetv1.loadFromUri(MODEL_URL),
|
|
faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL),
|
|
faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL)
|
|
]);
|
|
console.log('Modelos carregados com sucesso!');
|
|
|
|
// 2. Fetch students and their face descriptors from DB
|
|
console.log('Buscando alunos do banco de dados...');
|
|
const { rows: students } = await pool.query('SELECT id, nome, face_descriptor FROM alunos WHERE face_descriptor IS NOT NULL');
|
|
console.log(`Encontrados ${students.length} alunos com biometria no banco.`);
|
|
|
|
// 3. Read downloaded photos from disk
|
|
const photosDir = path.join(__dirname, 'photos');
|
|
const photoFiles = fs.readdirSync(photosDir).filter(f => f.endsWith('.webp'));
|
|
console.log(`Encontradas ${photoFiles.length} fotos webp locais.`);
|
|
|
|
const results = [];
|
|
|
|
// 4. Process each photo, get its descriptor, and find best match
|
|
for (const file of photoFiles) {
|
|
const filePath = path.join(photosDir, file);
|
|
console.log(`Processando ${file}...`);
|
|
|
|
// Use sharp to convert webp to raw RGB buffer for face-api tensor
|
|
const image = sharp(filePath);
|
|
const metadata = await image.metadata();
|
|
const { data, info } = await image
|
|
.raw()
|
|
.toBuffer({ resolveWithObject: true });
|
|
|
|
// Create tensor from raw pixels
|
|
const tensor = faceapi.tf.tensor3d(
|
|
new Uint8Array(data),
|
|
[info.height, info.width, 3],
|
|
'int32'
|
|
);
|
|
|
|
// Detect face and extract descriptor
|
|
const detection = await faceapi.detectSingleFace(tensor)
|
|
.withFaceLandmarks()
|
|
.withFaceDescriptor();
|
|
|
|
tensor.dispose();
|
|
|
|
if (!detection) {
|
|
console.warn(`Nenhum rosto detectado na foto ${file}`);
|
|
continue;
|
|
}
|
|
|
|
const imgDescriptor = detection.descriptor;
|
|
|
|
// Compare with all students using Euclidean distance
|
|
let bestMatch = null;
|
|
let minDistance = Infinity;
|
|
|
|
for (const s of students) {
|
|
const dbDescriptorStr = typeof s.face_descriptor === 'string'
|
|
? s.face_descriptor
|
|
: JSON.stringify(s.face_descriptor);
|
|
|
|
const dbDescriptor = new Float32Array(JSON.parse(dbDescriptorStr));
|
|
const distance = faceapi.euclideanDistance(imgDescriptor, dbDescriptor);
|
|
|
|
if (distance < minDistance) {
|
|
minDistance = distance;
|
|
bestMatch = s;
|
|
}
|
|
}
|
|
|
|
console.log(`Foto: ${file} | Melhor Match: ${bestMatch?.nome} | Distância: ${minDistance.toFixed(4)}`);
|
|
results.push({
|
|
file,
|
|
studentId: bestMatch?.id,
|
|
studentName: bestMatch?.nome,
|
|
distance: minDistance
|
|
});
|
|
}
|
|
|
|
console.log('\n--- RESULTADO DE PROJEÇÃO DE CORRELAÇÃO DE BIOMETRIA ---');
|
|
results.forEach(r => {
|
|
console.log(`Foto: ${r.file} -> Aluno: ${r.studentName} (ID: ${r.studentId}) | Distância: ${r.distance.toFixed(4)}`);
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error('Erro na execução:', err);
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
run();
|