ARCHITECTURE

功能架构

三个核心容器,外加一个 mDNS 服务,不需要独立的消息中间件,也不依赖云服务。照片通过上传或从 NAS 目录导入,依次完成元数据提取、缩略图生成、语义向量化、人脸与文字识别,最终成为可按自然语言、人物和地点检索的相册。

3 + 1个容器

Go 后端、AI 服务、PostgreSQL,和一个广播局域网域名的 mDNS

5类后台任务

metadata、thumbnail、clip、face、ocr

512–1024维向量

Chinese-CLIP 图文向量(按模型档位为 512、768 或 1024 维);ArcFace 人脸特征为 512 维

HNSW向量索引

pgvector 余弦距离索引,搜索和人脸比对在数据库里进行

SYSTEM

系统架构

浏览器只连接 Go 后端,由后端负责上传、任务调度、缩略图和权限管理,并将预览图交给 AI 服务推理,结果统一写入 PostgreSQL。AI 服务无状态,不访问数据库、不存储文件,可独立部署到性能更强的机器上。

客户端电脑浏览器网页,内嵌在 Go 程序里手机浏览器iPhone · Androidwanmory.local局域网域名,不用记 IPHTTP :8080DOCKER COMPOSEGo 后端 · serverAPI、上传、任务队列、缩略图、权限,网页静态REST APItus 断点上传任务队列 Worker账号 · 登录日志libvips 缩略图ExifTool 元数据FFmpeg 视频地点反查(可选)相册 · 智能相册连拍 · 相似AI 精选 · 那年今天目录扫描 15 分钟mdns广播 wanmory.localAI 服务 · mlFastAPI + ONNX Runtime / PyTorch,无状态SCRFD 人脸检测ArcFace 人脸特征Chinese-CLIPPP-OCR 文字识别PostgreSQL 17 + pgvector业务数据、任务队列、向量都在一个库里,不需要 Redisassetsfaces · peoplejobs 队列settingsHNSW 索引SQL/predict · 1440px 预览图存储 · 扩展/data上传的原图、缩略图、1440px 预览图/photos/*(可以只读 :ro)NAS 上已有的照片目录,可以挂多个./models模型文件,在网页设置里下载远程 AI 服务(可选)Mac(CoreML / MPS)· NVIDIA CUDA令牌鉴权,只收 1440px 预览图,不碰原图读写扫描 · 只读加载HTTP + 令牌
后端只有一个 Go 程序

网页打包后嵌在程序里,部署不需要 Node。启动时自动执行数据库迁移,升级就是换镜像。

AI 服务异地部署

内置 AI 服务运行在 Docker 内部网络中,使用 CPU 推理;在网页中填入远程地址,即可切换到家中带显卡的电脑或 Mac。

一个数据库,统一存储所有数据

照片信息、人物、设置、任务队列和向量统一存入 PostgreSQL,业务数据与任务状态可以一起备份;照片文件需要另外备份。

PIPELINE

照片处理流水线

照片导入后,先提取元数据、生成缩略图,再并行计算语义向量、识别人脸和文字。各步骤独立入队,重启或 AI 服务掉线也不会丢失任务,恢复后继续处理。

01ingest
导入
  • 网页上传走 tus,断网能续传
  • /photos 目录每 15 分钟扫一遍
  • 算 SHA-1 校验和,写进 assets
后端
02metadata
元数据
  • ExifTool 读尺寸、拍摄时间
  • GPS、相机、镜头、光圈快门
  • 没有时区的按设置的时区算
后端
03thumbnail
缩略图
  • libvips 生成缩略图
  • 再生成 1440px 预览图
  • 视频用 FFmpeg 取帧、转码
后端
04clip
语义向量
  • Chinese-CLIP 算 1024 维向量
  • 自然语言搜索、自动分类
  • 美学评分、相似照片
AI · 并行
05face
人脸
  • SCRFD 找脸、5 个关键点对齐
  • ArcFace 算 512 维特征
  • 增量归到人物里
AI · 并行
06ocr
文字
  • PP-OCR 认出照片里的字
  • 按字搜照片
  • 判断证件、文档、截图
AI · 并行
SCHEDULING

后台任务调度

没有独立的消息中间件:任务队列就是 PostgreSQL 中的一张 jobs 表,与照片数据在同一事务中写入,确保照片入库、任务入队。worker 通过 SKIP LOCKED 领取任务,用租期机制避免卡死,失败后按平方退避重试。

pending排队中running被 worker 领走done完成failed次数用完SKIP LOCKED 领取成功重试次数用完失败:等 n² × 30 秒再试AI 连不上:15 秒后再试,不算失败租期 10 分钟:超时没交回结果,每 30 秒回收一次,退回 pending
jobs/worker.go · claim
-- 领取一个任务(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   -- 多个 worker 同时领也不会抢到同一个
  LIMIT 1
)
RETURNING id, type, asset_id, payload, attempts, max_attempts;
调度规则
  • 同一张照片的同一类任务已经在排队或运行时不再重复入队。
  • 队列空时每秒查一次;按 priority 从高到低、到点时间从早到晚领。
  • 每个任务的租期(也是超时)10 分钟;进程重启时把自己名下没做完的任务退回队列。
让路和限流
  • 有人在上传照片时,AI 任务暂停领取,带宽和 CPU 先给上传。
  • AI 服务连不上时整个 AI worker 停下等,不会把任务一个个领出来失败、白白用掉重试次数。
  • 「自动」档位会先检测 AI 服务器硬件,选定合适的模型后再领取任务,有助于避免在树莓派等设备上加载超出可用内存的模型。
AI SCHEDULING

AI 调度

AI 服务只负责接收预览图,返回人脸、向量和文字。后端决定服务地址、模型和并发数;AI 服务根据硬件选择 CUDA、CoreML / MPS、OpenVINO 或 CPU。

AI 任务队列clip · face · ocr并发数按设置,限流可调Go 后端 · AI 客户端选服务 · 选档位 · 核对 model_ids· 每 15 秒检查一次 /health· 连不上 → 任务留在队列等· 档位自动 → 按硬件推荐领取内置 AI 服务Docker 内部网络 · CPU远程 AI 服务家里的 Mac · 显卡电脑远程工作节点主动领取任务(规划中)二选一CPUONNX RuntimeCoreML / MPS · CUDA自动挑可用的加速OpenVINOIntel 核显
三档模型组合

选「自动」时按 AI 服务器的硬件推荐:有可用显卡用高精度;只有 CPU 时,内存不到 6 GB 用轻量,6 GB 及以上用均衡。

档位适合的硬件人脸语义搜索
quality · 高精度Apple 芯片 Mac、NVIDIA 显卡SCRFD-10G + ArcFace R100(antelopev2)Chinese-CLIP ViT-H/14 · 1024 维
balanced · 均衡Intel i5 级别的 NASSCRFD-10G + ArcFace R100(antelopev2)Chinese-CLIP ViT-L/14 · 768 维
lite · 轻量N100、树莓派 5SCRFD + ArcFace R50(buffalo_l)Chinese-CLIP ViT-B/16 · 512 维
AI 服务接口 · /predict
POST /predict   (multipart: image + tasks)
Authorization: Bearer <令牌>

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 个数], "yaw": 0.12, "blur": 340}],
  "clip": {"embedding": [1024 个数]},
  "ocr":  {"text": "...", "lines": [...]},
  "model_ids": {"face": "antelopev2", "clip": "chinese-clip-vit-h-14"}
}
ALGORITHMS

核心算法

搜索、人物、连拍和 AI 精选背后用到的模型、算法和阈值,有的靠 AI 模型,有的是向量计算和规则。

自然语言搜索

Chinese-CLIP 把照片和文字放进同一个向量空间:照片入库时算好图片向量,搜索时把「宝宝三岁生日」算成文字向量,在 pgvector 的 HNSW 索引里按余弦距离找最近的照片。

sim(q, p) = q · p / (‖q‖ ‖p‖)
  • 三路结果合并:照片里认出这些字的排最前,再是在这个地名拍的,最后是意思最像的。
  • 只返回明显对得上的:要和最像的那张差不多像,还要明显比一般照片像,有助于过滤掉相关性弱的照片,而不是返回一堆沾边的结果。
  • 搜索词正好是分类名(风景、美食、截图……)时直接返回这一类。
人脸归类

SCRFD 找到脸和 5 个关键点,按关键点对齐裁成 112×112,ArcFace 算出 512 维特征(归一化后点积就是余弦相似度)。每张照片处理完就增量归一次,不用等全部算完。

d(a, b) = 1 − a · b  好脸 d < 0.55 · 模糊侧脸 d < 0.45
  • 够清楚、够正、够大的「好脸」找最像的脸:有已归人物的按票数归过去;都没归、但像的够 3 张就新建一个人物。
  • 模糊、侧脸、太小的脸不新建人物,只在很像某个人时跟着归进去,偶尔出现一次的路人不建。
  • 几个任务同时归类时拿同一把数据库咨询锁,不会把一个人建成两个。封面挑清楚(拉普拉斯方差)、正脸(yaw)、够大的单人脸。
连拍与相似
同一相机 Δt ≤ 2s 或 Δt ≤ 60s 且 sim ≥ 0.93
  • 同一场景接着拍的几张 CLIP 相似度通常在 0.94–0.98,换了场景或姿势一般在 0.7–0.89;以 0.93 作为相似度阈值,有助于找出可能是连拍的照片。
  • 推荐保留的那张:有脸时看脸清楚、睁眼看镜头;没脸时看文件大小(同一台相机同样设置,糊的照片细节少、压出来小)。
  • 同一个文件存了几份的单独放进「重复」,不用挑。「保留」把其余的放进最近删除,在永久删除之前都能恢复。
AI 精选的美学分

不另外跑模型,复用 CLIP 向量:用一组「摄影作品」的描述和一组「普通生活照」的描述各算平均向量,相减得到一个「好看」的方向,照片向量在这个方向上的投影就是它的分。

score(p) = p · (μ作品 − μ生活照)
  • 只用来排先后,不当绝对分数,首页的精选轮播按它挑照片。
  • 另有一组描述专门分出「拍坏了」的照片(糊、过曝之类),和「好不好看」分开算。
TECH STACK

用到的技术

都是成熟的开源组件,没有私有服务,换任何一块都有替代品。

后端
  • Go
    API、任务队列、缩略图调度
  • libvips
    缩略图和预览图
  • FFmpeg
    视频取帧、转码
  • ExifTool
    读写照片元数据
  • tus
    断点续传上传
AI 服务
  • Python + FastAPI
    无状态 HTTP 服务
  • ONNX Runtime
    人脸、文字识别推理
  • PyTorch
    Chinese-CLIP
  • InsightFace
    SCRFD + ArcFace
  • RapidOCR
    PP-OCR 文字识别
数据
  • PostgreSQL 17
    业务数据和任务队列
  • pgvector
    向量存储
  • HNSW
    余弦距离索引
  • ip2region
    登录日志的 IP 位置(离线)
  • Nominatim
    地点反查(可关闭)
前端与部署
  • React + TypeScript
    网页
  • Vite + Tailwind CSS
    构建和样式
  • MapLibre GL
    卫星地球
  • Docker Compose
    amd64 / arm64 镜像
  • mDNS
    wanmory.local 局域网域名
PRIVACY

隐私与安全

默认情况下,照片和 AI 结果都留在你的私有网络内。如果配置了远程 AI 服务,预览图会发送到该服务器处理。

最少数据

AI 服务只拿到 1440 像素的预览图,不保存任何文件,不接触数据库、人名和原图。

鉴权

内置 AI 服务只在 Docker 内部网络里,只有后端连得到;远程 AI 服务设了令牌后,每个请求都要带 Bearer 令牌。

权限

成员只能删除自己上传的照片,管理员能管全部;隐藏的照片只有自己看得到;登录日志按 IP 显示大概位置。

原文件只读

NAS 目录可以用 :ro 只读挂载,相册改不了、删不掉里面的文件。

不联网也能用

模型下载完以后识别和搜索都在本地;地点反查、卫星图这些需要联网的功能可以关掉。

模型许可

InsightFace 人脸模型仅限非商业用途;模型不进镜像,下载前在设置页展示每个模型的许可。