---
document_type: "architecture"
title: "Identity 1對多參考向量設計"
service: "MOMENTRY_CORE"
date: "2026-04-28"
status: "active"
current_state: "finalized"
owner: "Warren"
created_by: "OpenCode"
created_at: "2026-04-28"
version: "V1.0"
tags:
  - "identity"
  - "reference_vector"
  - "embedding"
  - "face_embedding"
  - "identity_embedding"
  - "1-to-many"
  - "matching_algorithm"
related_documents:
  - "MOMENTRY_CORE_ARCHITECTURE_V2.md"
  - "IMPLEMENTATION/FILE_IDENTITY_API_DESIGN.md"
  - "CLIP_EMBEDDING_BENCHMARK_PLAN.md"
ai_query_hints:
  - "查詢 1對多參考向量架構設計"
  - "查詢 reference_data JSONB 結構"
  - "查詢多角度人臉 embedding 存儲"
  - "查詢 Logo/Symbol identity_embedding"
  - "查詢匹配算法 (最佳匹配/投票/加權平均)"
---

# Identity 1對多參考向量設計

| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-04-28 |
| 文件版本 | V1.0 |

---

## 版本歷史

| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-04-28 | 創建 Identity 1對多參考向量架構設計 | OpenCode | OpenCode |

---

## 概述

本文檔定義 Momentry Core Identity 系統的 **1對多參考向量架構設計**，核心理念：
**同一 Identity 可存儲多個參考向量（不同角度、不同場景、不同版本），提高識別鲁棒性。**

---

## 核心設計理念

### 問題背景

**傳統 1對1 設計的局限**：
- 單一參考向量無法覆蓋不同角度（正面、側面、背面）
- 單一參考向量無法覆蓋不同場景（白底 Logo、黑底 Logo、複雜背景 Logo）
- 單一參考向量無法覆蓋不同版本（同一演員的不同定妝造型）
- 匹配失敗率高，鲁棒性不足

### 1對多設計優勢

| 優勢 | 說明 |
|------|------|
| **多角度覆蓋** | 人臉正面、側面、三側角度，覆蓋不同拍攝角度 |
| **多場景覆蓋** | Logo/Symbol 在不同背景下的 embedding |
| **多版本覆蓋** | 同一演員的不同定妝造型（老妝、武俠造型、現代造型） |
| **質量評分** | 每個參考向量記錄質量評分，用於加權匹配 |
| **來源追溯** | 記錄每個 embedding 的來源，方便更新和追溯 |

---

## 架構設計

### 資料庫 Schema

**identities 表核心字段**:

```sql
CREATE TABLE identities (
    identity_id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name                TEXT NOT NULL,
    identity_type       VARCHAR(30) NOT NULL,
    
    -- 參考向量 (centroid 或最佳代表)
    face_embedding      VECTOR(512),             -- ArcFace centroid
    voice_embedding     VECTOR(192),             -- ECAPA-TDNN centroid
    identity_embedding  VECTOR(768),             -- CLIP ViT-L/14 centroid
    
    -- 1對多參考向量存儲
    reference_data      JSONB DEFAULT '{}',      -- 多角度/多場景/多版本
    
    created_at          TIMESTAMPTZ DEFAULT NOW(),
    updated_at          TIMESTAMPTZ DEFAULT NOW()
);
```

**設計理念**:
- `face_embedding` 等 VECTOR 字段存儲 **centroid**（中心向量）或最佳代表向量
- `reference_data` JSONB 存儲 **所有參考向量**（多角度、多場景、多版本）
- 匹配時可選擇：
  - **快速匹配**: 使用 centroid（適合低延遲場景）
  - **鲁棒匹配**: 使用 reference_data 進行 1對多匹配（適合高精度場景）

---

## reference_data JSONB 結構

### 完整結構

```json
{
  "face_embeddings": [
    {
      "embedding": [0.1, 0.2, ...],
      "source": "tmdb_images",
      "image_url": "https://image.tmdb.org/t/p/original/xxx.jpg",
      "angle": "frontal",
      "quality_score": 0.95,
      "created_at": "2026-04-28T10:00:00Z"
    },
    {
      "embedding": [0.3, 0.4, ...],
      "source": "tmdb_images",
      "image_url": "https://image.tmdb.org/t/p/original/yyy.jpg",
      "angle": "profile_left",
      "quality_score": 0.88,
      "created_at": "2026-04-28T10:05:00Z"
    }
  ],
  "voice_embeddings": [
    {
      "embedding": [0.1, 0.2, ...],
      "source": "video_segment",
      "file_uuid": "vid_001",
      "timestamp_start": 120.5,
      "timestamp_end": 135.2,
      "quality_score": 0.88,
      "created_at": "2026-04-28T11:00:00Z"
    }
  ],
  "identity_embeddings": [
    {
      "embedding": [0.1, 0.2, ...],
      "source": "logo_image",
      "image_url": "https://www.accusys.com.tw/wp-content/uploads/2023/03/Accusys-Orange-2017.png",
      "context": "brand_logo",
      "created_at": "2026-04-28T12:00:00Z"
    }
  ],
  "sound_embeddings": [
    {
      "embedding": [0.1, 0.2, ...],
      "source": "audio_segment",
      "file_uuid": "vid_001",
      "timestamp_start": 10.0,
      "timestamp_end": 15.0,
      "sound_type": "animal_dog_bark",
      "created_at": "2026-04-28T13:00:00Z"
    }
  ],
  "image_urls": [
    "https://image.tmdb.org/t/p/original/xxx.jpg",
    "https://www.accusys.com.tw/wp-content/uploads/2023/03/Accusys-Orange-2017.png"
  ]
}
```

### 欄位說明

#### face_embeddings (人臉向量)

| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| embedding | Array[512] | Yes | 512-dim ArcFace 向量 |
| source | String | Yes | 來源: tmdb_profile, tmdb_images, manual_upload, auto_detection |
| image_url | String | Yes | 圖片 URL |
| angle | String | No | 人臉角度: frontal, profile_left, profile_right, three_quarter |
| quality_score | Float | No | 質量評分 (0.0-1.0) |
| created_at | String | Yes | 建立時間 (ISO 8601) |

#### voice_embeddings (聲紋向量)

| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| embedding | Array[192] | Yes | 192-dim ECAPA-TDNN 向量 |
| source | String | Yes | 來源: video_segment, audio_file |
| file_uuid | String | Yes | 檔案 UUID |
| timestamp_start | Float | Yes | 開始時間 (秒) |
| timestamp_end | Float | Yes | 結束時間 (秒) |
| quality_score | Float | No | 質量評分 (0.0-1.0) |
| created_at | String | Yes | 建立時間 (ISO 8601) |

#### identity_embeddings (身份向量 - Logo/Symbol/Object)

| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| embedding | Array[768] | Yes | 768-dim CLIP ViT-L/14 向量 |
| source | String | Yes | 來源: logo_image, symbol_image, object_image, concept_image |
| image_url | String | Yes | 圖片 URL |
| context | String | No | 識別場景: brand_logo, symbol, object, concept |
| created_at | String | Yes | 建立時間 (ISO 8601) |

#### sound_embeddings (聲音向量 - Phase 5+)

| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| embedding | Array[TBD] | Yes | TBD (動物叫聲、雷雨、槍炮、樂器) |
| source | String | Yes | 來源: audio_segment |
| file_uuid | String | Yes | 檔案 UUID |
| timestamp_start | Float | Yes | 開始時間 (秒) |
| timestamp_end | Float | Yes | 結束時間 (秒) |
| sound_type | String | Yes | 聲音類型: animal_dog_bark, environmental_thunder, weapon_gunshot, musical_guitar |
| created_at | String | Yes | 建立時間 (ISO 8601) |

---

## 匹配算法

### 1對多匹配策略

#### 策略 1: 最佳匹配 (Best Match)

```python
def best_match(detected_embedding, reference_embeddings):
    """
    策略 1: 取所有參考向量中的最高相似度
    
    適用場景:
    - 快速匹配
    - 低延遲需求
    """
    similarities = [
        cosine_similarity(detected_embedding, ref["embedding"])
        for ref in reference_embeddings
    ]
    return max(similarities)
```

#### 策略 2: 投票機制 (Voting)

```python
def voting_match(detected_embedding, reference_embeddings, threshold=0.85):
    """
    策略 2: 統計超過閾值的參考向量數量
    
    適用場景:
    - 高鲁棒性需求
    - 多角度覆蓋場景
    """
    similarities = [
        cosine_similarity(detected_embedding, ref["embedding"])
        for ref in reference_embeddings
    ]
    
    votes = sum(1 for sim in similarities if sim >= threshold)
    vote_ratio = votes / len(similarities)
    
    return {
        "votes": votes,
        "vote_ratio": vote_ratio,
        "is_match": vote_ratio >= 0.5  # 至少一半參考向量支持
    }
```

#### 策略 3: 加權平均 (Weighted Average)

```python
def weighted_match(detected_embedding, reference_embeddings):
    """
    策略 3: 根據質量評分加權計算相似度
    
    適用場景:
    - 參考向量質量不均
    - 需要考慮質量評分
    """
    similarities = [
        cosine_similarity(detected_embedding, ref["embedding"])
        for ref in reference_embeddings
    ]
    
    weights = [
        ref.get("quality_score", 1.0)
        for ref in reference_embeddings
    ]
    
    weighted_sim = sum(sim * w for sim, w in zip(similarities, weights)) / sum(weights)
    
    return {
        "weighted_similarity": weighted_sim,
        "is_match": weighted_sim >= 0.85
    }
```

#### 策略 4: 綜合評分 (Combined)

```python
def combined_match(detected_embedding, reference_embeddings, threshold=0.85):
    """
    策略 4: 綜合評分 (最佳匹配 + 投票 + 加權平均)
    
    適用場景:
    - 最高精度需求
    - 重要場景識別
    """
    best_match_score = best_match(detected_embedding, reference_embeddings)
    voting_result = voting_match(detected_embedding, reference_embeddings, threshold)
    weighted_result = weighted_match(detected_embedding, reference_embeddings)
    
    # 綜合評分: 50% 最佳匹配 + 30% 投票比率 + 20% 加權平均
    final_score = (
        best_match_score * 0.5 +
        voting_result["vote_ratio"] * 0.3 +
        weighted_result["weighted_similarity"] * 0.2
    )
    
    return {
        "best_match": best_match_score,
        "vote_ratio": voting_result["vote_ratio"],
        "weighted_similarity": weighted_result["weighted_similarity"],
        "final_score": final_score,
        "is_match": final_score >= threshold
    }
```

### 匹配算法選擇建議

| 場景 | 推薦策略 | 說明 |
|------|---------|------|
| **實時搜索** | Strategy 1 (Best Match) | 低延遲，快速匹配 |
| **批量處理** | Strategy 4 (Combined) | 最高精度，綜合評分 |
| **低置信度場景** | Strategy 2 (Voting) | 投票機制，提高鲁棒性 |
| **質量不均場景** | Strategy 3 (Weighted) | 加權平均，考慮質量評分 |

---

## TMDB 整合流程

### 1對多參考向量提取

```python
def tmdb_identity_integration(tmdb_person_id, identity_name):
    """
    TMDB 整合流程:
    1. 下載多張人臉照片 (TMDB /person/:id/images 端點)
    2. 提取每張照片的 ArcFace embedding
    3. 存儲到 reference_data JSONB
    4. 計算 centroid 存儲到 face_embedding
    """
    
    # Step 1: 獲取 TMDB 人物照片列表
    images = tmdb_api.get_person_images(tmdb_person_id)
    
    # Step 2: 下載並提取 embedding
    face_embeddings = []
    for image in images:
        # 下載圖片
        image_url = f"https://image.tmdb.org/t/p/original/{image['file_path']}"
        image_data = download_image(image_url)
        
        # 提取 ArcFace embedding
        embedding = insightface.extract_embedding(image_data)
        
        # 評估人臉角度和質量
        angle = detect_face_angle(image_data)
        quality_score = evaluate_face_quality(image_data)
        
        # 存儲到 reference_data
        face_embeddings.append({
            "embedding": embedding.tolist(),
            "source": "tmdb_images",
            "image_url": image_url,
            "angle": angle,
            "quality_score": quality_score,
            "created_at": datetime.now().isoformat()
        })
    
    # Step 3: 存儲到 identities 表
    identity = {
        "identity_id": generate_uuid(),
        "name": identity_name,
        "identity_type": "people",
        "source": "tmdb",
        "tmdb_id": tmdb_person_id,
        "reference_data": {
            "face_embeddings": face_embeddings,
            "image_urls": [img["image_url"] for img in face_embeddings]
        }
    }
    
    # Step 4: 計算 centroid
    centroid = calculate_centroid([e["embedding"] for e in face_embeddings])
    identity["face_embedding"] = centroid
    
    # 存儲到資料庫
    db.insert_identity(identity)
    
    return identity
```

### Centroid 計算

```python
def calculate_centroid(embeddings):
    """
    計算多個 embedding 的中心向量
    
    方法: 平均值
    """
    import numpy as np
    
    embeddings_array = np.array(embeddings)
    centroid = np.mean(embeddings_array, axis=0)
    
    return centroid.tolist()
```

---

## Logo/Symbol Identity 整合

### CLIP ViT-L/14 Embedding 提取

```python
def logo_identity_integration(logo_name, logo_url):
    """
    Logo Identity 整合流程:
    1. 下載 Logo 圖片
    2. 提取 CLIP ViT-L/14 embedding (768-dim)
    3. 存儲到 reference_data JSONB
    4. 存儲到 identity_embedding 字段
    """
    
    # Step 1: 下載圖片
    image_data = download_image(logo_url)
    
    # Step 2: 提取 CLIP embedding
    embedding = clip_model.extract_embedding(image_data)
    
    # Step 3: 存儲到 reference_data
    identity_embedding_data = {
        "embedding": embedding.tolist(),
        "source": "logo_image",
        "image_url": logo_url,
        "context": "brand_logo",
        "created_at": datetime.now().isoformat()
    }
    
    # Step 4: 存儲到 identities 表
    identity = {
        "identity_id": generate_uuid(),
        "name": logo_name,
        "identity_type": "logo",
        "source": "manual",
        "reference_data": {
            "identity_embeddings": [identity_embedding_data],
            "image_urls": [logo_url]
        },
        "identity_embedding": embedding.tolist()
    }
    
    # 存儲到資料庫
    db.insert_identity(identity)
    
    return identity
```

### 範例: Accusys Logo

```python
# 註冊 Accusys Logo Identity
accusys_logo = logo_identity_integration(
    logo_name="Accusys Storage Logo",
    logo_url="https://www.accusys.com.tw/wp-content/uploads/2023/03/Accusys-Orange-2017.png"
)

# 測試匹配
detected_logo_embedding = clip_model.extract_embedding(video_frame)
match_result = combined_match(
    detected_embedding=detected_logo_embedding,
    reference_embeddings=accusys_logo["reference_data"]["identity_embeddings"],
    threshold=0.85
)

print(f"Match result: {match_result['is_match']}")
print(f"Final score: {match_result['final_score']}")
```

---

## 實作計畫

### Phase 1: 資料庫 Migration

- [ ] Migration 023: identities 表添加 reference_data JSONB + identity_embedding VECTOR(768)
- [ ] 索引配置: identity_embedding 向量索引 (ivfflat 或 hnsw)
- [ ] 測試資料建立

### Phase 2: TMDB 整合實作

- [ ] TMDB /person/:id/images API 串接
- [ ] 多張照片下載邏輯
- [ ] ArcFace embedding 提取（多角度）
- [ ] reference_data JSONB 存儲
- [ ] Centroid 計算邏輯

### Phase 3: Logo/Symbol Identity 實作

- [ ] CLIP ViT-L/14 模型集成（MPS 支持）
- [ ] Logo/Symbol 檢測（OWL-ViT）
- [ ] identity_embedding 提取
- [ ] reference_data JSONB 存儲
- [ ] 匹配算法實作

### Phase 4: 匹配算法實作

- [ ] Strategy 1: Best Match
- [ ] Strategy 2: Voting
- [ ] Strategy 3: Weighted Average
- [ ] Strategy 4: Combined
- [ ] API 端點設計

### Phase 5: 声音识别扩展 (待辦事項)

- [ ] sound_embeddings 定義
- [ ] 動物叫聲 embedding 提取
- [ ] 雷雨聲 embedding 提取
- [ ] 槍炮聲 embedding 提取
- [ ] 樂器聲 embedding 提取

---

## 待辦事項

| 項目 | 優先級 | 說明 |
|------|--------|------|
| Migration 023 | 高 | Phase 1 |
| TMDB 整合實作 | 高 | Phase 2 |
| Logo/Symbol Identity | 中 | Phase 3 |
| 匹配算法實作 | 中 | Phase 4 |
| 声音识别扩展 | 低 | Phase 5+ (待辦事項) |

---

## 限制條件

- 本設計為全新架構，需要資料庫 Migration
- CLIP ViT-L/14 需要 MPS 或 CUDA 支持
- TMDB 整合需要 TMDB API Key
- 声音识别列为 Phase 5+ 待辦事項

---

## 相關文件

- `docs_v1.0/ARCHITECTURE/MOMENTRY_CORE_ARCHITECTURE_V2.md` - 核心架構設計
- `docs_v1.0/IMPLEMENTATION/FILE_IDENTITY_API_DESIGN.md` - API 設計
- `docs_v1.0/ARCHITECTURE/CLIP_EMBEDDING_BENCHMARK_PLAN.md` - CLIP 测试计划
- `docs_v1.0/STANDARDS/DOCS_STANDARD.md` - 文件創建規範

---

## 版本資訊

- 版本: V1.0
- 建立日期: 2026-04-28
- 文件更新: 2026-04-28
