154 lines
3.9 KiB
Markdown
154 lines
3.9 KiB
Markdown
Perfetto. Ora abbiamo il quadro completo: SQLite tramite better-sqlite3 + Knex.
|
||
|
||
Per il tuo caso farei una piccola estensione del DB esistente, senza modificare la struttura di photos.
|
||
|
||
Schema che proporrei
|
||
|
||
photos
|
||
│
|
||
└── faces
|
||
│
|
||
├── embedding BLOB ← 128 × float32
|
||
├── cluster_id
|
||
└── person_id
|
||
│
|
||
└── persons
|
||
|
||
e separatamente:
|
||
|
||
clusters
|
||
|
||
Quindi:
|
||
|
||
photos 1 ─── N faces N ─── 1 clusters
|
||
│
|
||
└──── N ─── 1 persons
|
||
|
||
Con una distinzione fondamentale:
|
||
|
||
cluster_id = risultato dell'algoritmo HDBSCAN
|
||
|
||
person_id = identità assegnata dall'utente
|
||
|
||
un person_id può avere più cluster_id
|
||
|
||
un nuovo volto riconosciuto può avere person_id ma cluster_id = NULL
|
||
|
||
|
||
faces
|
||
|
||
Per esempio:
|
||
|
||
faces
|
||
-----
|
||
id
|
||
photo_id
|
||
x
|
||
y
|
||
width
|
||
height
|
||
embedding
|
||
cluster_id
|
||
person_id
|
||
created_at
|
||
updated_at
|
||
|
||
embedding sarebbe un BLOB SQLite contenente:
|
||
|
||
128 × float32 = 512 byte
|
||
|
||
Questo è adatto al tuo motore OpenCL: leggiamo il BLOB e lo trasformiamo direttamente in float*.
|
||
|
||
clusters
|
||
|
||
clusters
|
||
--------
|
||
id
|
||
run_id
|
||
label
|
||
size
|
||
created_at
|
||
|
||
run_id è utile perché HDBSCAN potrebbe essere rieseguito più volte durante la fase iniziale.
|
||
|
||
persons
|
||
|
||
persons
|
||
-------
|
||
id
|
||
user
|
||
name
|
||
created_at
|
||
updated_at
|
||
|
||
Poi aggiungerei una struttura per i prototype embedding, invece di obbligarci ad avere un solo embedding rappresentativo per persona.
|
||
|
||
Ad esempio:
|
||
|
||
person 7
|
||
├── prototype 1 → volto frontale
|
||
├── prototype 2 → profilo
|
||
├── prototype 3 → occhiali
|
||
└── prototype 4 → barba
|
||
|
||
Questo diventa molto utile per il riconoscimento incrementale.
|
||
|
||
|
||
---
|
||
|
||
Una scelta importante: niente embedding dei cluster
|
||
|
||
Non salverei inizialmente un unico:
|
||
|
||
cluster.embedding
|
||
|
||
come rappresentazione obbligatoria.
|
||
|
||
Abbiamo già tutti i face embedding:
|
||
|
||
faces.embedding
|
||
|
||
e possiamo calcolare i rappresentanti quando servono.
|
||
|
||
Per esempio, dopo che l'utente associa un cluster a una persona, possiamo selezionare alcuni embedding rappresentativi del cluster e inserirli nei prototype della persona.
|
||
|
||
|
||
---
|
||
|
||
Quindi il flusso completo diventa
|
||
|
||
┌──────────────┐
|
||
│ photos │
|
||
└──────┬───────┘
|
||
│
|
||
face detector
|
||
│
|
||
▼
|
||
┌──────────────┐
|
||
│ faces │
|
||
│ bbox │
|
||
│ embedding │
|
||
└──────┬───────┘
|
||
│
|
||
┌──────────┴──────────┐
|
||
│ │
|
||
inizializzazione nuove foto
|
||
│ │
|
||
▼ ▼
|
||
HDBSCAN prototype search
|
||
│ │
|
||
▼ ▼
|
||
clusters person_id
|
||
│
|
||
UI utente
|
||
│
|
||
▼
|
||
persons
|
||
|
||
E soprattutto questa struttura è compatibile con il benchmark OpenCL che stiamo facendo: il DB rimane il persistente, mentre indices[N×32], distanze, MST ecc. rimangono dati di lavoro GPU/CPU e non devono necessariamente finire in SQLite.
|
||
|
||
Prima di scrivere init.js
|
||
|
||
Mi manca solo una verifica: hai già codice che estrae i volti / genera gli embedding RKNN?
|
||
|
||
Se sì, mandami il file che attualmente gestisce quella parte. Così possiamo progettare faces attorno al formato reale dell'output RKNN e non creare una seconda rappresentazione incompatibile.
|