功能架構
三個核心容器,外加一個 mDNS 服務,不需要獨立的訊息中介軟體,也不依賴雲端服務。照片透過上傳或從 NAS 目錄匯入,依次完成後設資料提取、縮圖生成、語意向量化、人臉與文字辨識,最終成為可按自然語言、人物和地點檢索的相簿。
Go 後端、AI 服務、PostgreSQL,和一個廣播區域網路網域的 mDNS
metadata、thumbnail、clip、face、ocr
Chinese-CLIP 圖文向量(按模型等級為 512、768 或 1024 維);ArcFace 人臉特徵為 512 維
pgvector 餘弦距離索引,搜尋和人臉比對在資料庫裡進行
系統架構
瀏覽器只連線 Go 後端,由後端負責上傳、任務排程、縮圖和權限管理,並將預覽圖交給 AI 服務推論,結果統一寫入 PostgreSQL。AI 服務無狀態,不存取資料庫、不儲存檔案,可獨立部署到效能更強的機器上。
網頁打包後嵌在程式裡,部署不需要 Node。啟動時自動執行資料庫遷移,升級就是換映像。
內建 AI 服務執行在 Docker 內部網路中,使用 CPU 推論;在網頁中填入遠端位址,即可切換到家中帶顯示卡的電腦或 Mac。
照片資訊、人物、設定、任務佇列和向量統一存入 PostgreSQL,應用資料與任務狀態可以一起備份;照片檔案需要另外備份。
照片處理處理管線
照片匯入後,先提取後設資料、生成縮圖,再平行計算語意向量、辨識人臉和文字。各步驟獨立入隊,重啟或 AI 服務掉線也不會丟失任務,恢復後繼續處理。
ingest- 網頁上傳走 tus,斷網能續傳
- /photos 目錄每 15 分鐘掃一遍
- 算 SHA-1 校驗和,寫進 assets
metadata- ExifTool 讀尺寸、拍攝時間
- GPS、相機、鏡頭、光圈快門
- 沒有時區的按設定的時區算
thumbnail- libvips 生成縮圖
- 再生成 1440px 預覽圖
- 影片用 FFmpeg 取幀、轉碼
clip- Chinese-CLIP 算 1024 維向量
- 自然語言搜尋、自動分類
- 美學評分、相似照片
face- SCRFD 找臉、5 個關鍵點對齊
- ArcFace 算 512 維特徵
- 增量歸到人物裡
ocr- PP-OCR 認出照片裡的字
- 按字搜照片
- 判斷證件、文件、螢幕截圖
後台任務排程
沒有獨立的訊息中介軟體:任務佇列就是 PostgreSQL 中的一張 jobs 表,與照片資料在同一事務中寫入,確保照片入庫、任務入隊。worker 透過 SKIP LOCKED 領取任務,用租約機制避免卡死,失敗後按平方退避重試。
-- 領取一個任務(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 排程
AI 服務只負責接收預覽圖,返回人臉、向量和文字。後端決定服務位址、模型和併發數;AI 服務根據硬體選擇 CUDA、CoreML / MPS、OpenVINO 或 CPU。
選「自動」時按 AI 伺服器的硬體推薦:有可用顯示卡用高精度;只有 CPU 時,記憶體不到 6 GB 用輕量,6 GB 及以上用均衡。
| 等級 | 適合的硬體 | 人臉 | 語意搜尋 |
|---|---|---|---|
| quality · 高精度 | Apple 晶片 Mac、NVIDIA 顯示卡 | SCRFD-10G + ArcFace R100(antelopev2) | Chinese-CLIP ViT-H/14 · 1024 維 |
| balanced · 均衡 | Intel i5 等級的 NAS | SCRFD-10G + ArcFace R100(antelopev2) | Chinese-CLIP ViT-L/14 · 768 維 |
| lite · 輕量 | N100、樹莓派 5 | SCRFD + ArcFace R50(buffalo_l) | Chinese-CLIP ViT-B/16 · 512 維 |
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"}
}核心演算法
搜尋、人物、連拍和 AI 精選背後用到的模型、演算法和閾值,有的靠 AI 模型,有的是向量計算和規則。
Chinese-CLIP 把照片和文字放進同一個向量空間:照片入庫時算好圖片向量,搜尋時把「寶寶三歲生日」算成文字向量,在 pgvector 的 HNSW 索引裡按餘弦距離找最近的照片。
- 三路結果合併:照片裡認出這些字的排最前,再是在這個地名拍的,最後是意思最像的。
- 只返回明顯對得上的:要和最像的那張差不多像,還要明顯比一般照片像,有助於過濾掉相關性弱的照片,而不是返回一堆沾邊的結果。
- 搜尋詞正好是分類名(風景、美食、螢幕截圖……)時直接返回這一類。
SCRFD 找到臉和 5 個關鍵點,按關鍵點對齊裁成 112×112,ArcFace 算出 512 維特徵(歸一化後點積就是餘弦相似度)。每張照片處理完就增量歸一次,不用等全部算完。
- 夠清楚、夠正、夠大的「好臉」找最像的臉:有已歸人物的按票數歸過去;都沒歸、但像的夠 3 張就新增一個人物。
- 模糊、側臉、太小的臉不新增人物,只在很像某個人時跟著歸進去,偶爾出現一次的路人不建。
- 幾個任務同時歸類時拿同一把資料庫諮詢鎖,不會把一個人建成兩個。封面挑清楚(拉普拉斯方差)、正臉(yaw)、夠大的單人臉。
- 同一場景接著拍的幾張 CLIP 相似度通常在 0.94–0.98,換了場景或姿勢一般在 0.7–0.89;以 0.93 作為相似度閾值,有助於找出可能是連拍的照片。
- 推薦保留的那張:有臉時看臉清楚、睜眼看鏡頭;沒臉時看檔案大小(同一台相機同樣設定,糊的照片細節少、壓出來小)。
- 同一個檔案存了幾份的單獨放進「重複」,不用挑。「保留」把其餘的放進最近刪除,在永久刪除之前都能恢復。
不另外跑模型,複用 CLIP 向量:用一組「攝影作品」的描述和一組「普通生活照」的描述各算平均向量,相減得到一個「好看」的方向,照片向量在這個方向上的投影就是它的分。
- 只用來排先後,不當絕對分數,首頁的精選輪播按它挑照片。
- 另有一組描述專門分出「拍壞了」的照片(糊、過曝之類),和「好不好看」分開算。
用到的技術
都是成熟的開源元件,沒有私有服務,換任何一塊都有替代品。
- GoAPI、任務佇列、縮圖排程
- libvips縮圖和預覽圖
- FFmpeg影片取幀、轉碼
- ExifTool讀寫照片後設資料
- tus斷點續傳上傳
- Python + FastAPI無狀態 HTTP 服務
- ONNX Runtime人臉、文字辨識推論
- PyTorchChinese-CLIP
- InsightFaceSCRFD + ArcFace
- RapidOCRPP-OCR 文字辨識
- PostgreSQL 17應用資料和任務佇列
- pgvector向量儲存
- HNSW餘弦距離索引
- ip2region登入日誌的 IP 位置(離線)
- Nominatim地點反查(可關閉)
- React + TypeScript網頁
- Vite + Tailwind CSS構建和樣式
- MapLibre GL衛星地球
- Docker Composeamd64 / arm64 映像
- mDNSwanmory.local 區域網路網域
隱私與安全
預設情況下,照片和 AI 結果都留在你的私有網路內。如果設定了遠端 AI 服務,預覽圖會傳送到該伺服器處理。
AI 服務只拿到 1440 像素的預覽圖,不儲存任何檔案,不接觸資料庫、人名和原圖。
內建 AI 服務只在 Docker 內部網路裡,只有後端連得到;遠端 AI 服務設了權杖後,每個請求都要帶 Bearer 權杖。
成員只能刪除自己上傳的照片,管理員能管全部;隱藏的照片只有自己看得到;登入日誌按 IP 顯示大概位置。
NAS 目錄可以用 :ro 只讀掛載,相簿改不了、刪不掉裡面的檔案。
模型下載完以後辨識和搜尋都在本地;地點反查、衛星圖這些需要聯網的功能可以關掉。
InsightFace 人臉模型僅限非商業用途;模型不進映像,下載前在設定頁展示每個模型的授權。