« Back to History
recognize_embedding.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php // recognize_embedding.php header('Content-Type: application/json; charset=utf-8'); function euclidean_distance($a, $b){ $sum = 0.0; $n = count($a); for($i=0;$i<$n;$i++){ $d = ($a[$i] - $b[$i]); $sum += $d*$d; } return sqrt($sum); } try { require_once __DIR__ . '/../../modules/auth/auth.php'; require_login(); $u = auth_user(); $company_id = (int)($u['company_id'] ?? 0); $user_id = (int)($u['id'] ?? 0); require_once __DIR__ . '/../../core/db.php'; $pdo = $GLOBALS['pdo'] ?? null; if(!$pdo) throw new Exception('DB not available'); $raw = json_decode(file_get_contents('php://input'), true); if(!$raw) throw new Exception('Bad input'); $descriptor = $raw['descriptor'] ?? null; $photo = $raw['photo'] ?? null; // optional dataUrl for logging if(!is_array($descriptor) || count($descriptor) === 0) { http_response_code(400); echo json_encode(['error'=>'descriptor required']); exit; } // load all embeddings for this company $stmt = $pdo->prepare("SELECT employee_id, embedding FROM fc_face_templates WHERE company_id=:cid AND embedding IS NOT NULL"); $stmt->execute([':cid'=>$company_id]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); if(!$rows) { echo json_encode(['error'=>'no_enrollments']); exit; } $bestEmp = null; $bestScore = PHP_FLOAT_MAX; // for euclidean lower is better foreach($rows as $r){ $employee_id = (int)$r['employee_id']; $emb_json = $r['embedding']; $emb_arr = json_decode($emb_json, true); if(!$emb_arr) continue; // embeddings could be stored as array of embeddings per employee if(array_values($emb_arr) && is_array($emb_arr[0])) { // multiple embeddings: check each foreach($emb_arr as $single){ $dist = euclidean_distance($descriptor, $single); if($dist < $bestScore){ $bestScore = $dist; $bestEmp = $employee_id; } } } else { // single embedding $dist = euclidean_distance($descriptor, $emb_arr); if($dist < $bestScore){ $bestScore = $dist; $bestEmp = $employee_id; } } } // threshold for face-api/face-rec model (Euclidean). Typical start: 0.6 $threshold = 0.6; if($bestScore <= $threshold){ // optionally record attendance here automatically // insert into fc_attendance $ins = $pdo->prepare("INSERT INTO fc_attendance (company_id, employee_id, punch_time, method, direction, device_id, photo_path, face_match_score, raw_payload, created_at) VALUES (:cid, :eid, NOW(), 'face', 'in', :device, NULL, :score, :raw, NOW())"); // you may wish to save $photo to uploads and set photo_path $ins->execute([':cid'=>$company_id, ':eid'=>$bestEmp, ':device'=>'web-face', ':score'=>$bestScore, ':raw'=>json_encode(['probe'=>$photo])]); // return matched employee info // try to get employee name $s2 = $pdo->prepare("SELECT name FROM company_employee_master WHERE company_id=:cid AND id=:eid LIMIT 1"); $s2->execute([':cid'=>$company_id, ':eid'=>$bestEmp]); $ename = $s2->fetchColumn(); echo json_encode(['ok'=>1,'employee_id'=>$bestEmp,'employee_name'=>$ename,'score'=>$bestScore]); exit; } else { echo json_encode(['error'=>'no_match','score'=>$bestScore]); exit; } } catch(Throwable $e) { error_log('recognize_embedding error: '.$e->getMessage()); http_response_code(500); echo json_encode(['error'=>'internal','msg'=>$e->getMessage()]); }