galleria_photo_face_rknn_op.../faces2.md
2026-10-03 12:16:58 +02:00

4.5 KiB
Raw Blame History

Perfetto. Ho guardato direttamente il repository e adesso possiamo progettare l'integrazione sul codice reale, non su ipotesi.

Il repository conferma che la pipeline produce esattamente ciò che ci serve: SCRFD → landmark → alignment 112×112 → SFace → 128 float L2-normalizzati. La Face attuale contiene già score, bbox, 5 landmark e std::vector embedding.

La cosa più importante

Non cambierei il modello RKNN.

Il formato ideale per il DB è già quello che abbiamo:

float embedding[128]

Il codice attuale fa anche già la L2 normalization prima di restituire l'embedding.

Quindi il flusso diventa:

photos.id │ ▼ SCRFD │ ├── bbox ├── confidence └── 5 landmarks │ ▼ alignment │ ▼ SFace │ ▼ float[128] │ ▼ faces │ ├── HDBSCAN │ └── recognition

E farei una piccola modifica all'architettura del repository

Attualmente face_recognition.cc è un programma di test: prende una/due immagini e stampa i risultati. Il main() stesso conferma che l'attuale uso è principalmente il confronto fra immagini.

Non lo trasformerei direttamente nel processo DB.

Creerei invece qualcosa del genere:

face-rknn/ ├── src/ │ ├── face_recognition.cc ← lasciamo il test attuale │ ├── face_engine.cc │ ├── face_engine.h │ └── face_scan.cc ← nuovo scanner DB

dove FaceEngine espone qualcosa tipo:

struct FaceResult { float score; float x1, y1, x2, y2; Point kps[5]; std::array<float, 128> embedding; };

class FaceEngine { public: bool init( const char* scrfd_model, const char* sface_model );

std::vector<FaceResult> process(
    const Image& image
);

};

Così il codice RKNN resta riutilizzabile.


DB: ora possiamo definirlo con precisione

Dato che sei su SQLite/better-sqlite3, userei:

faces.embedding → BLOB

con esattamente:

128 * sizeof(float) = 512 byte

per volto.

E non salverei FP16.

Il modello restituisce FP16, ma il codice attuale li converte già in float e poi normalizza.

Quindi salviamo il risultato finale:

float32[128]

che è esattamente quello che poi il tuo OpenCL può consumare.

Per 350.000 volti:

350000 × 512 ≈ 179.2 MB

assolutamente gestibile.


Una correzione rispetto a quello che ti avevo detto prima

Avendo visto il codice vero, non introdurrei subito prototypes nel DB.

Prima farei funzionare questa pipeline:

photos ↓ faces ↓ OpenCL kNN ↓ HDBSCAN ↓ clusters ↓ utente assegna persona ↓ persons

Poi, quando implementiamo il riconoscimento incrementale, decidiamo se i prototype debbano essere:

una tabella persistente;

un subset di faces;

oppure un rappresentante/calcolo derivato.

Non voglio aggiungere complessità prima che serva.


E c'è un dettaglio molto utile

Il repository usa già:

cosine(a, b)

su embedding L2-normalizzati.

Quindi il tuo benchmark OpenCL che stiamo facendo è perfettamente allineato:

cosine(a,b)

dot(a,b)

per vettori normalizzati.

Questa è una buona notizia: non dobbiamo cambiare metrica.

Il tuo OpenCL può quindi ricevere direttamente i BLOB float32[128] e fare il dot product.


Quindi io procederei così

Passo 1 — DB

Aggiungiamo a init.js:

faces face_clusters persons

senza toccare photos.

Passo 2 — FaceEngine

Separiamo detector + SFace dal main() di test.

Passo 3 — scanner

Prende:

photos

e per ogni foto non ancora processata:

image ↓ faces ↓ embedding ↓ INSERT faces

Passo 4 — GPU

Solo quando abbiamo gli embedding nel DB:

SQLite ↓ float32 array ↓ OpenCL ↓ kNN TOPK=32

Passo 5 — HDBSCAN

Usiamo quel kNN per costruire:

core distance mutual reachability MST condensed tree cluster

Passo 6 — UI

L'utente vede i cluster e assegna:

cluster 123 → "Mario"

che significa:

faces.cluster_id = 123 faces.person_id = Mario

Poi il sistema incrementale può riconoscere direttamente Mario senza rieseguire HDBSCAN globale.


A questo punto non ti chiederei altro codice alla cieca: abbiamo già DB + backend + face engine + formato embedding.

Il prossimo passo concreto è che , compatibile con il tuo better-sqlite3, e subito dopo invece di fare soltanto il test da riga di comando. ti preparo la modifica di db/init.js per le tre tabelleadattiamo face-rknn per produrre i record faces