Skip to content

Face Filter API 使用指南 — Studio 團隊參考

版本: v1.0
日期: 2026-08-09
狀態: 已實作


概述

Face Filter API 提供臉部過濾功能,讓 Studio 前端可以: 1. 查詢臉部列表(支持來源過濾) 2. 獲取單一臉部過濾報告 3. 套用過濾規則 4. 獲取過濾統計 5. 管理過濾規則


API 端點

1. 獲取臉部列表

端點: GET /api/v1/file/{file_uuid}/faces

功能: 獲取檔案的臉部列表,支持分頁和來源過濾

查詢參數:

{
  "source": "all",           // "all" | "apple_vision" | "mediapipe"
  "page": 1,                 // 頁碼(從 1 開始)
  "page_size": 50            // 每頁數量
}

回應範例:

{
  "success": true,
  "data": {
    "faces": [
      {
        "face_id": "face_001",
        "frame": 671,
        "primary_source": "apple_vision",
        "primary_confidence": 0.84,
        "detection_sources": [
          {"source_type": "apple_vision", "confidence": 0.84},
          {"source_type": "mediapipe", "confidence": 0.92}
        ],
        "filter_features": {
          "face_id": "face_001",
          "file_uuid": "0489e104d7d1e11e613da01f7aa130ca",
          "frame": 671,
          "bbox_size": 336,              // 新增:min(width, height)
          "eye_ratio": 0.33,
          "nose_ratio": 0.02,
          "pose_score": 0.08,
          "mediapipe_detected": true,
          "mediapipe_iou": 0.85,
          "embedding_similarity": 0.82,
          "consecutive_frames": 15,
          "total_frames_detected": 15,
          "apple_vision_confidence": 0.84,
          "mediapipe_confidence": 0.92,
          "primary_source": "apple_vision"
        }
      }
    ],
    "pagination": {
      "page": 1,
      "page_size": 50,
      "total": 392,
      "total_pages": 8
    },
    "source_summary": {
      "apple_vision": 392,
      "mediapipe": 5,
      "merged_unique": 392
    }
  }
}

Studio 使用範例:

// 獲取第一頁臉部
const response = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/faces', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    source: 'all',
    page: 1,
    page_size: 50
  })
});

const data = await response.json();
console.log(`Total faces: ${data.data.pagination.total}`);
console.log(`First face: ${data.data.faces[0].face_id}`);


2. 獲取單一臉部過濾報告

端點: GET /api/v1/file/{file_uuid}/face/{face_id}/filter-report

功能: 獲取單一臉部的詳細過濾報告

回應範例:

{
  "face_id": "face_001",
  "frame": 671,
  "overall_confidence": 0.45,
  "recommendation": "reject",
  "rule_results": {
    "landmark_analysis": {
      "enabled": true,
      "score": 0.75,
      "passed": true,
      "reason": "nose_ratio too low (0.020)"
    },
    "mediapipe_verification": {
      "enabled": true,
      "score": 1.0,
      "passed": true,
      "reason": null
    },
    "embedding_similarity": {
      "enabled": true,
      "score": 0.21,
      "passed": false,
      "reason": "Low embedding similarity (0.150)"
    },
    "temporal_consistency": {
      "enabled": true,
      "score": 0.33,
      "passed": false,
      "reason": "Single frame detection (1)"
    }
  },
  "reasons": [
    "nose_ratio too low (0.020)",
    "Low embedding similarity (0.150)",
    "Single frame detection (1)"
  ]
}

Studio 使用範例:

// 獲取臉部過濾報告
const response = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/face/face_001/filter-report');
const report = await response.json();

console.log(`Recommendation: ${report.recommendation}`);
console.log(`Confidence: ${report.overall_confidence}`);
console.log(`Reasons: ${report.reasons.join(', ')}`);

// 顯示每個規則的結果
Object.entries(report.rule_results).forEach(([rule, result]) => {
  console.log(`${rule}: ${result.passed ? '✅' : '❌'} (score: ${result.score})`);
});


3. 套用過濾規則

端點: POST /api/v1/file/{file_uuid}/faces/filter

功能: 套用過濾規則到所有臉部,返回過濾結果

請求範例:

{
  "rules": {
    "version": "1.0",
    "rules": {
      "landmark_analysis": {
        "enabled": true,
        "weight": 0.3,
        "thresholds": {
          "eye_ratio_min": 0.25,
          "eye_ratio_max": 0.55,
          "nose_ratio_min": 0.08,
          "pose_score_max": 0.8
        }
      },
      "mediapipe_verification": {
        "enabled": true,
        "weight": 0.2,
        "thresholds": {
          "min_iou": 0.3
        }
      },
      "embedding_similarity": {
        "enabled": true,
        "weight": 0.3,
        "thresholds": {
          "min_similarity": 0.70
        }
      },
      "temporal_consistency": {
        "enabled": true,
        "weight": 0.2,
        "thresholds": {
          "min_consecutive_frames": 3
        }
      }
    },
    "overall_thresholds": {
      "auto_accept": 0.7,
      "auto_reject": 0.3,
      "review_range": [0.3, 0.7]
    }
  }
}

回應範例:

{
  "total_faces": 392,
  "passed": 385,
  "filtered": 7,
  "filtered_faces": [
    {
      "face_id": "face_001",
      "frame": 671,
      "reasons": [
        "nose_ratio too low (0.020)",
        "Low embedding similarity (0.150)",
        "Single frame detection (1)"
      ]
    }
  ],
  "filter_stats": {
    "landmark": 3,
    "mediapipe": 1,
    "embedding": 2,
    "temporal": 1
  }
}

Studio 使用範例:

// 套用過濾規則
const filterConfig = {
  rules: {
    landmark_analysis: {
      enabled: true,
      weight: 0.3,
      thresholds: {
        eye_ratio_min: 0.25,
        eye_ratio_max: 0.55,
        nose_ratio_min: 0.08,
        pose_score_max: 0.8
      }
    },
    // ... 其他規則
  },
  overall_thresholds: {
    auto_accept: 0.7,
    auto_reject: 0.3,
    review_range: [0.3, 0.7]
  }
};

const response = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/faces/filter', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ rules: filterConfig })
});

const result = await response.json();
console.log(`Passed: ${result.passed}, Filtered: ${result.filtered}`);

// 顯示被過濾的臉部
result.filtered_faces.forEach(face => {
  console.log(`Face ${face.face_id} (frame ${face.frame}): ${face.reasons.join(', ')}`);
});


4. 獲取過濾統計

端點: GET /api/v1/file/{file_uuid}/filter-stats

功能: 獲取臉部的過濾統計資訊

回應範例:

{
  "total_faces": 392,
  "by_category": {
    "total_faces": 392,
    "by_source": {
      "apple_vision": 392,
      "mediapipe": 5
    },
    "by_quality": {
      "avg_eye_ratio": 0.37,
      "avg_nose_ratio": 0.12,
      "avg_pose_score": 0.15,
      "mediapipe_verified": 5,
      "avg_embedding_similarity": 0.78,
      "avg_consecutive_frames": 12.5
    }
  }
}

Studio 使用範例:

// 獲取過濾統計
const response = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/filter-stats');
const stats = await response.json();

console.log(`Total faces: ${stats.total_faces}`);
console.log(`Apple Vision faces: ${stats.by_category.by_source.apple_vision}`);
console.log(`MediaPipe faces: ${stats.by_category.by_source.mediapipe}`);
console.log(`Avg embedding similarity: ${stats.by_category.by_quality.avg_embedding_similarity}`);


5. 管理過濾規則

5.1 獲取過濾規則

端點: GET /api/v1/file/{file_uuid}/filter-rules

回應範例:

{
  "rules": [
    {
      "id": 1,
      "rule_name": "default_filter",
      "rules": {
        "version": "1.0",
        "rules": {
          "landmark_analysis": { ... },
          "mediapipe_verification": { ... },
          "embedding_similarity": { ... },
          "temporal_consistency": { ... }
        },
        "overall_thresholds": { ... }
      },
      "is_active": true,
      "created_at": "2026-08-09T12:00:00Z",
      "updated_at": "2026-08-09T12:00:00Z"
    }
  ]
}

5.2 儲存過濾規則

端點: POST /api/v1/file/{file_uuid}/filter-rules

請求範例:

{
  "rule_name": "strict_filter",
  "rules": {
    "version": "1.0",
    "rules": {
      "landmark_analysis": {
        "enabled": true,
        "weight": 0.3,
        "thresholds": {
          "eye_ratio_min": 0.30,
          "eye_ratio_max": 0.50,
          "nose_ratio_min": 0.10,
          "pose_score_max": 0.5
        }
      },
      // ... 其他規則
    },
    "overall_thresholds": {
      "auto_accept": 0.8,
      "auto_reject": 0.2,
      "review_range": [0.2, 0.8]
    }
  },
  "is_active": true
}

Studio 使用範例:

// 獲取現有規則
const getResponse = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/filter-rules');
const rulesData = await getResponse.json();

// 儲存新規則
const strictFilter = {
  rule_name: "strict_filter",
  rules: {
    // ... 規則配置
  },
  is_active: true
};

const saveResponse = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/filter-rules', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(strictFilter)
});

const savedRule = await saveResponse.json();
console.log(`Saved rule: ${savedRule.rule_name} (id: ${savedRule.id})`);


過濾規則說明

規則類型

1. Landmark 分析 (landmark_analysis)

功能: 檢查臉部 landmark 幾何特徵是否正常

閾值: - eye_ratio_min: 眼睛間距比例最小值(預設 0.25) - eye_ratio_max: 眼睛間距比例最大值(預設 0.55) - nose_ratio_min: 眼睛到鼻子比例最小值(預設 0.08) - pose_score_max: Pose 角度評分最大值(預設 0.8)

權重: 0.3


2. MediaPipe 驗證 (mediapipe_verification)

功能: 驗證 MediaPipe 是否也檢測到該臉部

值: - min_iou: 最小 IoU 值(預設 0.3)

權重: 0.2


3. Embedding 相似度 (embedding_similarity)

功能: 檢查臉部 embedding 與已知人臉的相似度

閾值: - min_similarity: 最小相似度(預設 0.70)

權重: 0.3


4. 時間一致性 (temporal_consistency)

功能: 檢查臉部是否在連續幀中出現

閾值: - min_consecutive_frames: 最小連續數(預設 3)

權重: 0.2


總體閾值

overall_thresholds: - auto_accept: 自動接受閾值(預設 0.7) - auto_reject: 自動拒絕閾值(預設 0.3) - review_range: 審查範圍(預設 [0.3, 0.7])

建議: - overall_confidence >= 0.7: 接受 - overall_confidence <= 0.3: 拒絕 - 0.3 < overall_confidence < 0.7: 審查


Bbox Size 說明

欄位: bbox_size

計算方式: min(width, height)

說明: - 預先計算並儲存於後端 - 用於前端即時過濾 - 維持當前 Swift 實作的計算方式

前端過濾範例:

// 前端即時過濾
const quickFilter = (faces, rules) => {
  return faces.filter(face => {
    // Bbox size 過濾
    if (rules.min_bbox_size && face.filter_features.bbox_size < rules.min_bbox_size) {
      return false;
    }
    return true;
  });
};

// 使用範例
const filtered = quickFilter(allFaces, {
  min_bbox_size: 80  // 最小 80 像素
});

建議閾值: - 保守: 80 像素 - 平衡: 64 像素(當前預設) - 寬鬆: 48 像素

未來擴展: - 可考慮增加 max(bbox.width, bbox.height) 選項 - 可考慮增加 bbox.width * bbox.height (面積) 選項


預設配置

API 提供預設過濾規則,可直接使用:

{
  "version": "1.0",
  "rules": {
    "landmark_analysis": {
      "enabled": true,
      "weight": 0.3,
      "thresholds": {
        "eye_ratio_min": 0.25,
        "eye_ratio_max": 0.55,
        "nose_ratio_min": 0.08,
        "pose_score_max": 0.8
      }
    },
    "mediapipe_verification": {
      "enabled": true,
      "weight": 0.2,
      "thresholds": {
        "min_iou": 0.3
      }
    },
    "embedding_similarity": {
      "enabled": true,
      "weight": 0.3,
      "thresholds": {
        "min_similarity": 0.70
      }
    },
    "temporal_consistency": {
      "enabled": true,
      "weight": 0.2,
      "thresholds": {
        "min_consecutive_frames": 3
      }
    }
  },
  "overall_thresholds": {
    "auto_accept": 0.7,
    "auto_reject": 0.3,
    "review_range": [0.3, 0.7]
  }
}

完整使用範例

場景 1: 過濾低品質臉部

// 1. 獲取臉部列表
const facesResponse = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/faces', {
  method: 'POST',
  body: JSON.stringify({ source: 'all', page: 1, page_size: 100 })
});
const facesData = await facesResponse.json();

// 2. 套用過濾規則
const filterResponse = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/faces/filter', {
  method: 'POST',
  body: JSON.stringify({ rules: null })  // 使用預設規則
});
const filterResult = await filterResponse.json();

console.log(`Total: ${filterResult.total_faces}`);
console.log(`Passed: ${filterResult.passed}`);
console.log(`Filtered: ${filterResult.filtered}`);

// 3. 顯示被過濾的臉部
filterResult.filtered_faces.forEach(face => {
  console.log(`❌ Face ${face.face_id} (frame ${face.frame})`);
  face.reasons.forEach(reason => {
    console.log(`   - ${reason}`);
  });
});

場景 2: 自訂嚴格過濾

// 自訂嚴格過濾規則
const strictRules = {
  version: "1.0",
  rules: {
    landmark_analysis: {
      enabled: true,
      weight: 0.3,
      thresholds: {
        eye_ratio_min: 0.30,  // 更嚴格
        eye_ratio_max: 0.50,  // 更嚴格
        nose_ratio_min: 0.10,  // 更嚴格
        pose_score_max: 0.5    // 更嚴格
      }
    },
    mediapipe_verification: {
      enabled: true,
      weight: 0.2,
      thresholds: {
        min_iou: 0.5  // 更嚴格
      }
    },
    embedding_similarity: {
      enabled: true,
      weight: 0.3,
      thresholds: {
        min_similarity: 0.80  // 更嚴格
      }
    },
    temporal_consistency: {
      enabled: true,
      weight: 0.2,
      thresholds: {
        min_consecutive_frames: 5  // 更嚴格
      }
    }
  },
  overall_thresholds: {
    auto_accept: 0.8,    // 更嚴格
    auto_reject: 0.2,    // 更嚴格
    review_range: [0.2, 0.8]
  }
};

// 儲存規則
await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/filter-rules', {
  method: 'POST',
  body: JSON.stringify({
    rule_name: "strict_filter",
    rules: strictRules,
    is_active: true
  })
});

// 套用嚴格規則
const result = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/faces/filter', {
  method: 'POST',
  body: JSON.stringify({ rules: strictRules })
});

錯誤處理

常見錯誤

404 Not Found:

{
  "error": "Face not found"
}
原因: 臉部 ID 不存在

500 Internal Server Error:

{
  "error": "Database error"
}
原因: 資料庫查詢失敗

錯誤處理範例

try {
  const response = await fetch('/api/v1/file/0489e104d7d1e11e613da01f7aa130ca/face/face_999/filter-report');

  if (!response.ok) {
    if (response.status === 404) {
      console.error('Face not found');
    } else {
      console.error('Server error');
    }
    return;
  }

  const report = await response.json();
  console.log(report);
} catch (error) {
  console.error('Request failed:', error);
}

最佳實踐

1. 使用預設規則開始

建議先使用預設規則,再根據實際需求調整。

2. 逐步調整閾值

不要一次性調整所有值,逐步調整並觀察結果。

3. 監控過濾統計

定期檢查過濾統計,確保過濾規則合理。

4. 保存多個規則配置

為不同場景保存不同的規則配置(如:嚴格、平衡、寬鬆)。

5. 使用者自訂

允許使用者自訂過濾規則,提供預設模板。


支援與反饋

如有問題或建議,請聯繫 Core Team。

文件版本: v1.0
最後更新: 2026-08-09