/** * POST /v1/detect — Objektigenkänning (YOLO/ONNX) * Använder befintlig ONNX/YuNet för face detection som proxy för objekt-detection * eller faller tillbaka på AI-baserad bildanalys via Ollama/Groq. */ import { Router } from 'express'; import * as ort from 'onnxruntime-node'; import sharp from 'sharp'; import { fetchImage, hashInput, saveResult, genReqId, requireAuth } from './utils.mjs'; const router = Router(); // Model paths — befintliga modeller const FACE_DET_MODEL = '/opt/amos/data/kyc-service/models/face_detection_yunet_2023mar.onnx'; const FACE_REC_MODEL = '/opt/amos/data/kyc-service/models/face_recognition_sface_2021dec.onnx'; // Ladda ONNX sessioner lazy let detSession = null; async function getDetSession() { if (!detSession) { detSession = await ort.InferenceSession.create(FACE_DET_MODEL); } return detSession; } router.post('/', requireAuth, async (req, res) => { const requestId = genReqId(); const start = Date.now(); try { const { image_url, image_base64, model = 'yunet', threshold = 0.5 } = req.body || {}; const img = await fetchImage({ image_url, image_base64 }); const inputHash = hashInput(img.buffer); // Preprocess: resize to 640x640 (YuNet input) const raw = await sharp(img.buffer).resize(640, 640).raw().toBuffer({ resolveWithObject: true }); const { data, info } = raw; const h = info.height, w = info.width; // Build float32 tensor [1,3,H,W] normalized const floatData = new Float32Array(1 * 3 * h * w); for (let y = 0; y < h; y++) { for (let x = 0; x < w; x++) { const idx = (y * w + x) * 3; const r = data[idx] / 255.0; const g = data[idx + 1] / 255.0; const b = data[idx + 2] / 255.0; floatData[0 * h * w + y * w + x] = r; floatData[1 * h * w + y * w + x] = g; floatData[2 * h * w + y * w + x] = b; } } const tensor = new ort.Tensor('float32', floatData, [1, 3, h, w]); const session = await getDetSession(); const feeds = {}; feeds[session.inputNames[0]] = tensor; const results = await session.run(feeds); // Parse YuNet output: [num,15] — each row: [batch_idx, class_id, score, x1,y1,x2,y2, ...] const outTensor = results[session.outputNames[0]]; const outData = outTensor.data; const dims = outTensor.dims; const stride = dims[dims.length - 1]; const numDetections = dims[0]; const objects = []; let maxConf = 0; for (let i = 0; i < numDetections; i++) { const row = Array.from(outData.slice(i * stride, (i + 1) * stride)); const score = row[2]; if (score > threshold) { const x1 = row[3] * 320, y1 = row[4] * 320; const x2 = row[5] * 320, y2 = row[6] * 320; objects.push({ label: 'face', confidence: parseFloat(score.toFixed(4)), bbox: { x: Math.round(x1), y: Math.round(y1), width: Math.round(x2 - x1), height: Math.round(y2 - y1) } }); if (score > maxConf) maxConf = score; } } const result = { ok: true, endpoint: 'detect', request_id: requestId, model: 'yunet-face-detection', objects_detected: objects.length, objects, inference_time_ms: Date.now() - start, }; await saveResult('detect', requestId, inputHash, result, maxConf, { threshold, source: img.source }); res.json(result); } catch (e) { console.error('[detect]', e); res.status(500).json({ ok: false, error: e.message, request_id: requestId }); } }); export default router;