Architecture
Trois conteneurs principaux, plus un service mDNS, sans courtier de messages séparé ni service cloud obligatoire. Les photos sont importées par envoi ou depuis des dossiers du NAS, puis traitées pour les métadonnées, les miniatures, les vecteurs sémantiques, les visages et le texte. Le résultat : une photothèque interrogeable en langage naturel, par personne et par lieu.
Backend Go, service IA et PostgreSQL, plus un service mDNS qui annonce le domaine local
metadata, thumbnail, clip, face, ocr
Vecteurs image-texte Chinese-CLIP (512, 768 ou 1024 selon le niveau de modèles) ; les empreintes de visage ArcFace font 512 dimensions
Index pgvector en distance cosinus ; recherche et comparaison des visages se font dans la base
Architecture du système
Le navigateur ne parle qu’au backend Go. Celui-ci gère les imports, l’ordonnancement des tâches, les miniatures et les droits, envoie les aperçus au service IA pour l’inférence et écrit tous les résultats dans PostgreSQL. Le service IA est sans état : il n’accède jamais à la base et ne stocke aucun fichier, il peut donc tourner sur une machine plus puissante.
L’application web est intégrée au binaire : le déploiement ne nécessite pas Node. Les migrations de la base s’exécutent automatiquement au démarrage, et mettre à jour revient à changer d’image.
Le service IA intégré tourne sur le réseau interne de Docker et calcule sur le processeur. Saisissez une adresse distante dans les réglages web pour passer à un PC avec carte graphique ou à un Mac à la maison.
Informations des photos, personnes, réglages, file de tâches et vecteurs sont tous dans PostgreSQL : les données applicatives et l’état des tâches peuvent être sauvegardés ensemble. Les fichiers photo nécessitent une sauvegarde à part.
Pipeline de traitement des photos
Après l’import, chaque photo voit d’abord ses métadonnées extraites et ses miniatures générées, puis ses vecteurs sémantiques, visages et textes sont calculés en parallèle. Chaque étape est mise en file séparément : un redémarrage ou une panne du service IA ne fait perdre aucune tâche, et le traitement reprend ensuite.
ingest- Imports web via tus, reprise après coupure
- Dossiers /photos analysés toutes les 15 minutes
- Somme SHA-1 calculée et enregistrée dans assets
metadata- ExifTool lit dimensions et date de prise
- GPS, appareil, objectif, ouverture et vitesse
- Heures sans fuseau : fuseau configuré
thumbnail- libvips génère les miniatures
- Puis un aperçu de 1440 px
- FFmpeg extrait les images et transcode les vidéos
clip- Chinese-CLIP calcule des vecteurs 1024-d
- Recherche en langage naturel, catégories
- Note esthétique, photos similaires
face- SCRFD détecte et aligne sur 5 points
- ArcFace calcule des empreintes 512-d
- Regroupement progressif en personnes
ocr- PP-OCR lit le texte des photos
- Rechercher des photos par leur texte
- Repère papiers, documents, captures
Ordonnancement des tâches de fond
Pas de courtier de messages séparé : la file de tâches est une table jobs dans PostgreSQL, écrite dans la même transaction que les données de la photo. Une photo enregistrée a donc toujours ses tâches en file. Les workers prennent les tâches avec SKIP LOCKED, un bail évite les blocages, et les échecs sont relancés avec un délai quadratique.
-- Prendre une tâche (server/internal/jobs/worker.go)
UPDATE jobs SET status = 'running', attempts = attempts + 1,
locked_by = $1, locked_until = now() + $2::interval
WHERE id = (
SELECT id FROM jobs
WHERE status = 'pending' AND run_after <= now() AND type = ANY($3)
ORDER BY priority DESC, run_after
FOR UPDATE SKIP LOCKED -- des workers simultanés ne prennent jamais la même
LIMIT 1
)
RETURNING id, type, asset_id, payload, attempts, max_attempts;- Une tâche du même type pour la même photo n’est jamais mise en file deux fois tant qu’une autre attend ou s’exécute.
- Une file vide est consultée chaque seconde ; les tâches sont prises par priorité décroissante, puis par échéance la plus proche.
- Le bail de chaque tâche (qui sert aussi de délai maximal) est de 10 minutes ; au redémarrage, un processus remet en file ses propres tâches inachevées.
- Pendant qu’une personne importe des photos, plus aucune tâche IA n’est prise afin de laisser la bande passante et le processeur à l’import.
- Quand le service IA est injoignable, tout le worker IA se met en pause au lieu de prendre les tâches une à une pour les faire échouer et gaspiller les relances.
- Le niveau « Automatique » détecte d’abord le matériel du serveur IA et choisit des modèles adaptés avant de prendre des tâches, ce qui aide à éviter de charger sur un Raspberry Pi ou un appareil similaire des modèles qui dépassent la mémoire disponible.
Pilotage de l’IA
Le service IA reçoit uniquement des aperçus et renvoie visages, vecteurs et texte. Le backend décide de l’adresse du service, des modèles et du parallélisme ; le service IA choisit CUDA, CoreML / MPS, OpenVINO ou le processeur selon son matériel.
En « Automatique », le niveau dépend du matériel du serveur IA : Précision avec une carte graphique exploitable ; sur processeur seul, Léger sous 6 Go de mémoire et Équilibré à partir de 6 Go.
| Niveau | Matériel adapté | Visages | Recherche sémantique |
|---|---|---|---|
| quality · Précision | Mac Apple Silicon, GPU NVIDIA | SCRFD-10G + ArcFace R100 (antelopev2) | Chinese-CLIP ViT-H/14 · 1024-d |
| balanced · Équilibré | NAS de classe Intel i5 | SCRFD-10G + ArcFace R100 (antelopev2) | Chinese-CLIP ViT-L/14 · 768-d |
| lite · Léger | N100, Raspberry Pi 5 | SCRFD + ArcFace R50 (buffalo_l) | Chinese-CLIP ViT-B/16 · 512-d |
POST /predict (multipart: image + tasks)
Authorization: Bearer <jeton>
tasks = {
"face": {"model": "antelopev2", "min_score": 0.5},
"clip": {"model": "chinese-clip-vit-h-14"},
"ocr": {}
}
→ {
"face": [{"box": [x, y, w, h], "score": 0.93,
"embedding": [512 nombres], "yaw": 0.12, "blur": 340}],
"clip": {"embedding": [1024 nombres]},
"ocr": {"text": "...", "lines": [...]},
"model_ids": {"face": "antelopev2", "clip": "chinese-clip-vit-h-14"}
}Algorithmes clés
Les modèles, algorithmes et seuils derrière la recherche, les personnes, les rafales et la sélection IA. Certains reposent sur des modèles d’IA, d’autres sur du calcul vectoriel et des règles.
Chinese-CLIP place photos et texte dans le même espace vectoriel. Les vecteurs d’image sont calculés à l’import ; au moment de la recherche, une phrase comme « troisième anniversaire de bébé » devient un vecteur de texte, et les photos les plus proches sont trouvées par distance cosinus dans l’index HNSW de pgvector.
- Trois sources de résultats sont fusionnées : d’abord les photos où les mots cherchés ont été reconnus, puis celles prises en un lieu correspondant, enfin les plus proches par le sens.
- Seules les correspondances nettes sont renvoyées : un résultat doit être presque aussi proche que le meilleur et nettement plus proche qu’une photo moyenne. Cela aide à écarter les correspondances faibles au lieu de renvoyer des photos vaguement liées.
- Si la requête est exactement un nom de catégorie (paysages, cuisine, captures d’écran…), cette catégorie est renvoyée directement.
SCRFD détecte chaque visage et ses 5 points caractéristiques, l’aligne et le recadre en 112×112, puis ArcFace calcule une empreinte de 512 dimensions (après normalisation, le produit scalaire est la similarité cosinus). Le regroupement se fait au fil de l’eau, photo après photo, sans attendre la fin du calcul.
- Les « bons visages », nets, de face et assez grands, cherchent leurs plus proches voisins : si certains appartiennent déjà à une personne, le visage la rejoint à la majorité ; si aucun n’est classé mais qu’au moins 3 se ressemblent, une nouvelle personne est créée.
- Les visages flous, de profil ou trop petits ne créent jamais de personne ; ils rejoignent seulement quelqu’un à qui ils ressemblent beaucoup. Les passants vus une seule fois ne sont pas ajoutés.
- Les regroupements simultanés partagent un même verrou consultatif de la base, si bien qu’une personne n’est jamais créée en double. Les vignettes privilégient les visages seuls nets (variance du laplacien), de face (lacet) et assez grands.
- Des prises successives de la même scène ont en général une similarité CLIP de 0,94 à 0,98, tandis qu’un changement de scène ou de pose tombe le plus souvent entre 0,7 et 0,89 ; un seuil de similarité de 0,93 aide à repérer les rafales probables.
- La photo conseillée : avec des visages, celle où les visages sont nets, les yeux ouverts tournés vers l’objectif ; sans visage, le fichier le plus lourd (avec le même appareil et les mêmes réglages, une photo floue a moins de détails et se compresse davantage).
- Les copies multiples d’un même fichier vont dans « Doublons », sans choix à faire. « Garder » déplace le reste vers « Supprimées récemment », d’où il reste restaurable jusqu’à sa suppression définitive.
Aucun modèle supplémentaire n’est lancé : les vecteurs CLIP sont réutilisés. En moyennant les vecteurs d’un ensemble de descriptions d’« œuvres photographiques » et d’un ensemble décrivant des « photos ordinaires », puis en les soustrayant, on obtient une direction « réussie ». La projection d’une photo sur cette direction est sa note.
- Sert uniquement à classer, jamais de note absolue ; le carrousel de sélection de l’accueil choisit ses photos selon elle.
- Un autre ensemble de descriptions repère les photos ratées (floues, surexposées, etc.), évaluées indépendamment de leur qualité esthétique.
Technologies utilisées
Uniquement des composants open source éprouvés, sans service propriétaire ; chaque brique a son équivalent.
- GoAPI, file de tâches, planification des miniatures
- libvipsMiniatures et aperçus
- FFmpegExtraction d’images vidéo, transcodage
- ExifToolLecture et écriture des métadonnées
- tusImports avec reprise
- Python + FastAPIService HTTP sans état
- ONNX RuntimeInférence visages et texte
- PyTorchChinese-CLIP
- InsightFaceSCRFD + ArcFace
- RapidOCRReconnaissance de texte PP-OCR
- PostgreSQL 17Données applicatives et file de tâches
- pgvectorStockage des vecteurs
- HNSWIndex en distance cosinus
- ip2regionPosition IP des connexions (hors ligne)
- NominatimGéocodage inverse (désactivable)
- React + TypeScriptApplication web
- Vite + Tailwind CSSBuild et styles
- MapLibre GLGlobe satellite
- Docker ComposeImages amd64 / arm64
- mDNSDomaine local wanmory.local
Confidentialité et sécurité
Par défaut, les photos et les résultats de l’IA restent dans votre réseau privé. Si vous configurez un service IA distant, les aperçus sont envoyés à ce serveur pour être traités.
Le service IA ne reçoit que des aperçus de 1440 pixels, ne stocke aucun fichier et n’accède ni à la base, ni aux noms, ni aux originaux.
Le service IA intégré n’existe que sur le réseau interne de Docker, joignable par le seul backend ; dès qu’un jeton est défini pour un service IA distant, chaque requête doit porter un jeton Bearer.
Les membres ne peuvent supprimer que leurs propres imports, les administrateurs gèrent tout ; les photos masquées ne sont visibles que de leur propriétaire ; l’historique des connexions indique une position approximative d’après l’IP.
Les dossiers du NAS peuvent être montés en lecture seule avec :ro, l’application ne peut alors ni modifier ni supprimer leurs fichiers.
Une fois les modèles téléchargés, reconnaissance et recherche se font en local ; les fonctions qui exigent Internet, comme le géocodage inverse et les images satellite, peuvent être désactivées.
Les modèles de visages InsightFace sont réservés à un usage non commercial ; les modèles ne sont pas inclus dans l’image, et la licence de chacun est affichée dans les réglages avant téléchargement.