Arquitectura
Tres contenedores principales, más un servicio mDNS, sin un intermediario de mensajes aparte ni servicios en la nube obligatorios. Las fotos se suben o se importan desde carpetas del NAS y luego se procesan para obtener metadatos, miniaturas, vectores semánticos, caras y texto. El resultado es una fototeca que se puede buscar por lenguaje natural, persona y lugar.
Backend en Go, servicio de IA y PostgreSQL, más un servicio mDNS que anuncia el dominio local
metadata, thumbnail, clip, face, ocr
Vectores imagen-texto de Chinese-CLIP (512, 768 o 1024 según el nivel de modelos); los rasgos faciales de ArcFace tienen 512 dimensiones
Índice de distancia coseno de pgvector; la búsqueda y la comparación de caras ocurren en la base de datos
Arquitectura del sistema
El navegador solo habla con el backend en Go, que se encarga de las subidas, la planificación de tareas, las miniaturas y los permisos, envía las vistas previas al servicio de IA para la inferencia y escribe todos los resultados en PostgreSQL. El servicio de IA no tiene estado: nunca accede a la base de datos ni guarda archivos, así que puede ejecutarse en un equipo más potente.
La app web va incrustada en el binario, así que la instalación no necesita Node. Las migraciones de la base de datos se ejecutan solas al arrancar, y actualizar es cambiar la imagen.
El servicio de IA integrado funciona en la red interna de Docker y calcula con la CPU. Escribe una dirección remota en los ajustes web para pasar a un PC con tarjeta gráfica o a un Mac de casa.
La información de las fotos, las personas, los ajustes, la cola de tareas y los vectores están todos en PostgreSQL, así que los datos de la app y el estado de las tareas se pueden copiar juntos. Los archivos de fotos necesitan una copia de seguridad aparte.
Flujo de procesamiento de fotos
Tras importar una foto, primero se extraen sus metadatos y se generan las miniaturas; luego se calculan en paralelo los vectores semánticos, las caras y el texto. Cada paso entra en la cola por separado, así que un reinicio o una caída del servicio de IA no pierde trabajo: el procesamiento sigue cuando todo vuelve.
ingest- Subidas web con tus, se reanudan tras cortes
- Carpetas /photos escaneadas cada 15 minutos
- Suma SHA-1 calculada y guardada en assets
metadata- ExifTool lee dimensiones y fecha de toma
- GPS, cámara, objetivo, diafragma y velocidad
- Horas sin zona usan la zona configurada
thumbnail- libvips genera las miniaturas
- Y una vista previa de 1440 px
- FFmpeg extrae fotogramas y transcodifica vídeo
clip- Chinese-CLIP calcula vectores de 1024-d
- Búsqueda en lenguaje natural, categorías
- Puntuación estética, fotos similares
face- SCRFD detecta y alinea con 5 puntos
- ArcFace calcula rasgos de 512-d
- Agrupadas en personas poco a poco
ocr- PP-OCR lee el texto de las fotos
- Buscar fotos por su texto
- Detecta documentos, carnés, capturas
Planificación de tareas en segundo plano
No hay un intermediario de mensajes aparte: la cola de tareas es una tabla jobs en PostgreSQL, escrita en la misma transacción que los datos de la foto, así que una foto guardada siempre tiene sus tareas en la cola. Los workers toman tareas con SKIP LOCKED, los arrendamientos evitan bloqueos y los fallos se reintentan con espera cuadrática.
-- Tomar una tarea (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 -- workers simultáneos nunca toman la misma
LIMIT 1
)
RETURNING id, type, asset_id, payload, attempts, max_attempts;- Una tarea del mismo tipo para la misma foto nunca se encola dos veces mientras otra espera o se ejecuta.
- Con la cola vacía se consulta cada segundo; las tareas se toman por prioridad (la más alta primero) y luego por vencimiento (la más temprana primero).
- El arrendamiento de cada tarea (que también es su tiempo límite) es de 10 minutos; al reiniciarse, un proceso devuelve a la cola sus propias tareas sin terminar.
- Mientras alguien sube fotos, no se toman tareas de IA, para que el ancho de banda y la CPU vayan primero a la subida.
- Si el servicio de IA no responde, todo el worker de IA se detiene en lugar de tomar tareas una a una, hacerlas fallar y gastar reintentos.
- El nivel «Automático» detecta primero el hardware del servidor de IA y elige modelos adecuados antes de tomar tareas, lo que ayuda a no cargar en una Raspberry Pi o similar modelos que superen la memoria disponible.
Planificación de la IA
El servicio de IA solo recibe vistas previas y devuelve caras, vectores y texto. El backend decide la dirección del servicio, los modelos y la concurrencia; el servicio de IA elige CUDA, CoreML / MPS, OpenVINO o CPU según su hardware.
Con «Automático», el nivel se elige según el hardware del servidor de IA: Alta precisión con una tarjeta gráfica utilizable; solo con CPU, Ligero con menos de 6 GB de memoria y Equilibrado con 6 GB o más.
| Nivel | Hardware adecuado | Caras | Búsqueda semántica |
|---|---|---|---|
| quality · Alta precisión | Mac con Apple Silicon, GPU NVIDIA | SCRFD-10G + ArcFace R100 (antelopev2) | Chinese-CLIP ViT-H/14 · 1024-d |
| balanced · Equilibrado | NAS de clase Intel i5 | SCRFD-10G + ArcFace R100 (antelopev2) | Chinese-CLIP ViT-L/14 · 768-d |
| lite · Ligero | N100, Raspberry Pi 5 | SCRFD + ArcFace R50 (buffalo_l) | Chinese-CLIP ViT-B/16 · 512-d |
POST /predict (multipart: image + tasks)
Authorization: Bearer <token>
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 números], "yaw": 0.12, "blur": 340}],
"clip": {"embedding": [1024 números]},
"ocr": {"text": "...", "lines": [...]},
"model_ids": {"face": "antelopev2", "clip": "chinese-clip-vit-h-14"}
}Algoritmos clave
Los modelos, algoritmos y umbrales detrás de la búsqueda, las personas, las ráfagas y la selección de la IA. Unos se apoyan en modelos de IA y otros en cálculo vectorial y reglas.
Chinese-CLIP coloca fotos y texto en el mismo espacio vectorial. Los vectores de imagen se calculan al importar; al buscar, una frase como «tercer cumpleaños del bebé» se convierte en un vector de texto y las fotos más cercanas se encuentran por distancia coseno en el índice HNSW de pgvector.
- Se combinan tres fuentes de resultados: primero las fotos en las que se reconocieron las palabras buscadas, luego las tomadas en un lugar con ese nombre y por último las más parecidas en significado.
- Solo se devuelven coincidencias claras: un resultado debe estar casi tan cerca como el mejor y claramente más cerca que una foto cualquiera. Así se filtran las coincidencias débiles en lugar de devolver fotos poco relacionadas.
- Si la búsqueda es exactamente el nombre de una categoría (paisajes, comida, capturas de pantalla…), se devuelve esa categoría directamente.
SCRFD encuentra cada cara y sus 5 puntos de referencia, la alinea y recorta a 112×112, y ArcFace calcula un vector de 512 dimensiones (tras normalizarlo, el producto escalar es la similitud coseno). La agrupación se hace poco a poco al terminar cada foto, sin esperar a toda la fototeca.
- Las «caras buenas», nítidas, de frente y de tamaño suficiente, buscan sus vecinas más parecidas: si algunas ya pertenecen a una persona, se une a ella por mayoría; si ninguna está asignada pero al menos 3 se parecen, se crea una persona nueva.
- Las caras borrosas, de perfil o muy pequeñas nunca crean una persona; solo se unen a alguien al que se parecen mucho. Los transeúntes que aparecen una vez no se añaden.
- Las agrupaciones simultáneas comparten un mismo bloqueo consultivo de la base de datos, así que nunca se crea la misma persona dos veces. Las portadas prefieren caras individuales nítidas (varianza del laplaciano), de frente (guiñada) y grandes.
- Las tomas seguidas de una misma escena suelen tener una similitud CLIP de 0,94 a 0,98, mientras que un cambio de escena o de pose suele quedar entre 0,7 y 0,89; un umbral de similitud de 0,93 ayuda a identificar las probables ráfagas.
- La foto recomendada: con caras, aquella en la que las caras se ven nítidas, con los ojos abiertos y mirando a cámara; sin caras, el archivo más grande (con la misma cámara y ajustes, las fotos movidas tienen menos detalle y se comprimen más).
- Las copias repetidas de un mismo archivo van a «Duplicados», sin nada que elegir. «Conservar» mueve el resto a «Eliminadas recientemente», desde donde se puede recuperar hasta que se elimine definitivamente.
No se ejecuta ningún modelo adicional: se reutilizan los vectores CLIP. Promediando los vectores de un conjunto de descripciones de «obras fotográficas» y de otro de «fotos cotidianas» y restándolos se obtiene una dirección de «queda bien». La proyección de una foto sobre esa dirección es su puntuación.
- Solo sirve para ordenar, nunca como nota absoluta; el carrusel de selecciones de la vista principal elige las fotos según ella.
- Otro conjunto de descripciones identifica las fotos fallidas (movidas, sobreexpuestas y similares), con una puntuación independiente de lo bien que se vea una foto.
Tecnologías utilizadas
Todo son componentes de código abierto maduros, sin servicios propietarios; cada pieza tiene alternativa.
- GoAPI, cola de tareas, planificación de miniaturas
- libvipsMiniaturas y vistas previas
- FFmpegFotogramas de vídeo, transcodificación
- ExifToolLectura y escritura de metadatos
- tusSubidas reanudables
- Python + FastAPIServicio HTTP sin estado
- ONNX RuntimeInferencia de caras y texto
- PyTorchChinese-CLIP
- InsightFaceSCRFD + ArcFace
- RapidOCRReconocimiento de texto PP-OCR
- PostgreSQL 17Datos de la app y cola de tareas
- pgvectorAlmacenamiento de vectores
- HNSWÍndice de distancia coseno
- ip2regionUbicación por IP de los accesos (sin conexión)
- NominatimGeocodificación inversa (desactivable)
- React + TypeScriptApp web
- Vite + Tailwind CSSCompilación y estilos
- MapLibre GLGlobo satelital
- Docker ComposeImágenes amd64 / arm64
- mDNSDominio local wanmory.local
Privacidad y seguridad
De forma predeterminada, las fotos y los resultados de la IA se quedan dentro de tu red privada. Si configuras un servicio de IA remoto, las vistas previas se envían a ese servidor para procesarlas.
El servicio de IA solo recibe vistas previas de 1440 píxeles, no guarda ningún archivo y nunca toca la base de datos, los nombres ni los originales.
El servicio de IA integrado solo existe en la red interna de Docker y únicamente el backend llega a él; cuando se define un token para un servicio de IA remoto, cada petición debe llevar un token Bearer.
Los miembros solo pueden borrar las fotos que subieron y los administradores gestionan todo; las fotos ocultas solo las ve su dueño; el historial de accesos muestra una ubicación aproximada por IP.
Las carpetas del NAS se pueden montar en solo lectura con :ro, así que la app no puede modificar ni borrar sus archivos.
Tras descargar los modelos, el reconocimiento y la búsqueda se hacen en local; las funciones que necesitan internet, como la geocodificación inversa y las imágenes de satélite, se pueden desactivar.
Los modelos faciales de InsightFace son solo para uso no comercial; los modelos no van incluidos en la imagen y los ajustes muestran la licencia de cada uno antes de descargarlo.