mirror of
https://github.com/yakhyo/uniface.git
synced 2026-05-17 14:23:46 +00:00
291 lines
9.5 KiB
Plaintext
291 lines
9.5 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Face Search: One-to-Many Face Matching\n",
|
|
"\n",
|
|
"<div style=\"display:flex; flex-wrap:wrap; align-items:center;\">\n",
|
|
" <a style=\"margin-right:10px; margin-bottom:6px;\" href=\"https://pepy.tech/projects/uniface\"><img alt=\"PyPI Downloads\" src=\"https://static.pepy.tech/personalized-badge/uniface?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Downloads\"></a>\n",
|
|
" <a style=\"margin-right:10px; margin-bottom:6px;\" href=\"https://pypi.org/project/uniface/\"><img alt=\"PyPI Version\" src=\"https://img.shields.io/pypi/v/uniface.svg\"></a>\n",
|
|
" <a style=\"margin-right:10px; margin-bottom:6px;\" href=\"https://opensource.org/licenses/MIT\"><img alt=\"License\" src=\"https://img.shields.io/badge/License-MIT-blue.svg\"></a>\n",
|
|
" <a style=\"margin-bottom:6px;\" href=\"https://github.com/yakhyo/uniface\"><img alt=\"GitHub Stars\" src=\"https://img.shields.io/github/stars/yakhyo/uniface.svg?style=social\"></a>\n",
|
|
"</div>\n",
|
|
"\n",
|
|
"**UniFace** is a lightweight, production-ready Python library for face detection, recognition, tracking, landmark analysis, face parsing, gaze estimation, and face attributes.\n",
|
|
"\n",
|
|
"🔗 **GitHub**: [github.com/yakhyo/uniface](https://github.com/yakhyo/uniface) | 📚 **Docs**: [yakhyo.github.io/uniface](https://yakhyo.github.io/uniface)\n",
|
|
"\n",
|
|
"---\n",
|
|
"\n",
|
|
"This notebook demonstrates how to build a face database and search for matching faces - useful for photo organization, security systems, and social media applications."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%pip install -q \"uniface[cpu]\"\n",
|
|
"\n",
|
|
"# Clone repo for assets (Colab only)\n",
|
|
"import os\n",
|
|
"if 'COLAB_GPU' in os.environ or 'COLAB_RELEASE_TAG' in os.environ:\n",
|
|
" if not os.path.exists('uniface'):\n",
|
|
" !git clone --depth 1 https://github.com/yakhyo/uniface.git\n",
|
|
" os.chdir('uniface/examples')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 2. Import Libraries"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import cv2\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"\n",
|
|
"import uniface\n",
|
|
"from uniface.analyzer import FaceAnalyzer\n",
|
|
"from uniface.detection import RetinaFace\n",
|
|
"from uniface.recognition import ArcFace\n",
|
|
"\n",
|
|
"print(uniface.__version__)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"analyzer = FaceAnalyzer(\n",
|
|
" detector=RetinaFace(confidence_threshold=0.5),\n",
|
|
" recognizer=ArcFace()\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Load Einstein's photo\n",
|
|
"einstein_path = '../assets/einstien.png'\n",
|
|
"einstein_image = cv2.imread(einstein_path)\n",
|
|
"\n",
|
|
"# Get Einstein's face features\n",
|
|
"einstein_faces = analyzer.analyze(einstein_image)\n",
|
|
"\n",
|
|
"if einstein_faces:\n",
|
|
" einstein_face = einstein_faces[0]\n",
|
|
" print(f'Detected {len(einstein_faces)} face with {einstein_face.embedding.shape[0]}D features')\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Load the group photo\n",
|
|
"group_photo_path = '../assets/scientists.png'\n",
|
|
"group_photo = cv2.imread(group_photo_path)\n",
|
|
"\n",
|
|
"# Find all faces in the group photo\n",
|
|
"group_faces = analyzer.analyze(group_photo)\n",
|
|
"print(f'Detected {len(group_faces)} people in the group photo')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"fig, axes = plt.subplots(1, 2, figsize=(15, 6))\n",
|
|
"\n",
|
|
"# Display Einstein's photo\n",
|
|
"axes[0].imshow(cv2.cvtColor(einstein_image, cv2.COLOR_BGR2RGB))\n",
|
|
"axes[0].set_title(\"Who we're looking for: Einstein\", fontsize=14, fontweight='bold')\n",
|
|
"axes[0].axis('off')\n",
|
|
"\n",
|
|
"# Display the group photo\n",
|
|
"axes[1].imshow(cv2.cvtColor(group_photo, cv2.COLOR_BGR2RGB))\n",
|
|
"axes[1].set_title(f'Where we search: Group of {len(group_faces)} scientists', fontsize=14, fontweight='bold')\n",
|
|
"axes[1].axis('off')\n",
|
|
"\n",
|
|
"plt.tight_layout()\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if not einstein_faces or not group_faces:\n",
|
|
" print('Error: Could not detect faces')\n",
|
|
"else:\n",
|
|
" # Compare Einstein with each person in the group\n",
|
|
" matches = []\n",
|
|
" for i, person in enumerate(group_faces):\n",
|
|
" similarity = einstein_face.compute_similarity(person)\n",
|
|
" matches.append((i, similarity))\n",
|
|
"\n",
|
|
" # Sort by similarity (best matches first)\n",
|
|
" matches.sort(key=lambda x: x[1], reverse=True)\n",
|
|
"\n",
|
|
" # Show top 5 matches\n",
|
|
" print('Top 5 most similar people:')\n",
|
|
" for rank, (person_idx, similarity) in enumerate(matches[:5], 1):\n",
|
|
" print(f'{rank}. Person #{person_idx + 1}: similarity = {similarity:.4f}')\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if einstein_faces and group_faces:\n",
|
|
" # Get the best match\n",
|
|
" best_match_idx, best_similarity = matches[0]\n",
|
|
"\n",
|
|
" # Draw bounding boxes\n",
|
|
" result_image = group_photo.copy()\n",
|
|
"\n",
|
|
" for i, person in enumerate(group_faces):\n",
|
|
" bbox = person.bbox.astype(int)\n",
|
|
"\n",
|
|
" if i == best_match_idx:\n",
|
|
" color = (0, 255, 0)\n",
|
|
" thickness = 3\n",
|
|
" else:\n",
|
|
" color = (128, 128, 128)\n",
|
|
" thickness = 1\n",
|
|
"\n",
|
|
" cv2.rectangle(result_image, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color, thickness)\n",
|
|
"\n",
|
|
" if i == best_match_idx:\n",
|
|
" label = f'Match: {best_similarity:.3f}'\n",
|
|
" cv2.putText(result_image, label, (bbox[0], bbox[1] - 10),\n",
|
|
" cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2)\n",
|
|
"\n",
|
|
" plt.figure(figsize=(15, 10))\n",
|
|
" plt.imshow(cv2.cvtColor(result_image, cv2.COLOR_BGR2RGB))\n",
|
|
" plt.title(f'Best match: Person #{best_match_idx + 1}', fontsize=14)\n",
|
|
" plt.axis('off')\n",
|
|
" plt.tight_layout()\n",
|
|
" plt.show()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if einstein_faces and group_faces:\n",
|
|
" # Show top 3 matches\n",
|
|
" top_k = min(3, len(matches))\n",
|
|
"\n",
|
|
" fig, axes = plt.subplots(1, top_k + 1, figsize=(16, 4))\n",
|
|
"\n",
|
|
" # Show Einstein's face\n",
|
|
" einstein_rgb = cv2.cvtColor(einstein_image, cv2.COLOR_BGR2RGB)\n",
|
|
" axes[0].imshow(einstein_rgb)\n",
|
|
" axes[0].set_title(\"Query\", fontsize=12)\n",
|
|
" axes[0].axis('off')\n",
|
|
"\n",
|
|
" # Show top 3 matches from the group\n",
|
|
" for i, (person_idx, similarity) in enumerate(matches[:top_k]):\n",
|
|
" person = group_faces[person_idx]\n",
|
|
" bbox = person.bbox.astype(int)\n",
|
|
"\n",
|
|
" # Crop this person's face\n",
|
|
" face_crop = group_photo[bbox[1]:bbox[3], bbox[0]:bbox[2]]\n",
|
|
"\n",
|
|
" if face_crop.size > 0:\n",
|
|
" face_rgb = cv2.cvtColor(face_crop, cv2.COLOR_BGR2RGB)\n",
|
|
" axes[i + 1].imshow(face_rgb)\n",
|
|
" axes[i + 1].set_title(f'Match {i + 1}: {similarity:.3f}', fontsize=12)\n",
|
|
" axes[i + 1].axis('off')\n",
|
|
"\n",
|
|
" plt.tight_layout()\n",
|
|
" plt.show()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Use threshold to determine if it's a match\n",
|
|
"THRESHOLD = 0.6\n",
|
|
"\n",
|
|
"if einstein_faces and group_faces:\n",
|
|
" best_match_idx, best_similarity = matches[0]\n",
|
|
"\n",
|
|
" print(f'Best match: Person #{best_match_idx + 1}')\n",
|
|
" print(f'Similarity: {best_similarity:.4f}')\n",
|
|
" print(f'Threshold: {THRESHOLD}')\n",
|
|
"\n",
|
|
" if best_similarity > THRESHOLD:\n",
|
|
" print(f'Result: Match found (Einstein is person #{best_match_idx + 1})')\n",
|
|
" else:\n",
|
|
" print(f'Result: No match (similarity below threshold)')\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Notes\n",
|
|
"\n",
|
|
"- Similarity score ranges from -1 to 1 (higher = more similar)\n",
|
|
"- Threshold of 0.6 is commonly used (above = match, below = no match)\n",
|
|
"- Adjust threshold based on your use case (higher = stricter matching)\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "base",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.13.5"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|