feat: Phase 1 handover - schema migration, correction mechanism, API fixes

Schema changes: dev.chunks->dev.chunk, remove old_chunk_id/chunk_index
Correction: asr-1.json format, generate/apply scripts
API: 37/37 endpoints fixed and tested
Docs: HANDOVER_V2.0.md for M4
This commit is contained in:
Accusys
2026-05-11 07:03:22 +08:00
parent ef894a44ad
commit 39ba5ddf76
147 changed files with 19843 additions and 3053 deletions

View File

@@ -0,0 +1,266 @@
# Face Trace Data Model v1.0.0
## 現狀問題
目前 trace 的資料模型是隱含的 — `face_detections` table 只有一個 `trace_id` 欄位,沒有獨立的 trace 實體:
```sql
-- 現狀trace 只是 face_detections 的一個 grouping column
SELECT trace_id, COUNT(*) FROM face_detections GROUP BY trace_id;
```
這導致:
- Trace metadata持續時間、平均信心度需要 aggregation query 才能取得
- Identity binding 只能在 detection 層級,無法對整個 trace 綁定
- Interpolation 資料沒有標準儲存位置
- 跨 file 的 trace 關聯(同一人 reappear無法表達
## 提議模型
### 新增 `face_traces` table
```sql
CREATE TABLE dev.face_traces (
id BIGSERIAL PRIMARY KEY,
file_uuid VARCHAR(32) NOT NULL,
trace_id INT NOT NULL, -- per-file trace number
identity_id INT REFERENCES dev.identities(id),
-- 時間範圍 (frame-based)
first_frame INT NOT NULL,
last_frame INT NOT NULL,
frame_count INT NOT NULL,
-- 時間範圍 (time-based)
first_sec FLOAT NOT NULL,
last_sec FLOAT NOT NULL,
duration_sec FLOAT NOT NULL,
-- 信心度
avg_confidence FLOAT NOT NULL,
min_confidence FLOAT NOT NULL,
max_confidence FLOAT NOT NULL,
-- 空間範圍
bbox_union JSONB, -- {x, y, w, h} 包含所有 detection 的最小外框
-- 比對用 embedding (trace 級別的 face embedding取質量最好的 detection)
sample_face_id VARCHAR(64), -- 最高信心度的 detection ID
embedding REAL[], -- 該 detection 的 embedding
-- 狀態
status VARCHAR(20) DEFAULT 'active', -- active | merged | deleted
merged_into INT, -- 如果被 merge指向新的 trace_id
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(file_uuid, trace_id)
);
```
### 與現有 `face_detections` 的關係
```
face_traces (new) face_detections (existing)
┌─────────────────────┐ ┌──────────────────────────┐
│ id: 1 │ 1:N │ id: 12400 │
│ trace_id: 3128 │────── │ trace_id: 3128 │
│ file_uuid: 3abeee...│ │ file_uuid: 3abeee... │
│ identity_id: 2102 │ │ frame_number: 68280 │
│ first_frame: 68161 │ │ x: 371, y: 468 │
│ last_frame: 69269 │ │ embedding: [...] │
│ avg_confidence: 0.78│ └──────────────────────────┘
│ sample_face_id: ....│
│ embedding: [...] │
└─────────────────────┘
```
### Migration
```sql
-- 從現有 face_detections 資料建立 face_traces
INSERT INTO dev.face_traces (
file_uuid, trace_id,
first_frame, last_frame, frame_count,
first_sec, last_sec, duration_sec,
avg_confidence, min_confidence, max_confidence
)
SELECT
file_uuid,
trace_id,
MIN(frame_number) AS first_frame,
MAX(frame_number) AS last_frame,
COUNT(*) AS frame_count,
MIN(frame_number)::float / 25.0 AS first_sec,
MAX(frame_number)::float / 25.0 AS last_sec,
(MAX(frame_number) - MIN(frame_number))::float / 25.0 AS duration_sec,
AVG(confidence) AS avg_confidence,
MIN(confidence) AS min_confidence,
MAX(confidence) AS max_confidence
FROM dev.face_detections
WHERE file_uuid = '3abeee81...' AND trace_id IS NOT NULL
GROUP BY file_uuid, trace_id;
```
### 新增 API
#### GET /api/v1/file/:file_uuid/face_trace/:trace_id
回傳單一 trace 的完整 metadata取代目前的 aggregation query
#### PATCH /api/v1/file/:file_uuid/face_trace/:trace_id
更新 trace 屬性(例如綁定 identity
```json
{"identity_id": 2102}
```
#### POST /api/v1/file/:file_uuid/face_trace/merge
合併多個 trace同一人 reappear 被切斷時的處理):
```json
{
"source_trace_ids": [3128, 3201, 3350],
"target_trace_id": 3128
}
```
#### POST /api/v1/file/:file_uuid/face_trace/:trace_id/interpolate
產生並儲存 interpolation 資料:
```json
{
"stride": 1,
"store": true
}
```
## 3D 立體化
### Z 軸來源
目前 2D bbox 可以透過以下方式推估深度 (z)
| 方法 | 公式 | 精度 | 需求 |
|------|------|:----:|------|
| **Bbox 大小推估** | `z = focal_length * real_height / bbox_height` | 低 | 假設人臉大小固定 ~20cm |
| **Bbox 面積** | `z ∝ 1 / sqrt(w * h)` | 低 | 無 |
| **Stereo / 多視角** | 三角測量 | 高 | 需多個 camera |
| **Depth model** | MiDaS / Depth Anything | 高 | 需 GPU inference |
| **LiDAR** | 直接深度 | 最高 | 需 LiDAR 硬體 |
### Z from Bbox Size (最簡單)
人到鏡頭的距離 ≈ `臉部真實大小(20cm) × 焦距 / bbox_pixel_height`
對於無 calibration 的影片,可以用相對深度:
```
z_rel = 1.0 / sqrt(bbox_width × bbox_height)
```
將 z_rel normalize 到 0.0 (最近) ~ 1.0 (最遠),即為相對深度。
### 3D Trace Schema 擴充
```sql
-- 在 face_traces 加入 Z 軸統計
ALTER TABLE dev.face_traces ADD COLUMN z_center FLOAT; -- 平均深度
ALTER TABLE dev.face_traces ADD COLUMN z_min FLOAT; -- 最近
ALTER TABLE dev.face_traces ADD COLUMN z_max FLOAT; -- 最遠
ALTER TABLE dev.face_traces ADD COLUMN z_travel FLOAT; -- 深度總移動量
-- 在 face_detections 加入 Z
ALTER TABLE dev.face_detections ADD COLUMN z_rel FLOAT; -- 單幀相對深度
```
### 3D 軌跡資料格式
```json
GET /api/v1/file/:file_uuid/trace/:trace_id/faces?dimension=3d
{
"trace_id": 3128,
"dimension": "3d",
"faces": [
{
"frame": 68280, "t": 2731.2,
"x": 371, "y": 468, "z": 0.45,
"bbox": {"w": 338, "h": 338}
}
]
}
```
### 從 2D bbox 計算 Z
```python
def bbox_to_z_rel(w: float, h: float, frame_w: int, frame_h: int) -> float:
"""
將 bbox 大小轉換為相對深度
- 傳回值 0.0 = 最近 (最大 bbox)
- 傳回值 1.0 = 最遠 (最小 bbox)
"""
area_pct = (w * h) / (frame_w * frame_h)
# 1% 面積 → z=0 (最近), 0.01% 面積 → z=1 (最遠)
z = 1.0 - min(area_pct * 50, 1.0)
return round(z, 4)
```
### 3D Trace 的應用
| 應用 | 說明 |
|------|------|
| **Approach/Retreat** | 人物走近/遠離鏡頭z 值變化 |
| **Fill ratio** | bbox 面積佔畫面比例 = 鏡頭構圖 |
| **MR Bridge** | (x, y, z, t) 直接餵給 AR/VR 引擎 |
| **Cross-camera** | 同一人物在不同 camera 的 z 值可校準空間位置 |
| **Heatmap Z-layer** | 熱力圖可依 z 值分層(前景 vs 背景) |
### Z 軸視覺化
```
t (time)
│ z (depth)
│ ●────●────●────●────● ← 人物從遠走到近
│ ╲ (z: 0.8 → 0.3)
│ ●────●──●
│ z_travel = 0.5
└──────────────────→ x, y
```
Z 軸變化可視為獨立的時間序列:
```
z_rel
1.0 ┤ far
│ ████
0.8 ┤ ██ ██
│ ██ ██
0.6 ┤ ██ ██
│ ██ ██
0.4 ┤██ ██
│ ██
0.2 ┤ ██
│ ██
0.0 ┤ ██ near
└────────────────────────→ time
2707s 2770s
解讀:人物先逐漸走近 (z 0.5→0.2),最後稍微後退
```
### 與現有系統的整合
| 元件 | 變更 |
|------|------|
| `face_trace/sortby` | 改從 `face_traces` 查詢(更快,不需 GROUP BY |
| `trace/:trace_id/faces` | 不變(仍從 `face_detections` |
| Qdrant sync | trace 層級的 embedding 寫入獨立 collection |
| Video render | 從 `face_traces` 讀 metadata 決定 render 參數 |
| Portal Timeline | 從 `face_traces` 讀取 identity 名稱顯示 |

View File

@@ -0,0 +1,209 @@
# Virtual Character Model v1.0.0
從 face traces 重建虛擬人物。
## Concept
將影片中同一 identity 的所有 trace 合併為一個**虛擬人物模型**,包含:
```
影片中的 Cary Grant
┌─────────────────────────┐
│ Virtual Character │
│ ├── Identity: Cary │
│ ├── 3D Paths │ ← 所有 trace 的 (x,y,z,t) 軌跡
│ ├── Appearance: │ ← 臉部樣本、embedding
│ ├── Voice: │ ← ASRX speaker embedding
│ ├── Behavior: │ ← 移動速度、停留位置
│ └── MR Data: │ ← 可直接餵給 AR/VR 的格式
└─────────────────────────┘
```
## Data Model
### Characters Table
```sql
CREATE TABLE dev.characters (
id BIGSERIAL PRIMARY KEY,
identity_id INT REFERENCES dev.identities(id),
file_uuid VARCHAR(32), -- 來源影片 (可跨多片)
-- 3D 空間範圍
world_bbox JSONB, -- 此角色在場景中的 3D 活動範圍
total_travel FLOAT, -- 總移動距離 (m)
-- 外觀
sample_image TEXT, -- 最佳臉部截圖路徑
face_model REAL[], -- 平均 face embedding
voice_model REAL[], -- 平均 voice embedding
-- 行為特徵
avg_speed FLOAT, -- 平均移動速度
height_avg FLOAT, -- 平均出現高度 (y%)
hotspots JSONB, -- 經常停留的區域 [{x, y, z, duration}]
-- MR
gltf_url TEXT, -- 3D 模型的 glTF 路徑(可選)
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
### Character Paths Table
```sql
CREATE TABLE dev.character_paths (
id BIGSERIAL PRIMARY KEY,
character_id INT REFERENCES dev.characters(id),
trace_id INT, -- 來源 trace
file_uuid VARCHAR(32),
-- 3D 軌跡 (簡化版 waypoints)
waypoints JSONB NOT NULL, -- [{t, x, y, z}, ...]
-- 統計
duration FLOAT,
distance FLOAT, -- 移動距離
speed_avg FLOAT,
speed_max FLOAT,
start_time FLOAT,
end_time FLOAT
);
```
## 虛擬人物建構流程
```
1. Face Detection
└→ 2D bbox (x, y, w, h) per frame
2. Face Tracking
└→ trace_id 賦予
3. 3D 化
└→ z = f(bbox_size) → 3D point (x, y, z, t)
4. Identity Binding
└→ trace_id → identity_id
5. Character Assembly
└→ 同一 identity 的所有 trace 合併
├── 路徑拼接trace 中斷處用 interpolation 連接
├── 速度曲線:計算各 segment 的速度
├── 熱點分析:找出停留點
└── 外觀模型:平均 face embedding
6. MR Export
└→ glTF / USDZ / 自訂格式
```
## 視覺化
### 角色路徑總覽
```
Cary Grant 在 Charade 中的完整路徑:
Y%
100% ┤
│ ╔══╗
│ ╔══╝ ╚══╗
50% ┤ ╔═══╝ ╚══╗
│ ╔═══╝ ╚══╗
│ ╔══╝ ╚══╗
0% ┤═╝ ╚════
└────────────────────────────────────────→ X%
0% 20% 40% 60% 80% 100%
點 → 每次出現的起始位置
線 → 移動軌跡
顏色 → 時間 (冷→暖)
```
### 行為分析
```json
{
"character": "Cary Grant",
"total_appearances": 47,
"total_screen_time": 823.5,
"avg_speed": 0.32,
"hotspots": [
{"x": 0.5, "y": 0.4, "duration": 45.2, "label": "沙發區"},
{"x": 0.7, "y": 0.3, "duration": 28.1, "label": "門口"}
],
"speed_profile": {
"still": 0.35,
"walking": 0.55,
"fast": 0.10
}
}
```
### MR 輸出
```json
{
"format": "momentry_character",
"version": "1.0",
"character": {
"name": "Cary Grant",
"tmdb_id": 2102
},
"scene": {
"file_uuid": "3abeee81...",
"duration": 5954
},
"paths": [
{
"trace_id": 3128,
"waypoints": [
{"t": 2707, "x": 0.12, "y": 0.25, "z": 0.45},
{"t": 2730, "x": 0.35, "y": 0.40, "z": 0.30},
{"t": 2750, "x": 0.50, "y": 0.55, "z": 0.20}
]
}
]
}
```
## API
### POST /api/v1/character/build
從 file 建立角色模型。
```json
{
"file_uuid": "3abeee81...",
"identity_ids": [2102, 187],
"include_mr_export": true
}
```
### GET /api/v1/character/:character_id
取得角色模型完整資料。
### GET /api/v1/character/:character_id/paths
取得角色 3D 路徑 for MR rendering。
## 與 Trace 的關係
```
Trace (現有) Character (新增)
┌────────────┐ ┌──────────────────┐
│ trace_id │ 1:N │ character_id │
│ file_uuid │────────────── │ identity_id │
│ face_count │ 多個 trace │ world_bbox │
│ duration │ 組成一個角色 │ total_travel │
│ 2D bbox │ │ speed_profile │
│ z from bbox│ │ mr_export │
└────────────┘ └──────────────────┘
```