Files
momentry_core/IDENTITY_BEST_FACE_API.md

8.8 KiB
Raw Blame History

Identity Best-Face API

狀態: 規劃中 提出日期: 2026-06-01 提出者: WordPress Portal 前端團隊


1. 背景

WordPress Portal 的 People 頁面需要在 identity detail view 與 grid card 中顯示代表臉部縮圖。目前前端作法:

  1. GET /identity/{uuid}/traces → 取得所有 trace 列表(含 avg_confidence
  2. 對每個 trace 載入第一幀 thumbnail → GET /file/{uuid}/trace/{tid}/thumbnail
  3. 從有 thumbnail 的 trace 中,選 avg_confidence 最高者作為代表圖

現有問題

  • 品質不佳trace thumbnail 固定取第一幀,不一定是該 trace 內最清晰或正面的臉部畫面
  • 浪費頻寬:前端需發送大量並行請求(最多 20 trace × thumbnail多數 thumbnail 最終不會被使用
  • 無快取:每次進入 detail view 都要重複載入所有 thumbnail
  • 不一致:同樣 identity 在 grid card 與 detail view 可能顯示不同代表圖

2. 目標

後端新增一個 endpoint對指定 identity 跨所有 trace 選出品質最佳(最清晰)的臉部畫面,並提供可直接使用的縮圖 URL支援 disk cache。


3. API 規格

GET /api/v1/identity/:identity_uuid/best-face

無 query parameter。

成功回應 200

{
  "success": true,
  "identity_uuid": "a6fb22eebefaef17e62af874997c5944",
  "name": "Audrey Hepburn",
  "source": "fresh",
  "best": {
    "file_uuid": "a6fb22eebefaef17e62af874997c5944",
    "trace_id": 42,
    "frame_number": 3120,
    "timestamp_secs": 124.8,
    "bbox": {
      "x": 240,
      "y": 180,
      "width": 120,
      "height": 160
    },
    "confidence": 0.97,
    "quality_score": 18624.0,
    "blur_score": 2.1,
    "thumbnail_url": "/api/v1/file/a6fb22eebefaef17e62af874997c5944/trace/42/thumbnail"
  }
}

無可用臉部 200

{
  "success": true,
  "identity_uuid": "a6fb22eebefaef17e62af874997c5944",
  "name": "Audrey Hepburn",
  "source": "fresh",
  "best": null
}

欄位說明

欄位 型態 說明
success boolean 請求是否成功
identity_uuid string identity UUID32字元無連字號
name string identity 名稱
source string "fresh"(即時計算)或 "cache"(來自 disk cache
best object/null 最佳臉部資訊,無可用臉部時為 null
best.file_uuid string 該臉部所屬檔案 UUID
best.trace_id int 該臉部所屬 trace ID
best.frame_number int 代表臉的影格編號
best.timestamp_secs float 代表臉的時間戳(秒)
best.bbox object 臉部 bounding box {x, y, width, height}
best.confidence float 該臉部的 detection confidence
best.quality_score float 品質分數 = (width * height) * confidence
best.blur_score float 模糊度分數ffmpeg blurdetect越低越清晰
best.thumbnail_url string 縮圖 URL相對路徑可直接用於瀏覽器

4. 實作建議

4.1 建議放置位置

選項 A建議 src/api/trace_agent_api.rs

  • 原因:核心邏輯重用 select_rep_face()(目前為 pub(crate),位於同一檔案),無需修改既有的 function visibility
  • trace_agent_routes() 中新增路由

選項 B src/api/identity_binding.rs

  • 需將 select_rep_face 改為 pub 才能跨檔案呼叫
  • 路由語意上更接近 identity 操作

4.2 演算法

1. DISK CACHE CHECK
   路徑:{OUTPUT_DIR}/identities/{uuid}/best_face.json
   讀取 identity.json 的 updated_at與 cache 中記錄的版本比較
   若 cache 未過期 → 直接回傳source: "cache"
   若無 cache 或已過期 → 繼續計算

2. QUERY IDENTITY
   SELECT id, name FROM identities
   WHERE REPLACE(uuid::text, '-', '') = $1

3. QUERY TOP N TRACES
   SELECT fd.file_uuid, fd.trace_id,
          AVG(fd.confidence)::float8 AS avg_conf
   FROM {schema}.face_detections fd
   WHERE fd.identity_id = $1
     AND fd.confidence > 0.7
     AND (fd.metadata->>'qc_ok' IS NULL
          OR (fd.metadata->>'qc_ok')::boolean = true)
   GROUP BY fd.file_uuid, fd.trace_id
   ORDER BY avg_conf DESC
   LIMIT 5

4. FOR EACH TRACE (並行)
   select_rep_face(pool, file_uuid, trace_id, err_fn)
    → 回傳該 trace 內 blur_score 最低(最清晰)的臉
   失敗則 skiplog warning

5. SELECT BEST AMONG RESULTS
   主排序blur_score ASC越低越清晰
   次排序quality_score DESCblur_score 差距 < 0.5 時)
   全部失敗 → best = null

6. WRITE DISK CACHE
   路徑:{OUTPUT_DIR}/identities/{uuid}/best_face.json
   內容best 欄位 + 計算時間 + identity updated_at

7. RESPONSE

4.3 效能參數

參數 說明
TOP N 5 只對 confidence 最高的 5 個 trace 做 blurdetect
confidence 門檻 > 0.7 同既有的 select_rep_face 邏輯
QC 過濾 qc_ok = true/null 同既有邏輯
ffmpeg timeout inherit from Command 每個 trace 約 1-3s
cache TTL 直到下一次 bind/unbind/merge 事件驅動失效

4.4 快取策略

寫入時機: get_identity_best_face 計算完成後

失效時機(刪除 best_face.json

觸發 operation 所在檔案 備註
bind_trace (POST) identity_binding.rs 新增 face 關聯
unbind (POST) identity_binding.rs 移除 face 關聯
mergeinto (POST) identity_binding.rs source + target 雙雙清除
profile-image (POST) identity_api.rs 使用者上傳新大頭照

Cache 驗證機制: 儲存計算時的 identity.updated_at,每次請求時比對:

  • 若 identity 的 updated_at 未變 → cache 有效
  • 若已變 → 重新計算

4.5 建議的新增/修改檔案

檔案 動作 說明
src/api/trace_agent_api.rs 新增 handler + struct + route ~+130 行
src/api/identity_binding.rs 修改 3 處 + cache invalidation helper ~+25 行
src/api/identity_api.rs 修改 1 處profile-image POST ~+5 行

4.6 需要的新 struct

src/api/trace_agent_api.rs(或獨立檔案 src/core/identity_best_face.rs

#[derive(Debug, Serialize, Deserialize)]
pub struct BestFaceResponse {
    pub success: bool,
    pub identity_uuid: String,
    pub name: String,
    pub source: String,
    pub best: Option<BestFaceResult>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BestFaceResult {
    pub file_uuid: String,
    pub trace_id: i32,
    pub frame_number: i64,
    pub timestamp_secs: f64,
    pub bbox: RepFaceBbox,
    pub confidence: f64,
    pub quality_score: f64,
    pub blur_score: f64,
    pub thumbnail_url: String,
}

4.7 Cache Invalidation Helper Function

async fn invalidate_best_face_cache(output_dir: &str, uuid_clean: &str) {
    let path = format!("{}/identities/{}/best_face.json", output_dir, uuid_clean);
    let _ = tokio::fs::remove_file(path).await;
}

5. 前端整合參考(供後端團隊理解使用情境)

WP snippet 72 (ms-people.js) 的 loadPersonDetail 中,優先使用新 endpoint

async function loadPersonDetail(person) {
  if (person.thumb && person._hasProfileImage) return;

  try {
    const res = await apiFetch('/identity/' + person.id + '/best-face');
    if (res?.success && res?.best) {
      const b = res.best;
      person.thumb = `${API_BASE}/file/${b.file_uuid}/trace/${b.trace_id}/thumbnail?api_key=${API_KEY}`;
      person._hasProfileImage = true;
      updateDetailAvatar(person);
      return;
    }
  } catch (e) { /* fallback to legacy */ }

  // 原邏輯traces → thumbnails → confidence sort
}

同樣可用於 grid card 的代表圖載入(loadGridThumbnails

// 一次性載入所有 pending identity 的 best-face
const results = await Promise.allSettled(
  persons.map(p => apiFetch('/identity/' + p.id + '/best-face'))
);

6. 驗收標準

  1. GET /api/v1/identity/{uuid}/best-face200 + valid JSON
  2. 有 trace 的 identity → best 不為 nullblur_score 為該 identity 所有 trace 中最低
  3. 無 trace 的 identity → best: null
  4. 短時間內重複請求同一 identity → source: "cache",回應時間 < 10ms
  5. 綁定新 trace 後再次請求 → source: "fresh"cache 已正確失效)
  6. thumbnail_url 可直接用於 <img> 顯示

7. 風險與注意事項

  • 首次請求延遲:對有大量 trace 的 identity如主角首次請求可能需 5-15 秒。建議前端顯示 loading state
  • ffmpeg 資源:同時多個請求可能導致高 CPU 使用。可考慮加入 per-identity lock 避免重複計算
  • 邊界案例trace 內的 faces 全部 confidence ≤ 0.7 或 qc_ok=false則該 trace 被跳過,可能導致 best: null