ARCHITECTURE

Kiến trúc

Ba container cốt lõi cùng một dịch vụ mDNS, không cần trình trung gian tin nhắn riêng hay dịch vụ đám mây bắt buộc nào. Ảnh được tải lên hoặc nhập từ thư mục trên NAS, rồi được xử lý để lấy siêu dữ liệu, ảnh thu nhỏ, vector ngữ nghĩa, khuôn mặt và chữ. Kết quả là một thư viện ảnh tìm được theo ngôn ngữ tự nhiên, theo người và theo địa điểm.

3 + 1container

Backend Go, dịch vụ AI, PostgreSQL và một dịch vụ mDNS phát tên miền nội bộ

5loại tác vụ nền

metadata, thumbnail, clip, face, ocr

512–1024chiều vector

Vector ảnh–chữ của Chinese-CLIP (512, 768 hoặc 1024 chiều tùy mức mô hình); đặc trưng khuôn mặt ArcFace là 512 chiều

HNSWchỉ mục vector

Chỉ mục khoảng cách cosin của pgvector; tìm kiếm và so khớp khuôn mặt chạy ngay trong cơ sở dữ liệu

SYSTEM

Kiến trúc hệ thống

Trình duyệt chỉ kết nối với backend Go. Backend lo việc tải lên, điều phối tác vụ, ảnh thu nhỏ và phân quyền, gửi ảnh xem trước cho dịch vụ AI suy luận rồi ghi mọi kết quả vào PostgreSQL. Dịch vụ AI không giữ trạng thái: không truy cập cơ sở dữ liệu, không lưu tệp, nên có thể tách ra chạy trên máy mạnh hơn.

MÁY KHÁCHTrình duyệt máy tínhỨng dụng web nhúng trong chương trình GoTrình duyệt điện thoạiiPhone · Androidwanmory.localTên miền nội bộ, khỏi nhớ IPHTTP :8080DOCKER COMPOSEBackend Go · serverAPI, tải lên, hàng đợi, ảnh thu nhỏ, phân quyền, webREST APITải lên tusWorker tác vụTài khoản · Đăng nhậpẢnh nhỏ libvipsSiêu dữ liệu ExifToolVideo FFmpegTra địa điểmAlbum · Thông minhLiên tiếp · GiốngAI chọn · Năm xưaQuét mỗi 15 phútmdnsPhát wanmory.localDịch vụ AI · mlFastAPI + ONNX Runtime / PyTorch, không trạng tháiPhát hiện SCRFDĐặc trưng ArcFaceChinese-CLIPNhận dạng chữ PP-OCRPostgreSQL 17 + pgvectorDữ liệu, hàng đợi và vector chung một cơ sở dữ liệu, không cần Redisassetsfaces · peoplehàng đợi jobssettingschỉ mục HNSWSQL/predict · ảnh xem trước 1440pxLƯU TRỮ · MỞ RỘNG/dataẢnh gốc tải lên, ảnh thu nhỏ, ảnh xem trước 1440px/photos/* (có thể chỉ đọc :ro)Thư mục ảnh có sẵn trên NAS, gắn bao nhiêu cũng được./modelsTệp mô hình, tải trong phần cài đặt webDịch vụ AI từ xa (tùy chọn)Mac (CoreML / MPS) · NVIDIA CUDAXác thực bằng token, chỉ nhận ảnh xem trước 1440px, không chạm ảnh gốcđọc ghiquét · chỉ đọcnạpHTTP + token
Backend chỉ là một chương trình Go

Ứng dụng web được đóng gói vào chương trình nên triển khai không cần Node. Khi khởi động sẽ tự chạy di chuyển cơ sở dữ liệu, nâng cấp chỉ là đổi image.

Dịch vụ AI chạy ở nơi khác

Dịch vụ AI tích hợp chạy trong mạng nội bộ của Docker và suy luận bằng CPU; điền địa chỉ từ xa trong trang web là chuyển sang máy có card đồ họa hoặc Mac ở nhà.

Một cơ sở dữ liệu cho mọi dữ liệu

Thông tin ảnh, mọi người, cài đặt, hàng đợi tác vụ và vector đều nằm trong PostgreSQL, nên dữ liệu ứng dụng và trạng thái tác vụ có thể sao lưu cùng nhau; tệp ảnh cần sao lưu riêng.

PIPELINE

Quy trình xử lý ảnh

Sau khi nhập, ảnh được trích xuất siêu dữ liệu và tạo ảnh thu nhỏ trước, sau đó song song tính vector ngữ nghĩa, nhận diện khuôn mặt và chữ. Mỗi bước vào hàng đợi riêng, nên khởi động lại hay dịch vụ AI mất kết nối cũng không mất tác vụ, khôi phục là xử lý tiếp.

01ingest
Nhập
  • Tải lên qua tus, mất mạng vẫn tải tiếp được
  • Thư mục /photos được quét mỗi 15 phút
  • Tính checksum SHA-1, ghi vào assets
Backend
02metadata
Siêu dữ liệu
  • ExifTool đọc kích thước, thời điểm chụp
  • GPS, máy ảnh, ống kính, khẩu độ, tốc độ
  • Không có múi giờ thì dùng múi giờ cài đặt
Backend
03thumbnail
Ảnh thu nhỏ
  • libvips tạo ảnh thu nhỏ
  • Thêm ảnh xem trước 1440px
  • Video dùng FFmpeg lấy khung, chuyển mã
Backend
04clip
Vector ngữ nghĩa
  • Chinese-CLIP tính vector 1024 chiều
  • Tìm bằng ngôn ngữ tự nhiên, phân loại
  • Điểm thẩm mỹ, ảnh giống nhau
AI · song song
05face
Khuôn mặt
  • SCRFD tìm mặt, căn theo 5 điểm mốc
  • ArcFace tính đặc trưng 512 chiều
  • Gom dần vào từng người
AI · song song
06ocr
Chữ
  • PP-OCR đọc chữ trong ảnh
  • Tìm ảnh theo chữ
  • Nhận ra giấy tờ, tài liệu, ảnh chụp màn hình
AI · song song
SCHEDULING

Điều phối tác vụ nền

Không có trình trung gian tin nhắn riêng: hàng đợi tác vụ là bảng jobs trong PostgreSQL, được ghi cùng một giao dịch với dữ liệu ảnh, bảo đảm ảnh vào kho là tác vụ vào hàng. Worker nhận tác vụ bằng SKIP LOCKED, dùng cơ chế thời hạn thuê để không bị treo, thất bại thì thử lại với thời gian chờ tăng theo bình phương.

pendingđang xếp hàngrunningworker đã nhậndonehoàn tấtfailedhết lượt thửnhận bằng SKIP LOCKEDthành cônghết lượt thử lạithất bại: chờ n² × 30 giây rồi thử lạiAI mất kết nối: 15 giây sau thử lại, không tính là thất bạiThời hạn thuê 10 phút: quá hạn chưa có kết quả thì mỗi 30 giây thu hồi về pending
jobs/worker.go · claim
-- Nhận một tác vụ (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   -- nhiều worker nhận cùng lúc cũng không trùng nhau
  LIMIT 1
)
RETURNING id, type, asset_id, payload, attempts, max_attempts;
Quy tắc điều phối
  • Khi cùng một ảnh đã có tác vụ cùng loại đang xếp hàng hoặc đang chạy thì không thêm vào hàng nữa.
  • Hàng đợi trống thì mỗi giây kiểm tra một lần; nhận theo priority từ cao đến thấp, rồi theo thời điểm đến hạn từ sớm đến muộn.
  • Thời hạn thuê (cũng là thời gian chờ tối đa) của mỗi tác vụ là 10 phút; khi tiến trình khởi động lại, nó trả các tác vụ chưa xong của mình về hàng đợi.
Nhường đường và giới hạn
  • Khi có người đang tải ảnh lên, tác vụ AI tạm ngừng nhận, nhường băng thông và CPU cho việc tải lên trước.
  • Khi dịch vụ AI mất kết nối, toàn bộ worker AI dừng lại chờ, không lấy từng tác vụ ra cho thất bại rồi tốn lượt thử lại vô ích.
  • Mức «Tự động» sẽ kiểm tra phần cứng máy chủ AI trước, chọn mô hình phù hợp rồi mới nhận tác vụ, giúp tránh nạp những mô hình vượt quá bộ nhớ còn trống trên Raspberry Pi và các thiết bị tương tự.
AI SCHEDULING

Điều phối AI

Dịch vụ AI chỉ nhận ảnh xem trước và trả về khuôn mặt, vector và chữ. Backend quyết định địa chỉ dịch vụ, mô hình và số luồng song song; dịch vụ AI dựa vào phần cứng để chọn CUDA, CoreML / MPS, OpenVINO hoặc CPU.

Hàng đợi tác vụ AIclip · face · ocrSong song theo cài đặt, giới hạn điều chỉnh đượcBackend Go · máy khách AIChọn dịch vụ, chọn mức, đối chiếu model_ids· Mỗi 15 giây kiểm tra /health· Mất kết nối → tác vụ chờ trong hàng· Mức tự động → theo phần cứngnhậnDịch vụ AI tích hợpMạng nội bộ Docker · CPUDịch vụ AI từ xaMac ở nhà · máy có card đồ họaNút xử lý từ xaTự nhận tác vụ (đang lên kế hoạch)chọn mộtCPUONNX RuntimeCoreML / MPS · CUDATự chọn tăng tốc có sẵnOpenVINOGPU tích hợp Intel
Ba mức mô hình

Khi chọn «Tự động», hệ thống đề xuất theo phần cứng máy chủ AI: có card đồ họa dùng được thì dùng Độ chính xác cao; chỉ có CPU thì bộ nhớ dưới 6 GB dùng Nhẹ, từ 6 GB trở lên dùng Cân bằng.

MứcPhần cứng phù hợpKhuôn mặtTìm kiếm ngữ nghĩa
quality · Độ chính xác caoMac chip Apple, card đồ họa NVIDIASCRFD-10G + ArcFace R100 (antelopev2)Chinese-CLIP ViT-H/14 · 1024 chiều
balanced · Cân bằngNAS cỡ Intel i5SCRFD-10G + ArcFace R100 (antelopev2)Chinese-CLIP ViT-L/14 · 768 chiều
lite · NhẹN100, Raspberry Pi 5SCRFD + ArcFace R50 (buffalo_l)Chinese-CLIP ViT-B/16 · 512 chiều
API dịch vụ AI · /predict
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 số], "yaw": 0.12, "blur": 340}],
  "clip": {"embedding": [1024 số]},
  "ocr":  {"text": "...", "lines": [...]},
  "model_ids": {"face": "antelopev2", "clip": "chinese-clip-vit-h-14"}
}
ALGORITHMS

Thuật toán cốt lõi

Các mô hình, thuật toán và ngưỡng đứng sau tìm kiếm, mọi người, ảnh liên tiếp và AI chọn lọc — có cái dựa vào mô hình AI, có cái là tính toán vector và quy tắc.

Tìm kiếm bằng ngôn ngữ tự nhiên

Chinese-CLIP đặt ảnh và chữ vào cùng một không gian vector: khi ảnh vào kho thì tính sẵn vector ảnh, khi tìm thì biến câu như «sinh nhật ba tuổi của bé» thành vector chữ, rồi tìm ảnh gần nhất theo khoảng cách cosin trong chỉ mục HNSW của pgvector.

sim(q, p) = q · p / (‖q‖ ‖p‖)
  • Gộp ba nguồn kết quả: ảnh nhận ra đúng những chữ này xếp đầu, tiếp đến ảnh chụp ở địa danh này, cuối cùng là ảnh có ý nghĩa gần nhất.
  • Chỉ trả về ảnh khớp rõ ràng: phải gần ngang ảnh giống nhất và giống hơn hẳn ảnh thông thường, giúp lọc bớt kết quả khớp yếu thay vì trả về những ảnh chỉ hơi liên quan.
  • Khi từ khóa đúng là tên một phân loại (phong cảnh, ẩm thực, ảnh chụp màn hình…) thì trả về thẳng phân loại đó.
Gom nhóm khuôn mặt

SCRFD tìm khuôn mặt và 5 điểm mốc, căn chỉnh theo các điểm mốc rồi cắt thành 112×112, ArcFace tính ra đặc trưng 512 chiều (sau khi chuẩn hóa, tích vô hướng chính là độ tương đồng cosin). Mỗi ảnh xử lý xong là gom nhóm tăng dần một lần, không cần chờ tính hết.

d(a, b) = 1 − a · b mặt tốt d < 0.55 · mờ/nghiêng d < 0.45
  • «Mặt tốt» đủ rõ, đủ thẳng, đủ lớn sẽ tìm những mặt giống nhất: nếu đã có mặt thuộc về một người thì theo số phiếu mà gom vào; nếu chưa thuộc ai nhưng có từ 3 mặt giống nhau thì tạo người mới.
  • Mặt mờ, nghiêng hay quá nhỏ không tạo người mới, chỉ gom vào khi rất giống một người nào đó; người qua đường chỉ xuất hiện một lần sẽ không được thêm.
  • Nhiều tác vụ gom nhóm cùng lúc dùng chung một khóa tư vấn (advisory lock) của cơ sở dữ liệu, nên một người không bị tạo thành hai. Ảnh đại diện chọn mặt đơn rõ nét (phương sai Laplace), nhìn thẳng (yaw) và đủ lớn.
Ảnh liên tiếp và ảnh giống nhau
cùng máy ảnh Δt ≤ 2s hoặc Δt ≤ 60s và sim ≥ 0.93
  • Mấy tấm chụp liền cùng một cảnh thường có độ tương đồng CLIP 0,94–0,98, đổi cảnh hay đổi dáng thường ở mức 0,7–0,89; ngưỡng tương đồng 0,93 giúp nhận ra những ảnh nhiều khả năng là chụp liên tiếp.
  • Tấm gợi ý giữ lại: có mặt người thì chọn tấm mặt rõ, mắt mở nhìn ống kính; không có mặt người thì xem dung lượng tệp (cùng máy, cùng thiết lập, ảnh bị nhòe ít chi tiết nên nén ra nhỏ hơn).
  • Cùng một tệp lưu nhiều bản thì cho riêng vào «Trùng lặp», không cần chọn. «Giữ lại» sẽ chuyển các tấm còn lại vào mục Đã xóa gần đây, khôi phục được cho đến khi bị xóa vĩnh viễn.
Điểm thẩm mỹ của AI chọn lọc

Không chạy thêm mô hình mà dùng lại vector CLIP: lấy trung bình vector của một nhóm mô tả «tác phẩm nhiếp ảnh» và một nhóm mô tả «ảnh đời thường», trừ nhau ra được một hướng «đẹp»; hình chiếu của vector ảnh lên hướng này chính là điểm của nó.

score(p) = p · (μ_tác phẩm − μ_đời thường)
  • Chỉ dùng để xếp thứ tự, không coi là điểm tuyệt đối; băng chuyền ảnh chọn lọc ở trang chính chọn ảnh theo điểm này.
  • Một nhóm mô tả khác chuyên nhận ra ảnh «chụp hỏng» (nhòe, cháy sáng…), tính riêng với chuyện đẹp hay không.
TECH STACK

Công nghệ sử dụng

Toàn là thành phần mã nguồn mở đã chín muồi, không có dịch vụ độc quyền, phần nào cũng có phương án thay thế.

BACKEND
  • Go
    API, hàng đợi tác vụ, điều phối ảnh thu nhỏ
  • libvips
    Ảnh thu nhỏ và ảnh xem trước
  • FFmpeg
    Lấy khung video, chuyển mã
  • ExifTool
    Đọc ghi siêu dữ liệu ảnh
  • tus
    Tải lên có thể tiếp tục
DỊCH VỤ AI
  • Python + FastAPI
    Dịch vụ HTTP không trạng thái
  • ONNX Runtime
    Suy luận khuôn mặt và chữ
  • PyTorch
    Chinese-CLIP
  • InsightFace
    SCRFD + ArcFace
  • RapidOCR
    Nhận dạng chữ PP-OCR
DỮ LIỆU
  • PostgreSQL 17
    Dữ liệu ứng dụng và hàng đợi
  • pgvector
    Lưu trữ vector
  • HNSW
    Chỉ mục khoảng cách cosin
  • ip2region
    Vị trí IP trong lịch sử đăng nhập (ngoại tuyến)
  • Nominatim
    Tra ngược địa điểm (tắt được)
GIAO DIỆN VÀ TRIỂN KHAI
  • React + TypeScript
    Ứng dụng web
  • Vite + Tailwind CSS
    Đóng gói và giao diện
  • MapLibre GL
    Quả địa cầu vệ tinh
  • Docker Compose
    Image amd64 / arm64
  • mDNS
    Tên miền nội bộ wanmory.local
PRIVACY

Quyền riêng tư và bảo mật

Mặc định, ảnh và kết quả AI đều nằm trong mạng riêng của bạn. Nếu bạn cấu hình dịch vụ AI từ xa, ảnh xem trước sẽ được gửi tới máy chủ đó để xử lý.

Dữ liệu tối thiểu

Dịch vụ AI chỉ nhận ảnh xem trước 1440 pixel, không lưu tệp nào, không chạm vào cơ sở dữ liệu, tên người và ảnh gốc.

Xác thực

Dịch vụ AI tích hợp chỉ nằm trong mạng nội bộ của Docker, chỉ backend kết nối được; dịch vụ AI từ xa khi đã đặt token thì mọi yêu cầu đều phải kèm token Bearer.

Phân quyền

Thành viên chỉ xóa được ảnh mình tải lên, quản trị viên quản lý tất cả; ảnh đã ẩn chỉ mình thấy; lịch sử đăng nhập hiển thị vị trí ước chừng theo IP.

Ảnh gốc chỉ đọc

Thư mục NAS có thể gắn chỉ đọc bằng :ro, ứng dụng không sửa được, cũng không xóa được tệp bên trong.

Không có mạng vẫn dùng được

Tải mô hình xong thì nhận diện và tìm kiếm đều chạy cục bộ; các tính năng cần mạng như tra ngược địa điểm, ảnh vệ tinh đều tắt được.

Giấy phép mô hình

Mô hình khuôn mặt InsightFace chỉ dùng cho mục đích phi thương mại; mô hình không nằm trong image, trước khi tải sẽ hiện giấy phép của từng mô hình ở trang cài đặt.