diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 256c185..49c53a5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -13,6 +13,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full history for git-committers and git-revision-date plugins - uses: actions/setup-python@v5 with: @@ -21,9 +23,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install mkdocs-material pymdown-extensions + pip install mkdocs-material pymdown-extensions mkdocs-git-committers-plugin-2 mkdocs-git-revision-date-localized-plugin - name: Build docs + env: + MKDOCS_GIT_COMMITTERS_APIKEY: ${{ secrets.MKDOCS_GIT_COMMITTERS_APIKEY }} run: mkdocs build --strict - name: Deploy to GitHub Pages diff --git a/MODELS.md b/MODELS.md deleted file mode 100644 index e977fc3..0000000 --- a/MODELS.md +++ /dev/null @@ -1,533 +0,0 @@ -# UniFace Model Zoo - -Complete guide to all available models, their performance characteristics, and selection criteria. - ---- - -## Face Detection Models - -### RetinaFace Family - -RetinaFace models are trained on the WIDER FACE dataset and provide excellent accuracy-speed tradeoffs. - -| Model Name | Params | Size | Easy | Medium | Hard | Use Case | -| -------------- | ------ | ----- | ------ | ------ | ------ | ----------------------------- | -| `MNET_025` | 0.4M | 1.7MB | 88.48% | 87.02% | 80.61% | Mobile/Edge devices | -| `MNET_050` | 1.0M | 2.6MB | 89.42% | 87.97% | 82.40% | Mobile/Edge devices | -| `MNET_V1` | 3.5M | 3.8MB | 90.59% | 89.14% | 84.13% | Balanced mobile | -| `MNET_V2` ⭐ | 3.2M | 3.5MB | 91.70% | 91.03% | 86.60% | **Recommended default** | -| `RESNET18` | 11.7M | 27MB | 92.50% | 91.02% | 86.63% | Server/High accuracy | -| `RESNET34` | 24.8M | 56MB | 94.16% | 93.12% | 88.90% | Maximum accuracy | - -**Accuracy**: WIDER FACE validation set (Easy/Medium/Hard subsets) - from [RetinaFace paper](https://arxiv.org/abs/1905.00641) -**Speed**: Benchmark on your own hardware using `tools/detection.py --source --iterations 100` - -#### Usage - -```python -from uniface import RetinaFace -from uniface.constants import RetinaFaceWeights - -# Default (recommended) -detector = RetinaFace() # Uses MNET_V2 - -# Specific model -detector = RetinaFace( - model_name=RetinaFaceWeights.MNET_025, # Fastest - confidence_threshold=0.5, - nms_thresh=0.4, - input_size=(640, 640) -) -``` - ---- - -### SCRFD Family - -SCRFD (Sample and Computation Redistribution for Efficient Face Detection) models offer state-of-the-art speed-accuracy tradeoffs. - -| Model Name | Params | Size | Easy | Medium | Hard | Use Case | -| ---------------- | ------ | ----- | ------ | ------ | ------ | ------------------------------- | -| `SCRFD_500M` | 0.6M | 2.5MB | 90.57% | 88.12% | 68.51% | Real-time applications | -| `SCRFD_10G` ⭐ | 4.2M | 17MB | 95.16% | 93.87% | 83.05% | **High accuracy + speed** | - -**Accuracy**: WIDER FACE validation set - from [SCRFD paper](https://arxiv.org/abs/2105.04714) -**Speed**: Benchmark on your own hardware using `tools/detection.py --source --iterations 100` - -#### Usage - -```python -from uniface import SCRFD -from uniface.constants import SCRFDWeights - -# Fast real-time detection -detector = SCRFD( - model_name=SCRFDWeights.SCRFD_500M_KPS, - confidence_threshold=0.5, - input_size=(640, 640) -) - -# High accuracy -detector = SCRFD( - model_name=SCRFDWeights.SCRFD_10G_KPS, - confidence_threshold=0.5 -) -``` - ---- - -### YOLOv5-Face Family - -YOLOv5-Face models provide excellent detection accuracy with 5-point facial landmarks, optimized for real-time applications. - -| Model Name | Size | Easy | Medium | Hard | Use Case | -| -------------- | ---- | ------ | ------ | ------ | ------------------------------ | -| `YOLOV5N` | 11MB | 93.61% | 91.52% | 80.53% | Lightweight/Mobile | -| `YOLOV5S` ⭐ | 28MB | 94.33% | 92.61% | 83.15% | **Real-time + accuracy** | -| `YOLOV5M` | 82MB | 95.30% | 93.76% | 85.28% | High accuracy | - -**Accuracy**: WIDER FACE validation set - from [YOLOv5-Face paper](https://arxiv.org/abs/2105.12931) -**Speed**: Benchmark on your own hardware using `tools/detection.py --source --iterations 100` -**Note**: Fixed input size of 640×640. Models exported to ONNX from [deepcam-cn/yolov5-face](https://github.com/deepcam-cn/yolov5-face) - -#### Usage - -```python -from uniface import YOLOv5Face -from uniface.constants import YOLOv5FaceWeights - -# Lightweight/Mobile -detector = YOLOv5Face( - model_name=YOLOv5FaceWeights.YOLOV5N, - confidence_threshold=0.6, - nms_thresh=0.5 -) - -# Real-time detection (recommended) -detector = YOLOv5Face( - model_name=YOLOv5FaceWeights.YOLOV5S, - confidence_threshold=0.6, - nms_thresh=0.5 -) - -# High accuracy -detector = YOLOv5Face( - model_name=YOLOv5FaceWeights.YOLOV5M, - confidence_threshold=0.6 -) - -# Detect faces with landmarks -faces = detector.detect(image) -for face in faces: - bbox = face.bbox # [x1, y1, x2, y2] - confidence = face.confidence - landmarks = face.landmarks # 5-point landmarks (5, 2) -``` - ---- - -## Face Recognition Models - -### ArcFace - -State-of-the-art face recognition using additive angular margin loss. - -| Model Name | Backbone | Params | Size | Use Case | -| ----------- | --------- | ------ | ----- | -------------------------------- | -| `MNET` ⭐ | MobileNet | 2.0M | 8MB | **Balanced (recommended)** | -| `RESNET` | ResNet50 | 43.6M | 166MB | Maximum accuracy | - -**Dataset**: Trained on MS1M-V2 (5.8M images, 85K identities) -**Accuracy**: Benchmark on your own dataset or use standard face verification benchmarks - -#### Usage - -```python -from uniface import ArcFace -from uniface.constants import ArcFaceWeights - -# Default (MobileNet backbone) -recognizer = ArcFace() - -# High accuracy (ResNet50 backbone) -recognizer = ArcFace(model_name=ArcFaceWeights.RESNET) - -# Extract embedding -embedding = recognizer.get_normalized_embedding(image, landmarks) -# Returns: (1, 512) normalized embedding vector -``` - ---- - -### MobileFace - -Lightweight face recognition optimized for mobile devices. - -| Model Name | Backbone | Params | Size | LFW | CALFW | CPLFW | AgeDB-30 | Use Case | -| ----------------- | ---------------- | ------ | ---- | ------ | ------ | ------ | -------- | --------------------- | -| `MNET_025` | MobileNetV1 0.25 | 0.36M | 1MB | 98.76% | 92.02% | 82.37% | 90.02% | Ultra-lightweight | -| `MNET_V2` ⭐ | MobileNetV2 | 2.29M | 4MB | 99.55% | 94.87% | 86.89% | 95.16% | **Mobile/Edge** | -| `MNET_V3_SMALL` | MobileNetV3-S | 1.25M | 3MB | 99.30% | 93.77% | 85.29% | 92.79% | Mobile optimized | -| `MNET_V3_LARGE` | MobileNetV3-L | 3.52M | 10MB | 99.53% | 94.56% | 86.79% | 95.13% | Balanced mobile | - -**Dataset**: Trained on MS1M-V2 (5.8M images, 85K identities) -**Accuracy**: Evaluated on LFW, CALFW, CPLFW, and AgeDB-30 benchmarks -**Note**: These models are lightweight alternatives to ArcFace for resource-constrained environments - -#### Usage - -```python -from uniface import MobileFace -from uniface.constants import MobileFaceWeights - -# Lightweight -recognizer = MobileFace(model_name=MobileFaceWeights.MNET_V2) -``` - ---- - -### SphereFace - -Face recognition using angular softmax loss. - -| Model Name | Backbone | Params | Size | LFW | CALFW | CPLFW | AgeDB-30 | Use Case | -| ------------ | -------- | ------ | ---- | ------ | ------ | ------ | -------- | ------------------- | -| `SPHERE20` | Sphere20 | 24.5M | 50MB | 99.67% | 95.61% | 88.75% | 96.58% | Research/Comparison | -| `SPHERE36` | Sphere36 | 34.6M | 92MB | 99.72% | 95.64% | 89.92% | 96.83% | Research/Comparison | - -**Dataset**: Trained on MS1M-V2 (5.8M images, 85K identities) -**Accuracy**: Evaluated on LFW, CALFW, CPLFW, and AgeDB-30 benchmarks -**Note**: SphereFace uses angular softmax loss, an earlier approach before ArcFace. These models provide good accuracy with moderate resource requirements. - -#### Usage - -```python -from uniface import SphereFace -from uniface.constants import SphereFaceWeights - -recognizer = SphereFace(model_name=SphereFaceWeights.SPHERE20) -``` - ---- - -## Facial Landmark Models - -### 106-Point Landmark Detection - -High-precision facial landmark localization. - -| Model Name | Points | Params | Size | Use Case | -| ---------- | ------ | ------ | ---- | ------------------------ | -| `2D106` | 106 | 3.7M | 14MB | Face alignment, analysis | - -**Note**: Provides 106 facial keypoints for detailed face analysis and alignment - -#### Usage - -```python -from uniface import Landmark106 - -landmarker = Landmark106() -landmarks = landmarker.get_landmarks(image, bbox) -# Returns: (106, 2) array of (x, y) coordinates -``` - -**Landmark Groups:** - -- Face contour: 0-32 (33 points) -- Eyebrows: 33-50 (18 points) -- Nose: 51-62 (12 points) -- Eyes: 63-86 (24 points) -- Mouth: 87-105 (19 points) - ---- - -## Attribute Analysis Models - -### Age & Gender Detection - -| Model Name | Attributes | Params | Size | Use Case | -| ----------- | ----------- | ------ | ---- | --------------- | -| `DEFAULT` | Age, Gender | 2.1M | 8MB | General purpose | - -**Dataset**: Trained on CelebA -**Note**: Accuracy varies by demographic and image quality. Test on your specific use case. - -#### Usage - -```python -from uniface import AgeGender - -predictor = AgeGender() -result = predictor.predict(image, bbox) -# Returns: AttributeResult with gender, age, sex property -# result.gender: 0 for Female, 1 for Male -# result.sex: "Female" or "Male" -# result.age: age in years -``` - ---- - -### FairFace Attributes - -| Model Name | Attributes | Params | Size | Use Case | -| ----------- | --------------------- | ------ | ----- | --------------------------- | -| `DEFAULT` | Race, Gender, Age Group | - | 44MB | Balanced demographic prediction | - -**Dataset**: Trained on FairFace dataset with balanced demographics -**Note**: FairFace provides more equitable predictions across different racial and gender groups - -**Race Categories (7):** White, Black, Latino Hispanic, East Asian, Southeast Asian, Indian, Middle Eastern - -**Age Groups (9):** 0-2, 3-9, 10-19, 20-29, 30-39, 40-49, 50-59, 60-69, 70+ - -#### Usage - -```python -from uniface import FairFace - -predictor = FairFace() -result = predictor.predict(image, bbox) -# Returns: AttributeResult with gender, age_group, race, sex property -# result.gender: 0 for Female, 1 for Male -# result.sex: "Female" or "Male" -# result.age_group: "20-29", "30-39", etc. -# result.race: "East Asian", "White", etc. -``` - ---- - -### Emotion Detection - -| Model Name | Classes | Params | Size | Use Case | -| ------------- | ------- | ------ | ---- | --------------- | -| `AFFECNET7` | 7 | 0.5M | 2MB | 7-class emotion | -| `AFFECNET8` | 8 | 0.5M | 2MB | 8-class emotion | - -**Classes (7)**: Neutral, Happy, Sad, Surprise, Fear, Disgust, Anger -**Classes (8)**: Above + Contempt - -**Dataset**: Trained on AffectNet -**Note**: Emotion detection accuracy depends heavily on facial expression clarity and cultural context - -#### Usage - -```python -from uniface import Emotion -from uniface.constants import DDAMFNWeights - -predictor = Emotion(model_name=DDAMFNWeights.AFFECNET7) -result = predictor.predict(image, landmarks) -# result.emotion: predicted emotion label -# result.confidence: confidence score -``` - ---- - -## Gaze Estimation Models - -### MobileGaze Family - -Real-time gaze direction prediction models trained on Gaze360 dataset. Returns pitch (vertical) and yaw (horizontal) angles in radians. - -| Model Name | Params | Size | MAE* | Use Case | -| -------------- | ------ | ------- | ----- | ----------------------------- | -| `RESNET18` | 11.7M | 43 MB | 12.84 | Balanced accuracy/speed | -| `RESNET34` ⭐ | 24.8M | 81.6 MB | 11.33 | **Recommended default** | -| `RESNET50` | 25.6M | 91.3 MB | 11.34 | High accuracy | -| `MOBILENET_V2` | 3.5M | 9.59 MB | 13.07 | Mobile/Edge devices | -| `MOBILEONE_S0` | 2.1M | 4.8 MB | 12.58 | Lightweight/Real-time | - -*MAE (Mean Absolute Error) in degrees on Gaze360 test set - lower is better - -**Dataset**: Trained on Gaze360 (indoor/outdoor scenes with diverse head poses) -**Training**: 200 epochs with classification-based approach (binned angles) - -#### Usage - -```python -from uniface import MobileGaze -from uniface.constants import GazeWeights -import numpy as np - -# Default (recommended) -gaze_estimator = MobileGaze() # Uses RESNET34 - -# Lightweight model -gaze_estimator = MobileGaze(model_name=GazeWeights.MOBILEONE_S0) - -# Estimate gaze from face crop -result = gaze_estimator.estimate(face_crop) -print(f"Pitch: {np.degrees(result.pitch):.1f}°, Yaw: {np.degrees(result.yaw):.1f}°") -``` - -**Note**: Requires face crop as input. Use face detection first to obtain bounding boxes. - ---- - -## Face Parsing Models - -### BiSeNet Family - -BiSeNet (Bilateral Segmentation Network) models for semantic face parsing. Segments face images into 19 facial component classes. - -| Model Name | Params | Size | Classes | Use Case | -| -------------- | ------ | ------- | ------- | ----------------------------- | -| `RESNET18` ⭐ | 13.3M | 50.7 MB | 19 | **Recommended default** | -| `RESNET34` | 24.1M | 89.2 MB | 19 | Higher accuracy | - -**19 Facial Component Classes:** -1. Background -2. Skin -3. Left Eyebrow -4. Right Eyebrow -5. Left Eye -6. Right Eye -7. Eye Glasses -8. Left Ear -9. Right Ear -10. Ear Ring -11. Nose -12. Mouth -13. Upper Lip -14. Lower Lip -15. Neck -16. Neck Lace -17. Cloth -18. Hair -19. Hat - -**Dataset**: Trained on CelebAMask-HQ -**Architecture**: BiSeNet with ResNet backbone -**Input Size**: 512×512 (automatically resized) - -#### Usage - -```python -from uniface.parsing import BiSeNet -from uniface.constants import ParsingWeights -from uniface.visualization import vis_parsing_maps -import cv2 - -# Default (recommended) -parser = BiSeNet() # Uses RESNET18 - -# Higher accuracy model -parser = BiSeNet(model_name=ParsingWeights.RESNET34) - -# Parse face image (already cropped) -mask = parser.parse(face_image) - -# Visualize with overlay -face_rgb = cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB) -vis_result = vis_parsing_maps(face_rgb, mask, save_image=False) - -# mask shape: (H, W) with values 0-18 representing classes -print(f"Detected {len(np.unique(mask))} facial components") -``` - -**Applications:** -- Face makeup and beauty applications -- Virtual try-on systems -- Face editing and manipulation -- Facial feature extraction -- Portrait segmentation - -**Note**: Input should be a cropped face image. For full pipeline, use face detection first to obtain face crops. - ---- - -## Anti-Spoofing Models - -### MiniFASNet Family - -Lightweight face anti-spoofing models for liveness detection. Detect if a face is real (live) or fake (photo, video replay, mask). - -| Model Name | Size | Scale | Use Case | -| ---------- | ------ | ----- | ----------------------------- | -| `V1SE` | 1.2 MB | 4.0 | Squeeze-and-excitation variant | -| `V2` ⭐ | 1.2 MB | 2.7 | **Recommended default** | - -**Dataset**: Trained on face anti-spoofing datasets -**Output**: Returns `SpoofingResult(is_real, confidence)` where is_real: True=Real, False=Fake - -#### Usage - -```python -from uniface import RetinaFace -from uniface.spoofing import MiniFASNet -from uniface.constants import MiniFASNetWeights - -# Default (V2, recommended) -detector = RetinaFace() -spoofer = MiniFASNet() - -# V1SE variant -spoofer = MiniFASNet(model_name=MiniFASNetWeights.V1SE) - -# Detect and check liveness -faces = detector.detect(image) -for face in faces: - result = spoofer.predict(image, face.bbox) - # result.is_real: True for real, False for fake - label = 'Real' if result.is_real else 'Fake' - print(f"{label}: {result.confidence:.1%}") -``` - -**Note**: Requires face bounding box from a detector. Use with RetinaFace, SCRFD, or YOLOv5Face. - ---- - -## Model Updates - -Models are automatically downloaded and cached on first use. Cache location: `~/.uniface/models/` - -### Manual Model Management - -```python -from uniface.model_store import verify_model_weights -from uniface.constants import RetinaFaceWeights - -# Download specific model -model_path = verify_model_weights( - RetinaFaceWeights.MNET_V2, - root='./custom_cache' -) - -# Models are verified with SHA-256 checksums -``` - -### Download All Models - -```bash -# Using the provided script -python tools/download_model.py - -# Download specific model -python tools/download_model.py --model MNET_V2 -``` - ---- - -## References - -### Model Training & Architectures - -- **RetinaFace Training**: [yakhyo/retinaface-pytorch](https://github.com/yakhyo/retinaface-pytorch) - PyTorch implementation and training code -- **YOLOv5-Face Original**: [deepcam-cn/yolov5-face](https://github.com/deepcam-cn/yolov5-face) - Original PyTorch implementation -- **YOLOv5-Face ONNX**: [yakhyo/yolov5-face-onnx-inference](https://github.com/yakhyo/yolov5-face-onnx-inference) - ONNX inference implementation -- **Face Recognition Training**: [yakhyo/face-recognition](https://github.com/yakhyo/face-recognition) - ArcFace, MobileFace, SphereFace training code -- **Gaze Estimation Training**: [yakhyo/gaze-estimation](https://github.com/yakhyo/gaze-estimation) - MobileGaze training code and pretrained weights -- **Face Parsing Training**: [yakhyo/face-parsing](https://github.com/yakhyo/face-parsing) - BiSeNet training code and pretrained weights -- **Face Anti-Spoofing**: [yakhyo/face-anti-spoofing](https://github.com/yakhyo/face-anti-spoofing) - MiniFASNet ONNX inference (weights from [minivision-ai/Silent-Face-Anti-Spoofing](https://github.com/minivision-ai/Silent-Face-Anti-Spoofing)) -- **FairFace**: [yakhyo/fairface-onnx](https://github.com/yakhyo/fairface-onnx) - FairFace ONNX inference for race, gender, age prediction -- **InsightFace**: [deepinsight/insightface](https://github.com/deepinsight/insightface) - Model architectures and pretrained weights - -### Papers - -- **RetinaFace**: [Single-Shot Multi-Level Face Localisation in the Wild](https://arxiv.org/abs/1905.00641) -- **SCRFD**: [Sample and Computation Redistribution for Efficient Face Detection](https://arxiv.org/abs/2105.04714) -- **YOLOv5-Face**: [YOLO5Face: Why Reinventing a Face Detector](https://arxiv.org/abs/2105.12931) -- **ArcFace**: [Additive Angular Margin Loss for Deep Face Recognition](https://arxiv.org/abs/1801.07698) -- **SphereFace**: [Deep Hypersphere Embedding for Face Recognition](https://arxiv.org/abs/1704.08063) -- **BiSeNet**: [Bilateral Segmentation Network for Real-time Semantic Segmentation](https://arxiv.org/abs/1808.00897) diff --git a/QUICKSTART.md b/QUICKSTART.md deleted file mode 100644 index 436725e..0000000 --- a/QUICKSTART.md +++ /dev/null @@ -1,695 +0,0 @@ -# UniFace Quick Start Guide - -Get up and running with UniFace in 5 minutes! This guide covers the most common use cases. - ---- - -## Installation - -```bash -# macOS (Apple Silicon) - automatically includes ARM64 optimizations -pip install uniface - -# Linux/Windows with NVIDIA GPU -pip install uniface[gpu] - -# CPU-only (all platforms) -pip install uniface -``` - ---- - -## 1. Face Detection (30 seconds) - -Detect faces in an image: - -```python -import cv2 -from uniface import RetinaFace - -# Load image -image = cv2.imread("photo.jpg") - -# Initialize detector (models auto-download on first use) -detector = RetinaFace() - -# Detect faces -faces = detector.detect(image) - -# Print results -for i, face in enumerate(faces): - print(f"Face {i+1}:") - print(f" Confidence: {face.confidence:.2f}") - print(f" BBox: {face.bbox}") - print(f" Landmarks: {len(face.landmarks)} points") -``` - -**Output:** - -``` -Face 1: - Confidence: 0.99 - BBox: [120.5, 85.3, 245.8, 210.6] - Landmarks: 5 points -``` - ---- - -## 2. Visualize Detections (1 minute) - -Draw bounding boxes and landmarks: - -```python -import cv2 -from uniface import RetinaFace -from uniface.visualization import draw_detections - -# Detect faces -detector = RetinaFace() -image = cv2.imread("photo.jpg") -faces = detector.detect(image) - -# Extract visualization data -bboxes = [f.bbox for f in faces] -scores = [f.confidence for f in faces] -landmarks = [f.landmarks for f in faces] - -# Draw on image -draw_detections( - image=image, - bboxes=bboxes, - scores=scores, - landmarks=landmarks, - vis_threshold=0.6, -) - -# Save result -cv2.imwrite("output.jpg", image) -print("Saved output.jpg") -``` - ---- - -## 3. Face Recognition (2 minutes) - -Compare two faces: - -```python -import cv2 -import numpy as np -from uniface import RetinaFace, ArcFace - -# Initialize models -detector = RetinaFace() -recognizer = ArcFace() - -# Load two images -image1 = cv2.imread("person1.jpg") -image2 = cv2.imread("person2.jpg") - -# Detect faces -faces1 = detector.detect(image1) -faces2 = detector.detect(image2) - -if faces1 and faces2: - # Extract embeddings - emb1 = recognizer.get_normalized_embedding(image1, faces1[0].landmarks) - emb2 = recognizer.get_normalized_embedding(image2, faces2[0].landmarks) - - # Compute similarity (cosine similarity) - similarity = np.dot(emb1, emb2.T)[0][0] - - # Interpret result - if similarity > 0.6: - print(f"Same person (similarity: {similarity:.3f})") - else: - print(f"Different people (similarity: {similarity:.3f})") -else: - print("No faces detected") -``` - -**Similarity thresholds:** - -- `> 0.6`: Same person (high confidence) -- `0.4 - 0.6`: Uncertain (manual review) -- `< 0.4`: Different people - ---- - -## 4. Webcam Demo (2 minutes) - -Real-time face detection: - -```python -import cv2 -from uniface import RetinaFace -from uniface.visualization import draw_detections - -detector = RetinaFace() -cap = cv2.VideoCapture(0) - -print("Press 'q' to quit") - -while True: - ret, frame = cap.read() - if not ret: - break - - # Detect faces - faces = detector.detect(frame) - - # Draw results - bboxes = [f.bbox for f in faces] - scores = [f.confidence for f in faces] - landmarks = [f.landmarks for f in faces] - draw_detections( - image=frame, - bboxes=bboxes, - scores=scores, - landmarks=landmarks, - ) - - # Show frame - cv2.imshow("UniFace - Press 'q' to quit", frame) - - if cv2.waitKey(1) & 0xFF == ord('q'): - break - -cap.release() -cv2.destroyAllWindows() -``` - ---- - -## 5. Age & Gender Detection (2 minutes) - -Detect age and gender: - -```python -import cv2 -from uniface import RetinaFace, AgeGender - -# Initialize models -detector = RetinaFace() -age_gender = AgeGender() - -# Load image -image = cv2.imread("photo.jpg") -faces = detector.detect(image) - -# Predict attributes -for i, face in enumerate(faces): - result = age_gender.predict(image, face.bbox) - print(f"Face {i+1}: {result.sex}, {result.age} years old") - # result.gender: 0=Female, 1=Male - # result.sex: "Female" or "Male" - # result.age: age in years -``` - -**Output:** - -``` -Face 1: Male, 32 years old -Face 2: Female, 28 years old -``` - ---- - -## 5b. FairFace Attributes (2 minutes) - -Detect race, gender, and age group with balanced demographics: - -```python -import cv2 -from uniface import RetinaFace, FairFace - -# Initialize models -detector = RetinaFace() -fairface = FairFace() - -# Load image -image = cv2.imread("photo.jpg") -faces = detector.detect(image) - -# Predict attributes -for i, face in enumerate(faces): - result = fairface.predict(image, face.bbox) - print(f"Face {i+1}: {result.sex}, {result.age_group}, {result.race}") - # result.gender: 0=Female, 1=Male - # result.sex: "Female" or "Male" - # result.age_group: "20-29", "30-39", etc. - # result.race: "East Asian", "White", etc. -``` - -**Output:** - -``` -Face 1: Male, 30-39, East Asian -Face 2: Female, 20-29, White -``` - -**Race Categories:** White, Black, Latino Hispanic, East Asian, Southeast Asian, Indian, Middle Eastern - -**Age Groups:** 0-2, 3-9, 10-19, 20-29, 30-39, 40-49, 50-59, 60-69, 70+ - ---- - -## 6. Facial Landmarks (2 minutes) - -Detect 106 facial landmarks: - -```python -import cv2 -from uniface import RetinaFace, Landmark106 - -# Initialize models -detector = RetinaFace() -landmarker = Landmark106() - -# Detect face and landmarks -image = cv2.imread("photo.jpg") -faces = detector.detect(image) - -if faces: - landmarks = landmarker.get_landmarks(image, faces[0].bbox) - print(f"Detected {len(landmarks)} landmarks") - - # Draw landmarks - for x, y in landmarks.astype(int): - cv2.circle(image, (x, y), 2, (0, 255, 0), -1) - - cv2.imwrite("landmarks.jpg", image) -``` - ---- - -## 7. Gaze Estimation (2 minutes) - -Estimate where a person is looking: - -```python -import cv2 -import numpy as np -from uniface import RetinaFace, MobileGaze -from uniface.visualization import draw_gaze - -# Initialize models -detector = RetinaFace() -gaze_estimator = MobileGaze() - -# Load image -image = cv2.imread("photo.jpg") -faces = detector.detect(image) - -# Estimate gaze for each face -for i, face in enumerate(faces): - x1, y1, x2, y2 = map(int, face.bbox[:4]) - face_crop = image[y1:y2, x1:x2] - - if face_crop.size > 0: - result = gaze_estimator.estimate(face_crop) - print(f"Face {i+1}: pitch={np.degrees(result.pitch):.1f}°, yaw={np.degrees(result.yaw):.1f}°") - - # Draw gaze direction - draw_gaze(image, face.bbox, result.pitch, result.yaw) - -cv2.imwrite("gaze_output.jpg", image) -``` - -**Output:** - -``` -Face 1: pitch=5.2°, yaw=-12.3° -Face 2: pitch=-8.1°, yaw=15.7° -``` - ---- - -## 8. Face Parsing (2 minutes) - -Segment face into semantic components (skin, eyes, nose, mouth, hair, etc.): - -```python -import cv2 -import numpy as np -from uniface.parsing import BiSeNet -from uniface.visualization import vis_parsing_maps - -# Initialize parser -parser = BiSeNet() # Uses ResNet18 by default - -# Load face image (already cropped) -face_image = cv2.imread("face.jpg") - -# Parse face into 19 components -mask = parser.parse(face_image) - -# Visualize with overlay -face_rgb = cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB) -vis_result = vis_parsing_maps(face_rgb, mask, save_image=False) - -# Convert back to BGR for saving -vis_bgr = cv2.cvtColor(vis_result, cv2.COLOR_RGB2BGR) -cv2.imwrite("parsed_face.jpg", vis_bgr) - -print(f"Detected {len(np.unique(mask))} facial components") -``` - -**Output:** - -``` -Detected 12 facial components -``` - -**19 Facial Component Classes:** -- Background, Skin, Eyebrows (L/R), Eyes (L/R), Eye Glasses -- Ears (L/R), Ear Ring, Nose, Mouth, Lips (Upper/Lower) -- Neck, Neck Lace, Cloth, Hair, Hat - ---- - -## 9. Face Anonymization (2 minutes) - -Automatically blur faces for privacy protection: - -```python -from uniface.privacy import anonymize_faces -import cv2 - -# One-liner: automatic detection and blurring -image = cv2.imread("group_photo.jpg") -anonymized = anonymize_faces(image, method='pixelate') -cv2.imwrite("anonymized.jpg", anonymized) -print("Faces anonymized successfully!") -``` - -**Manual control with custom parameters:** - -```python -from uniface import RetinaFace -from uniface.privacy import BlurFace - -# Initialize detector and blurrer -detector = RetinaFace() -blurrer = BlurFace(method='gaussian', blur_strength=5.0) - -# Detect and anonymize -faces = detector.detect(image) -anonymized = blurrer.anonymize(image, faces) -cv2.imwrite("output.jpg", anonymized) -``` - -**Available blur methods:** - -```python -# Pixelation (news media standard) -blurrer = BlurFace(method='pixelate', pixel_blocks=8) - -# Gaussian blur (smooth, natural) -blurrer = BlurFace(method='gaussian', blur_strength=4.0) - -# Black boxes (maximum privacy) -blurrer = BlurFace(method='blackout', color=(0, 0, 0)) - -# Elliptical blur (natural face shape) -blurrer = BlurFace(method='elliptical', blur_strength=3.0, margin=30) - -# Median blur (edge-preserving) -blurrer = BlurFace(method='median', blur_strength=3.0) -``` - -**Webcam anonymization:** - -```python -import cv2 -from uniface import RetinaFace -from uniface.privacy import BlurFace - -detector = RetinaFace() -blurrer = BlurFace(method='pixelate') -cap = cv2.VideoCapture(0) - -while True: - ret, frame = cap.read() - if not ret: - break - - faces = detector.detect(frame) - frame = blurrer.anonymize(frame, faces, inplace=True) - - cv2.imshow('Anonymized', frame) - if cv2.waitKey(1) & 0xFF == ord('q'): - break - -cap.release() -cv2.destroyAllWindows() -``` - -**Command-line tool:** - -```bash -# Anonymize image with pixelation -python tools/face_anonymize.py --source photo.jpg - -# Real-time webcam anonymization -python tools/face_anonymize.py --source 0 --method gaussian - -# Custom blur strength -python tools/face_anonymize.py --source photo.jpg --method gaussian --blur-strength 5.0 -``` - ---- - -## 10. Face Anti-Spoofing (2 minutes) - -Detect if a face is real or fake (photo, video replay, mask): - -```python -from uniface import RetinaFace -from uniface.spoofing import MiniFASNet - -detector = RetinaFace() -spoofer = MiniFASNet() # Uses V2 by default - -image = cv2.imread("photo.jpg") -faces = detector.detect(image) - -for i, face in enumerate(faces): - result = spoofer.predict(image, face.bbox) - # result.is_real: True for real, False for fake - label = 'Real' if result.is_real else 'Fake' - print(f"Face {i+1}: {label} ({result.confidence:.1%})") -``` - -**Output:** - -``` -Face 1: Real (98.5%) -``` - -**Command-line tool:** - -```bash -# Image -python tools/spoofing.py --source photo.jpg - -# Webcam -python tools/spoofing.py --source 0 -``` - ---- - -## 11. Batch Processing (3 minutes) - -Process multiple images: - -```python -import cv2 -from pathlib import Path -from uniface import RetinaFace - -detector = RetinaFace() - -# Process all images in a folder -image_dir = Path("images/") -output_dir = Path("output/") -output_dir.mkdir(exist_ok=True) - -for image_path in image_dir.glob("*.jpg"): - print(f"Processing {image_path.name}...") - - image = cv2.imread(str(image_path)) - faces = detector.detect(image) - - print(f" Found {len(faces)} face(s)") - - # Save results - output_path = output_dir / image_path.name - # ... draw and save ... - -print("Done!") -``` - ---- - -## 12. Model Selection - -Choose the right model for your use case: - -### Detection Models - -```python -from uniface.detection import RetinaFace, SCRFD, YOLOv5Face -from uniface.constants import RetinaFaceWeights, SCRFDWeights, YOLOv5FaceWeights - -# Fast detection (mobile/edge devices) -detector = RetinaFace( - model_name=RetinaFaceWeights.MNET_025, - confidence_threshold=0.7 -) - -# Balanced (recommended) -detector = RetinaFace( - model_name=RetinaFaceWeights.MNET_V2 -) - -# Real-time with high accuracy -detector = YOLOv5Face( - model_name=YOLOv5FaceWeights.YOLOV5S, - confidence_threshold=0.6, - nms_thresh=0.5 -) - -# High accuracy (server/GPU) -detector = SCRFD( - model_name=SCRFDWeights.SCRFD_10G_KPS, - confidence_threshold=0.5 -) -``` - -### Recognition Models - -```python -from uniface import ArcFace, MobileFace, SphereFace -from uniface.constants import MobileFaceWeights, SphereFaceWeights - -# ArcFace (recommended for most use cases) -recognizer = ArcFace() # Best accuracy - -# MobileFace (lightweight for mobile/edge) -recognizer = MobileFace(model_name=MobileFaceWeights.MNET_V2) # Fast, small size - -# SphereFace (angular margin approach) -recognizer = SphereFace(model_name=SphereFaceWeights.SPHERE20) # Alternative method -``` - -### Gaze Estimation Models - -```python -from uniface import MobileGaze -from uniface.constants import GazeWeights - -# Default (recommended) -gaze_estimator = MobileGaze() # Uses RESNET34 - -# Lightweight (mobile/edge devices) -gaze_estimator = MobileGaze(model_name=GazeWeights.MOBILEONE_S0) - -# High accuracy -gaze_estimator = MobileGaze(model_name=GazeWeights.RESNET50) -``` - -### Face Parsing Models - -```python -from uniface.parsing import BiSeNet -from uniface.constants import ParsingWeights - -# Default (recommended, 50.7 MB) -parser = BiSeNet() # Uses RESNET18 - -# Higher accuracy (89.2 MB) -parser = BiSeNet(model_name=ParsingWeights.RESNET34) -``` - ---- - -## Common Issues - -### 1. Models Not Downloading - -```python -# Manually download a model -from uniface.model_store import verify_model_weights -from uniface.constants import RetinaFaceWeights - -model_path = verify_model_weights(RetinaFaceWeights.MNET_V2) -print(f"Model downloaded to: {model_path}") -``` - -### 2. Check Hardware Acceleration - -```python -import onnxruntime as ort -print("Available providers:", ort.get_available_providers()) - -# macOS M-series should show: ['CoreMLExecutionProvider', ...] -# NVIDIA GPU should show: ['CUDAExecutionProvider', ...] -``` - -### 3. Slow Performance on Mac - -The standard installation includes ARM64 optimizations for Apple Silicon. If performance is slow, verify you're using the ARM64 build of Python: - -```bash -python -c "import platform; print(platform.machine())" -# Should show: arm64 (not x86_64) -``` - -### 4. Import Errors - -```python -# Correct imports -from uniface.detection import RetinaFace -from uniface.recognition import ArcFace -from uniface.landmark import Landmark106 - -# Wrong imports -from uniface import retinaface # Module, not class -``` - ---- - -## Next Steps - -### Jupyter Notebook Examples - -Explore interactive examples for common tasks: - -| Example | Description | Notebook | -|---------|-------------|----------| -| **Face Detection** | Detect faces and facial landmarks | [01_face_detection.ipynb](examples/01_face_detection.ipynb) | -| **Face Alignment** | Align and crop faces for recognition | [02_face_alignment.ipynb](examples/02_face_alignment.ipynb) | -| **Face Verification** | Compare two faces to verify identity | [03_face_verification.ipynb](examples/03_face_verification.ipynb) | -| **Face Search** | Find a person in a group photo | [04_face_search.ipynb](examples/04_face_search.ipynb) | -| **Face Analyzer** | All-in-one detection, recognition & attributes | [05_face_analyzer.ipynb](examples/05_face_analyzer.ipynb) | -| **Face Parsing** | Segment face into semantic components | [06_face_parsing.ipynb](examples/06_face_parsing.ipynb) | -| **Face Anonymization** | Blur or pixelate faces for privacy protection | [07_face_anonymization.ipynb](examples/07_face_anonymization.ipynb) | -| **Gaze Estimation** | Estimate gaze direction | [08_gaze_estimation.ipynb](examples/08_gaze_estimation.ipynb) | - -### Additional Resources - -- **Model Benchmarks**: See [MODELS.md](MODELS.md) for performance comparisons -- **Full Documentation**: Read [README.md](README.md) for complete API reference - ---- - -## References - -- **RetinaFace Training**: [yakhyo/retinaface-pytorch](https://github.com/yakhyo/retinaface-pytorch) -- **YOLOv5-Face ONNX**: [yakhyo/yolov5-face-onnx-inference](https://github.com/yakhyo/yolov5-face-onnx-inference) -- **Face Recognition Training**: [yakhyo/face-recognition](https://github.com/yakhyo/face-recognition) -- **Gaze Estimation Training**: [yakhyo/gaze-estimation](https://github.com/yakhyo/gaze-estimation) -- **Face Parsing Training**: [yakhyo/face-parsing](https://github.com/yakhyo/face-parsing) -- **FairFace**: [yakhyo/fairface-onnx](https://github.com/yakhyo/fairface-onnx) - Race, gender, age prediction -- **InsightFace**: [deepinsight/insightface](https://github.com/deepinsight/insightface) diff --git a/README.md b/README.md index cbd867c..51cf45f 100644 --- a/README.md +++ b/README.md @@ -2,683 +2,124 @@
-[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) -[![Python](https://img.shields.io/badge/Python-3.11%2B-blue)](https://www.python.org/) [![PyPI](https://img.shields.io/pypi/v/uniface.svg)](https://pypi.org/project/uniface/) +[![Python](https://img.shields.io/badge/Python-3.11%2B-blue)](https://www.python.org/) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![CI](https://github.com/yakhyo/uniface/actions/workflows/ci.yml/badge.svg)](https://github.com/yakhyo/uniface/actions) [![Downloads](https://static.pepy.tech/badge/uniface)](https://pepy.tech/project/uniface) -[![DeepWiki](https://img.shields.io/badge/DeepWiki-AI_Docs-blue.svg?logo=bookstack)](https://deepwiki.com/yakhyo/uniface) +[![Docs](https://img.shields.io/badge/Docs-UniFace-blue.svg)](https://yakhyo.github.io/uniface/)
- +
**UniFace** is a lightweight, production-ready face analysis library built on ONNX Runtime. It provides high-performance face detection, recognition, landmark detection, face parsing, gaze estimation, and attribute analysis with hardware acceleration support across platforms. +> 💬 **Have questions?** [Chat with this codebase on DeepWiki](https://deepwiki.com/yakhyo/uniface) - AI-powered docs that let you ask anything about UniFace. + --- ## Features -- **High-Speed Face Detection**: ONNX-optimized RetinaFace, SCRFD, and YOLOv5-Face models -- **Facial Landmark Detection**: Accurate 106-point landmark localization -- **Face Recognition**: ArcFace, MobileFace, and SphereFace embeddings -- **Face Parsing**: BiSeNet-based semantic segmentation with 19 facial component classes -- **Gaze Estimation**: Real-time gaze direction prediction with MobileGaze -- **Attribute Analysis**: Age, gender, race (FairFace), and emotion detection -- **Anti-Spoofing**: Face liveness detection with MiniFASNet models -- **Face Anonymization**: Privacy-preserving face blurring with 5 methods (pixelate, gaussian, blackout, elliptical, median) -- **Face Alignment**: Precise alignment for downstream tasks -- **Hardware Acceleration**: ARM64 optimizations (Apple Silicon), CUDA (NVIDIA), CPU fallback -- **Simple API**: Intuitive factory functions and clean interfaces -- **Production-Ready**: Type hints, comprehensive logging, PEP8 compliant +- **Face Detection** — RetinaFace, SCRFD, and YOLOv5-Face with 5-point landmarks +- **Face Recognition** — ArcFace, MobileFace, and SphereFace embeddings +- **Facial Landmarks** — 106-point landmark localization +- **Face Parsing** — BiSeNet semantic segmentation (19 classes) +- **Gaze Estimation** — Real-time gaze direction with MobileGaze +- **Attribute Analysis** — Age, gender, race (FairFace), and emotion +- **Anti-Spoofing** — Face liveness detection with MiniFASNet +- **Face Anonymization** — 5 blur methods for privacy protection +- **Hardware Acceleration** — ARM64 (Apple Silicon), CUDA (NVIDIA), CPU --- ## Installation -### Quick Install (All Platforms) - ```bash +# Standard installation pip install uniface -``` -### Platform-Specific Installation - -#### macOS (Apple Silicon - M1/M2/M3/M4) - -For Apple Silicon Macs, the standard installation automatically includes optimized ARM64 support: - -```bash -pip install uniface -``` - -The base `onnxruntime` package (included with uniface) has native Apple Silicon support with ARM64 optimizations built-in since version 1.13+. - -#### Linux/Windows with NVIDIA GPU - -For CUDA acceleration on NVIDIA GPUs: - -```bash +# GPU support (CUDA) pip install uniface[gpu] -``` -**Requirements:** - -- CUDA 11.x or 12.x -- cuDNN 8.x -- See [ONNX Runtime GPU requirements](https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html) - -#### CPU-Only (All Platforms) - -```bash -pip install uniface -``` - -### Install from Source - -```bash +# From source git clone https://github.com/yakhyo/uniface.git -cd uniface -pip install -e . +cd uniface && pip install -e . ``` --- -## Quick Start - -### Face Detection +## Quick Example ```python import cv2 from uniface import RetinaFace -# Initialize detector +# Initialize detector (models auto-download on first use) detector = RetinaFace() -# Load image -image = cv2.imread("image.jpg") - # Detect faces -faces = detector.detect(image) - -# Process results -for face in faces: - bbox = face.bbox # np.ndarray [x1, y1, x2, y2] - confidence = face.confidence - landmarks = face.landmarks # np.ndarray (5, 2) landmarks - print(f"Face detected with confidence: {confidence:.2f}") -``` - -### Face Recognition - -```python -from uniface import ArcFace, RetinaFace -from uniface import compute_similarity - -# Initialize models -detector = RetinaFace() -recognizer = ArcFace() - -# Detect and extract embeddings -faces1 = detector.detect(image1) -faces2 = detector.detect(image2) - -embedding1 = recognizer.get_normalized_embedding(image1, faces1[0].landmarks) -embedding2 = recognizer.get_normalized_embedding(image2, faces2[0].landmarks) - -# Compare faces -similarity = compute_similarity(embedding1, embedding2) -print(f"Similarity: {similarity:.4f}") -``` - -### Facial Landmarks - -```python -from uniface import RetinaFace, Landmark106 - -detector = RetinaFace() -landmarker = Landmark106() - -faces = detector.detect(image) -landmarks = landmarker.get_landmarks(image, faces[0].bbox) -# Returns 106 (x, y) landmark points -``` - -### Age & Gender Detection - -```python -from uniface import RetinaFace, AgeGender - -detector = RetinaFace() -age_gender = AgeGender() - -faces = detector.detect(image) -result = age_gender.predict(image, faces[0].bbox) -print(f"{result.sex}, {result.age} years old") -# result.gender: 0=Female, 1=Male -# result.sex: "Female" or "Male" -# result.age: age in years -``` - -### FairFace Attributes (Race, Gender, Age Group) - -```python -from uniface import RetinaFace, FairFace - -detector = RetinaFace() -fairface = FairFace() - -faces = detector.detect(image) -result = fairface.predict(image, faces[0].bbox) -print(f"{result.sex}, {result.age_group}, {result.race}") -# result.gender: 0=Female, 1=Male -# result.sex: "Female" or "Male" -# result.age_group: "20-29", "30-39", etc. -# result.race: "East Asian", "White", etc. -``` - -### Gaze Estimation - -```python -from uniface import RetinaFace, MobileGaze -from uniface.visualization import draw_gaze -import numpy as np - -detector = RetinaFace() -gaze_estimator = MobileGaze() - -faces = detector.detect(image) -for face in faces: - x1, y1, x2, y2 = map(int, face.bbox[:4]) - face_crop = image[y1:y2, x1:x2] - - result = gaze_estimator.estimate(face_crop) - print(f"Gaze: pitch={np.degrees(result.pitch):.1f}°, yaw={np.degrees(result.yaw):.1f}°") - - # Visualize - draw_gaze(image, face.bbox, result.pitch, result.yaw) -``` - -### Face Parsing - -```python -from uniface.parsing import BiSeNet -from uniface.visualization import vis_parsing_maps - -# Initialize parser -parser = BiSeNet() # Uses ResNet18 by default - -# Parse face image (already cropped) -mask = parser.parse(face_image) - -# Visualize with overlay -import cv2 -face_rgb = cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB) -vis_result = vis_parsing_maps(face_rgb, mask, save_image=False) - -# mask contains 19 classes: skin, eyes, nose, mouth, hair, etc. -print(f"Unique classes: {len(np.unique(mask))}") -``` - -### Face Anti-Spoofing - -Detect if a face is real or fake (photo, video replay, mask): - -```python -from uniface import RetinaFace -from uniface.spoofing import MiniFASNet - -detector = RetinaFace() -spoofer = MiniFASNet() # Uses V2 by default - -faces = detector.detect(image) -for face in faces: - result = spoofer.predict(image, face.bbox) - # result.is_real: True for real, False for fake - # result.confidence: confidence score - label = 'Real' if result.is_real else 'Fake' - print(f"{label}: {result.confidence:.1%}") -``` - -### Face Anonymization - -Protect privacy by blurring or pixelating faces with 5 different methods: - -```python -from uniface import RetinaFace -from uniface.privacy import BlurFace, anonymize_faces -import cv2 - -# Method 1: One-liner with automatic detection image = cv2.imread("photo.jpg") -anonymized = anonymize_faces(image, method='pixelate') -cv2.imwrite("anonymized.jpg", anonymized) - -# Method 2: Manual control with custom parameters -detector = RetinaFace() -blurrer = BlurFace(method='gaussian', blur_strength=5.0) - faces = detector.detect(image) -anonymized = blurrer.anonymize(image, faces) -# Available blur methods: -methods = { - 'pixelate': BlurFace(method='pixelate', pixel_blocks=10), # Blocky effect (news media standard) - 'gaussian': BlurFace(method='gaussian', blur_strength=3.0), # Smooth, natural blur - 'blackout': BlurFace(method='blackout', color=(0, 0, 0)), # Solid color boxes (maximum privacy) - 'elliptical': BlurFace(method='elliptical', margin=20), # Soft oval blur (natural face shape) - 'median': BlurFace(method='median', blur_strength=3.0) # Edge-preserving blur -} - -# Real-time webcam anonymization -cap = cv2.VideoCapture(0) -detector = RetinaFace() -blurrer = BlurFace(method='pixelate') - -while True: - ret, frame = cap.read() - if not ret: - break - - faces = detector.detect(frame) - frame = blurrer.anonymize(frame, faces, inplace=True) - - cv2.imshow('Anonymized', frame) - if cv2.waitKey(1) & 0xFF == ord('q'): - break - -cap.release() -cv2.destroyAllWindows() +for face in faces: + print(f"Confidence: {face.confidence:.2f}") + print(f"BBox: {face.bbox}") + print(f"Landmarks: {face.landmarks.shape}") ``` - ---- - -## Documentation - -- [**QUICKSTART.md**](QUICKSTART.md) - 5-minute getting started guide -- [**MODELS.md**](MODELS.md) - Model zoo, benchmarks, and selection guide -- [**Examples**](examples/) - Jupyter notebooks with detailed examples - ---- - -## API Overview - -### Factory Functions (Recommended) - -```python -from uniface.detection import RetinaFace, SCRFD -from uniface.recognition import ArcFace -from uniface.landmark import Landmark106 -from uniface.privacy import BlurFace, anonymize_faces - -from uniface.constants import SCRFDWeights - -# Create detector with default settings -detector = RetinaFace() - -# Create with custom config -detector = SCRFD( - model_name=SCRFDWeights.SCRFD_10G_KPS, # SCRFDWeights.SCRFD_500M_KPS - confidence_threshold=0.4, - input_size=(640, 640) -) -# Or with defaults settings: detector = SCRFD() - -# Recognition and landmarks -recognizer = ArcFace() -landmarker = Landmark106() -``` - -### Direct Model Instantiation - -```python -from uniface import RetinaFace, SCRFD, YOLOv5Face, ArcFace, MobileFace, SphereFace -from uniface.constants import RetinaFaceWeights, YOLOv5FaceWeights - -# Detection -detector = RetinaFace( - model_name=RetinaFaceWeights.MNET_V2, - confidence_threshold=0.5, - nms_threshold=0.4 -) -# Or detector = RetinaFace() - -# YOLOv5-Face detection -detector = YOLOv5Face( - model_name=YOLOv5FaceWeights.YOLOV5S, - confidence_threshold=0.6, - nms_threshold=0.5 -) -# Or detector = YOLOv5Face - -# Recognition -recognizer = ArcFace() # Uses default weights -recognizer = MobileFace() # Lightweight alternative -recognizer = SphereFace() # Angular softmax alternative -``` - -### High-Level Detection API - -```python -from uniface import detect_faces - -# One-line face detection -faces = detect_faces(image, method='retinaface', confidence_threshold=0.8) # methods: retinaface, scrfd, yolov5face -``` - -### Key Parameters (quick reference) - -**Detection** - -| Class | Key params (defaults) | Notes | -| -------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | -| `RetinaFace` | `model_name=RetinaFaceWeights.MNET_V2`, `confidence_threshold=0.5`, `nms_threshold=0.4`, `input_size=(640, 640)`, `dynamic_size=False` | Supports 5-point landmarks | -| `SCRFD` | `model_name=SCRFDWeights.SCRFD_10G_KPS`, `confidence_threshold=0.5`, `nms_threshold=0.4`, `input_size=(640, 640)` | Supports 5-point landmarks | -| `YOLOv5Face` | `model_name=YOLOv5FaceWeights.YOLOV5S`, `confidence_threshold=0.6`, `nms_threshold=0.5`, `input_size=640` (fixed) | Supports 5-point landmarks; models: YOLOV5N/S/M; `input_size` must be 640 | - -**Recognition** - -| Class | Key params (defaults) | Notes | -| -------------- | ----------------------------------------- | ------------------------------------- | -| `ArcFace` | `model_name=ArcFaceWeights.MNET` | Returns 512-dim normalized embeddings | -| `MobileFace` | `model_name=MobileFaceWeights.MNET_V2` | Lightweight embeddings | -| `SphereFace` | `model_name=SphereFaceWeights.SPHERE20` | Angular softmax variant | - -**Landmark & Attributes** - -| Class | Key params (defaults) | Notes | -| --------------- | --------------------------------------------------------------------- | --------------------------------------- | -| `Landmark106` | No required params | 106-point landmarks | -| `AgeGender` | `model_name=AgeGenderWeights.DEFAULT`; `input_size` auto-detected | Returns `AttributeResult` with gender, age | -| `FairFace` | `model_name=FairFaceWeights.DEFAULT`, `input_size=(224, 224)` | Returns `AttributeResult` with gender, age_group, race | -| `Emotion` | `model_weights=DDAMFNWeights.AFFECNET7`, `input_size=(112, 112)` | Requires 5-point landmarks; TorchScript | - -**Gaze Estimation** - -| Class | Key params (defaults) | Notes | -| ------------- | ------------------------------------------ | ------------------------------------ | -| `MobileGaze` | `model_name=GazeWeights.RESNET34` | Returns `GazeResult(pitch, yaw)` in radians; trained on Gaze360 | - -**Face Parsing** - -| Class | Key params (defaults) | Notes | -| ---------- | ---------------------------------------- | ------------------------------------ | -| `BiSeNet` | `model_name=ParsingWeights.RESNET18`, `input_size=(512, 512)` | 19 facial component classes; BiSeNet architecture with ResNet backbone | - -**Anti-Spoofing** - -| Class | Key params (defaults) | Notes | -| ------------- | ----------------------------------------- | ------------------------------------ | -| `MiniFASNet` | `model_name=MiniFASNetWeights.V2` | Returns `SpoofingResult(is_real, confidence)` | - ---- - -## Model Performance - -### Face Detection (WIDER FACE Dataset) - -| Model | Easy | Medium | Hard | Use Case | -| ------------------ | ------ | ------ | ------ | ---------------------- | -| retinaface_mnet025 | 88.48% | 87.02% | 80.61% | Mobile/Edge devices | -| retinaface_mnet_v2 | 91.70% | 91.03% | 86.60% | Balanced (recommended) | -| retinaface_r34 | 94.16% | 93.12% | 88.90% | High accuracy | -| scrfd_500m | 90.57% | 88.12% | 68.51% | Real-time applications | -| scrfd_10g | 95.16% | 93.87% | 83.05% | Best accuracy/speed | -| yolov5n_face | 93.61% | 91.52% | 80.53% | Lightweight/Mobile | -| yolov5s_face | 94.33% | 92.61% | 83.15% | Real-time + accuracy | -| yolov5m_face | 95.30% | 93.76% | 85.28% | High accuracy | - -_Accuracy values from original papers: [RetinaFace](https://arxiv.org/abs/1905.00641), [SCRFD](https://arxiv.org/abs/2105.04714), [YOLOv5-Face](https://arxiv.org/abs/2105.12931)_ - -**Benchmark on your hardware:** - -```bash -python tools/detection.py --source assets/test.jpg --iterations 100 -``` - -See [MODELS.md](MODELS.md) for detailed model information and selection guide. -
--- -## Examples +## Documentation + +📚 **Full documentation**: [yakhyo.github.io/uniface](https://yakhyo.github.io/uniface/) + +| Resource | Description | +|----------|-------------| +| [Quickstart](https://yakhyo.github.io/uniface/quickstart/) | Get up and running in 5 minutes | +| [Model Zoo](https://yakhyo.github.io/uniface/models/) | All models, benchmarks, and selection guide | +| [API Reference](https://yakhyo.github.io/uniface/modules/detection/) | Detailed module documentation | +| [Tutorials](https://yakhyo.github.io/uniface/recipes/image-pipeline/) | Step-by-step workflow examples | +| [Guides](https://yakhyo.github.io/uniface/concepts/overview/) | Architecture and design principles | ### Jupyter Notebooks -Interactive examples covering common face analysis tasks: - -| Example | Description | Notebook | -|---------|-------------|----------| -| **Face Detection** | Detect faces and facial landmarks | [01_face_detection.ipynb](examples/01_face_detection.ipynb) | -| **Face Alignment** | Align and crop faces for recognition | [02_face_alignment.ipynb](examples/02_face_alignment.ipynb) | -| **Face Verification** | Compare two faces to verify identity | [03_face_verification.ipynb](examples/03_face_verification.ipynb) | -| **Face Search** | Find a person in a group photo | [04_face_search.ipynb](examples/04_face_search.ipynb) | -| **Face Analyzer** | All-in-one detection, recognition & attributes | [05_face_analyzer.ipynb](examples/05_face_analyzer.ipynb) | -| **Face Parsing** | Segment face into semantic components | [06_face_parsing.ipynb](examples/06_face_parsing.ipynb) | -| **Face Anonymization** | Blur or pixelate faces for privacy protection | [07_face_anonymization.ipynb](examples/07_face_anonymization.ipynb) | -| **Gaze Estimation** | Estimate gaze direction from face images | [08_gaze_estimation.ipynb](examples/08_gaze_estimation.ipynb) | - -### Webcam Face Detection - -```python -import cv2 -from uniface import RetinaFace -from uniface.visualization import draw_detections - -detector = RetinaFace() -cap = cv2.VideoCapture(0) - -while True: - ret, frame = cap.read() - if not ret: - break - - faces = detector.detect(frame) - - # Extract data for visualization - bboxes = [f.bbox for f in faces] - scores = [f.confidence for f in faces] - landmarks = [f.landmarks for f in faces] - - draw_detections( - image=frame, - bboxes=bboxes, - scores=scores, - landmarks=landmarks, - vis_threshold=0.6, - ) - - cv2.imshow("Face Detection", frame) - if cv2.waitKey(1) & 0xFF == ord('q'): - break - -cap.release() -cv2.destroyAllWindows() -``` - -### Face Search System - -```python -import numpy as np -from uniface import RetinaFace, ArcFace - -detector = RetinaFace() -recognizer = ArcFace() - -# Build face database -database = {} -for person_id, image_path in person_images.items(): - image = cv2.imread(image_path) - faces = detector.detect(image) - if faces: - embedding = recognizer.get_normalized_embedding( - image, faces[0].landmarks - ) - database[person_id] = embedding - -# Search for a face -query_image = cv2.imread("query.jpg") -query_faces = detector.detect(query_image) -if query_faces: - query_embedding = recognizer.get_normalized_embedding( - query_image, query_faces[0].landmarks - ) - - # Find best match - best_match = None - best_similarity = -1 - - for person_id, db_embedding in database.items(): - similarity = np.dot(query_embedding, db_embedding.T)[0][0] - if similarity > best_similarity: - best_similarity = similarity - best_match = person_id - - print(f"Best match: {best_match} (similarity: {best_similarity:.4f})") -``` - -More examples in the [examples/](examples/) directory. - ---- - -## Advanced Configuration - -### Custom ONNX Runtime Providers - -```python -from uniface.onnx_utils import get_available_providers, create_onnx_session - -# Check available providers -providers = get_available_providers() -print(f"Available: {providers}") - -# Force CPU-only execution -from uniface import RetinaFace -detector = RetinaFace() -# Internally uses create_onnx_session() which auto-selects best provider -``` - -### Model Download and Caching - -Models are automatically downloaded on first use and cached in `~/.uniface/models/`. - -```python -from uniface.model_store import verify_model_weights -from uniface.constants import RetinaFaceWeights - -# Manually download and verify a model -model_path = verify_model_weights( - RetinaFaceWeights.MNET_V2, - root='./custom_models' # Custom cache directory -) -``` - -### Logging Configuration - -```python -from uniface import Logger -import logging - -# Set logging level -Logger.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR - -# Disable logging -Logger.setLevel(logging.CRITICAL) -``` - ---- - -## Testing - -```bash -# Run all tests -pytest - -# Run with coverage -pytest --cov=uniface --cov-report=html - -# Run specific test file -pytest tests/test_retinaface.py -v -``` - ---- - -## Development - -### Setup Development Environment - -```bash -git clone https://github.com/yakhyo/uniface.git -cd uniface - -# Install in editable mode with dev dependencies -pip install -e ".[dev]" - -# Run tests -pytest -``` - -### Code Formatting - -This project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting. - -```bash -# Format code -ruff format . - -# Check for linting errors -ruff check . - -# Auto-fix linting errors -ruff check . --fix -``` - -Ruff configuration is in `pyproject.toml`. Key settings: - -- Line length: 120 -- Python target: 3.10+ -- Import sorting: `uniface` as first-party - -### Project Structure - -``` -uniface/ -├── uniface/ -│ ├── detection/ # Face detection models -│ ├── recognition/ # Face recognition models -│ ├── landmark/ # Landmark detection -│ ├── parsing/ # Face parsing -│ ├── gaze/ # Gaze estimation -│ ├── attribute/ # Age, gender, emotion -│ ├── spoofing/ # Face anti-spoofing -│ ├── privacy/ # Face anonymization & blurring -│ ├── onnx_utils.py # ONNX Runtime utilities -│ ├── model_store.py # Model download & caching -│ └── visualization.py # Drawing utilities -├── tests/ # Unit tests -├── examples/ # Example notebooks -└── tools/ # CLI utilities -``` +| Example | Colab | Description | +|---------|:-----:|-------------| +| [01_face_detection.ipynb](examples/01_face_detection.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/yakhyo/uniface/blob/main/examples/01_face_detection.ipynb) | Face detection and landmarks | +| [02_face_alignment.ipynb](examples/02_face_alignment.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/yakhyo/uniface/blob/main/examples/02_face_alignment.ipynb) | Face alignment for recognition | +| [03_face_verification.ipynb](examples/03_face_verification.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/yakhyo/uniface/blob/main/examples/03_face_verification.ipynb) | Compare faces for identity | +| [04_face_search.ipynb](examples/04_face_search.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/yakhyo/uniface/blob/main/examples/04_face_search.ipynb) | Find a person in group photos | +| [05_face_analyzer.ipynb](examples/05_face_analyzer.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/yakhyo/uniface/blob/main/examples/05_face_analyzer.ipynb) | All-in-one analysis | +| [06_face_parsing.ipynb](examples/06_face_parsing.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/yakhyo/uniface/blob/main/examples/06_face_parsing.ipynb) | Semantic face segmentation | +| [07_face_anonymization.ipynb](examples/07_face_anonymization.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/yakhyo/uniface/blob/main/examples/07_face_anonymization.ipynb) | Privacy-preserving blur | +| [08_gaze_estimation.ipynb](examples/08_gaze_estimation.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/yakhyo/uniface/blob/main/examples/08_gaze_estimation.ipynb) | Gaze direction estimation | --- ## References -- **RetinaFace Training**: [yakhyo/retinaface-pytorch](https://github.com/yakhyo/retinaface-pytorch) - PyTorch implementation and training code -- **YOLOv5-Face ONNX**: [yakhyo/yolov5-face-onnx-inference](https://github.com/yakhyo/yolov5-face-onnx-inference) - ONNX inference implementation -- **Face Recognition Training**: [yakhyo/face-recognition](https://github.com/yakhyo/face-recognition) - ArcFace, MobileFace, SphereFace training code -- **Face Parsing Training**: [yakhyo/face-parsing](https://github.com/yakhyo/face-parsing) - BiSeNet face parsing training code and pretrained weights -- **Gaze Estimation Training**: [yakhyo/gaze-estimation](https://github.com/yakhyo/gaze-estimation) - MobileGaze training code and pretrained weights -- **Face Anti-Spoofing**: [yakhyo/face-anti-spoofing](https://github.com/yakhyo/face-anti-spoofing) - MiniFASNet ONNX inference (weights from [minivision-ai/Silent-Face-Anti-Spoofing](https://github.com/minivision-ai/Silent-Face-Anti-Spoofing)) -- **FairFace**: [yakhyo/fairface-onnx](https://github.com/yakhyo/fairface-onnx) - FairFace ONNX inference for race, gender, age prediction -- **InsightFace**: [deepinsight/insightface](https://github.com/deepinsight/insightface) - Model architectures and pretrained weights +- [yakhyo/retinaface-pytorch](https://github.com/yakhyo/retinaface-pytorch) — RetinaFace training +- [yakhyo/yolov5-face-onnx-inference](https://github.com/yakhyo/yolov5-face-onnx-inference) — YOLOv5-Face ONNX +- [yakhyo/face-recognition](https://github.com/yakhyo/face-recognition) — ArcFace, MobileFace, SphereFace +- [yakhyo/face-parsing](https://github.com/yakhyo/face-parsing) — BiSeNet face parsing +- [yakhyo/gaze-estimation](https://github.com/yakhyo/gaze-estimation) — MobileGaze training +- [yakhyo/face-anti-spoofing](https://github.com/yakhyo/face-anti-spoofing) — MiniFASNet inference +- [yakhyo/fairface-onnx](https://github.com/yakhyo/fairface-onnx) — FairFace attributes +- [deepinsight/insightface](https://github.com/deepinsight/insightface) — Model architectures + +--- ## Contributing -Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/yakhyo/uniface). +Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. + +## License + +This project is licensed under the [MIT License](LICENSE). diff --git a/docs/api/reference.md b/docs/api/reference.md deleted file mode 100644 index 71eeeb3..0000000 --- a/docs/api/reference.md +++ /dev/null @@ -1,200 +0,0 @@ -# API Reference - -Quick reference for all UniFace classes and functions. - ---- - -## Detection - -### RetinaFace - -```python -from uniface import RetinaFace - -detector = RetinaFace( - model_name=RetinaFaceWeights.MNET_V2, # Model variant - confidence_threshold=0.5, # Min confidence - nms_threshold=0.4, # NMS IoU threshold - input_size=(640, 640) # Input resolution -) - -faces = detector.detect(image) # Returns list[Face] -``` - -### SCRFD - -```python -from uniface import SCRFD - -detector = SCRFD( - model_name=SCRFDWeights.SCRFD_10G_KPS, - confidence_threshold=0.5, - nms_threshold=0.4, - input_size=(640, 640) -) -``` - -### YOLOv5Face - -```python -from uniface import YOLOv5Face - -detector = YOLOv5Face( - model_name=YOLOv5FaceWeights.YOLOV5S, - confidence_threshold=0.6, - nms_threshold=0.5 -) -``` - ---- - -## Recognition - -### ArcFace - -```python -from uniface import ArcFace - -recognizer = ArcFace(model_name=ArcFaceWeights.MNET) - -embedding = recognizer.get_normalized_embedding(image, landmarks) -# Returns: np.ndarray (1, 512) -``` - -### MobileFace / SphereFace - -```python -from uniface import MobileFace, SphereFace - -recognizer = MobileFace(model_name=MobileFaceWeights.MNET_V2) -recognizer = SphereFace(model_name=SphereFaceWeights.SPHERE20) -``` - ---- - -## Landmarks - -```python -from uniface import Landmark106 - -landmarker = Landmark106() -landmarks = landmarker.get_landmarks(image, bbox) -# Returns: np.ndarray (106, 2) -``` - ---- - -## Attributes - -### AgeGender - -```python -from uniface import AgeGender - -predictor = AgeGender() -result = predictor.predict(image, bbox) -# Returns: AttributeResult(gender, age, sex) -``` - -### FairFace - -```python -from uniface import FairFace - -predictor = FairFace() -result = predictor.predict(image, bbox) -# Returns: AttributeResult(gender, age_group, race, sex) -``` - ---- - -## Gaze - -```python -from uniface import MobileGaze - -gaze = MobileGaze(model_name=GazeWeights.RESNET34) -result = gaze.estimate(face_crop) -# Returns: GazeResult(pitch, yaw) in radians -``` - ---- - -## Parsing - -```python -from uniface.parsing import BiSeNet - -parser = BiSeNet(model_name=ParsingWeights.RESNET18) -mask = parser.parse(face_image) -# Returns: np.ndarray (H, W) with values 0-18 -``` - ---- - -## Anti-Spoofing - -```python -from uniface.spoofing import MiniFASNet - -spoofer = MiniFASNet(model_name=MiniFASNetWeights.V2) -result = spoofer.predict(image, bbox) -# Returns: SpoofingResult(is_real, confidence) -``` - ---- - -## Privacy - -```python -from uniface.privacy import BlurFace, anonymize_faces - -# One-liner -anonymized = anonymize_faces(image, method='pixelate') - -# Manual control -blurrer = BlurFace(method='gaussian', blur_strength=3.0) -anonymized = blurrer.anonymize(image, faces) -``` - ---- - -## Types - -### Face - -```python -@dataclass -class Face: - bbox: np.ndarray # [x1, y1, x2, y2] - confidence: float # 0.0 to 1.0 - landmarks: np.ndarray # (5, 2) - embedding: np.ndarray | None = None - gender: int | None = None - age: int | None = None - age_group: str | None = None - race: str | None = None -``` - -### Result Types - -```python -GazeResult(pitch: float, yaw: float) -SpoofingResult(is_real: bool, confidence: float) -AttributeResult(gender: int, age: int, age_group: str, race: str) -EmotionResult(emotion: str, confidence: float) -``` - ---- - -## Utilities - -```python -from uniface import ( - compute_similarity, # Compare embeddings - face_alignment, # Align face for recognition - draw_detections, # Visualize detections - vis_parsing_maps, # Visualize parsing - verify_model_weights, # Download/verify models -) -``` diff --git a/docs/changelog.md b/docs/changelog.md deleted file mode 100644 index 3b7e8c6..0000000 --- a/docs/changelog.md +++ /dev/null @@ -1,46 +0,0 @@ -# Changelog - -All notable changes to UniFace. - ---- - -## [2.0.0] - 2025 - -### Added - -- YOLOv5-Face detection models (N/S/M variants) -- FairFace attribute prediction (race, gender, age group) -- Face parsing with BiSeNet (ResNet18/34) -- Gaze estimation with MobileGaze -- Anti-spoofing with MiniFASNet -- Face anonymization with 5 blur methods -- FaceAnalyzer for combined analysis -- Type hints throughout -- Comprehensive documentation - -### Changed - -- Unified API across all modules -- Improved model download with SHA-256 verification -- Better error messages - ---- - -## [1.0.0] - 2024 - -### Added - -- RetinaFace detection -- SCRFD detection -- ArcFace recognition -- MobileFace recognition -- SphereFace recognition -- 106-point landmarks -- Age/Gender prediction -- Emotion detection - ---- - -## Contributing - -See [Contributing Guide](contributing.md) for how to contribute. diff --git a/docs/concepts/overview.md b/docs/concepts/overview.md index 747a940..b1fa511 100644 --- a/docs/concepts/overview.md +++ b/docs/concepts/overview.md @@ -193,3 +193,4 @@ path = verify_model_weights(RetinaFaceWeights.MNET_V2) - [Inputs & Outputs](inputs-outputs.md) - Understand data types - [Execution Providers](execution-providers.md) - Hardware acceleration - [Detection Module](../modules/detection.md) - Start with face detection +- [Image Pipeline Recipe](../recipes/image-pipeline.md) - Complete workflow diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index e7689f3..0000000 --- a/docs/faq.md +++ /dev/null @@ -1,138 +0,0 @@ -# FAQ - -Frequently asked questions. - ---- - -## General - -### What is UniFace? - -A Python library for face analysis: detection, recognition, landmarks, attributes, parsing, gaze estimation, anti-spoofing, and privacy protection. - -### What are the requirements? - -- Python 3.11+ -- Works on macOS, Linux, Windows - -### Is GPU required? - -No. CPU works fine. GPU (CUDA) provides faster inference. - ---- - -## Models - -### Where are models stored? - -``` -~/.uniface/models/ -``` - -### How to use offline? - -Pre-download models: - -```python -from uniface.model_store import verify_model_weights -from uniface.constants import RetinaFaceWeights - -verify_model_weights(RetinaFaceWeights.MNET_V2) -``` - -### Which detection model is best? - -| Use Case | Model | -|----------|-------| -| Balanced | RetinaFace MNET_V2 | -| Accuracy | SCRFD 10G | -| Speed | YOLOv5n-Face | - ---- - -## Usage - -### What image format? - -BGR (OpenCV default): - -```python -image = cv2.imread("photo.jpg") # BGR -``` - -### How to compare faces? - -```python -from uniface import compute_similarity - -similarity = compute_similarity(emb1, emb2) -if similarity > 0.6: - print("Same person") -``` - -### How to get age and gender? - -```python -from uniface import AgeGender - -predictor = AgeGender() -result = predictor.predict(image, face.bbox) -print(f"{result.sex}, {result.age}") -``` - ---- - -## Performance - -### How to speed up detection? - -1. Use smaller input: - ```python - detector = RetinaFace(input_size=(320, 320)) - ``` - -2. Skip frames in video: - ```python - if frame_count % 3 == 0: - faces = detector.detect(frame) - ``` - -3. Use GPU: - ```bash - pip install uniface[gpu] - ``` - ---- - -## Accuracy - -### Detection threshold? - -Default: 0.5 - -- Higher (0.7+): Fewer false positives -- Lower (0.3): More detections - -### Similarity threshold? - -| Threshold | Meaning | -|-----------|---------| -| > 0.6 | Same person | -| 0.4-0.6 | Uncertain | -| < 0.4 | Different | - ---- - -## Privacy - -### How to blur faces? - -```python -from uniface.privacy import anonymize_faces - -result = anonymize_faces(image, method='pixelate') -``` - -### Available blur methods? - -`pixelate`, `gaussian`, `blackout`, `elliptical`, `median` diff --git a/docs/index.md b/docs/index.md index 87e692f..9d8afa8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,30 +2,26 @@ hide: - toc - navigation + - edit +template: home.html ---
-# :material-face-recognition: UniFace { .hero-title } +# UniFace { .hero-title } -A lightweight, production-ready face analysis library built on ONNX Runtime. +

A lightweight, production-ready face analysis library built on ONNX Runtime

[![PyPI](https://img.shields.io/pypi/v/uniface.svg)](https://pypi.org/project/uniface/) [![Python](https://img.shields.io/badge/Python-3.11%2B-blue)](https://www.python.org/) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Downloads](https://static.pepy.tech/badge/uniface)](https://pepy.tech/project/uniface) -![UniFace](assets/logo.webp){ width="60%" } - [Get Started](quickstart.md){ .md-button .md-button--primary } [View on GitHub](https://github.com/yakhyo/uniface){ .md-button }
---- - -## Features -
@@ -98,7 +94,7 @@ Face anonymization with 5 blur methods for privacy protection. ## Next Steps -
+
### :material-rocket-launch: Quickstart @@ -108,24 +104,24 @@ Get up and running in 5 minutes with common use cases.
-### :material-book-open-variant: Concepts -Learn about the architecture and design principles. +### :material-school: Tutorials +Step-by-step examples for common workflows. -[Read Concepts →](concepts/overview.md) +[View Tutorials →](recipes/image-pipeline.md)
-### :material-puzzle: Modules +### :material-api: API Reference Explore individual modules and their APIs. -[Browse Modules →](modules/detection.md) +[Browse API →](modules/detection.md)
-### :material-chef-hat: Recipes -Complete examples for common workflows. +### :material-book-open-variant: Guides +Learn about the architecture and design principles. -[View Recipes →](recipes/image-pipeline.md) +[Read Guides →](concepts/overview.md)
diff --git a/docs/installation.md b/docs/installation.md index f4b277a..8ef52ed 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -170,5 +170,5 @@ python -c "import platform; print(platform.machine())" ## Next Steps -- [Quickstart](quickstart.md) - Get started with common use cases -- [Concepts Overview](concepts/overview.md) - Understand the architecture +- [Quickstart Guide](quickstart.md) - Get started in 5 minutes +- [Execution Providers](concepts/execution-providers.md) - Hardware acceleration setup diff --git a/docs/license-attribution.md b/docs/license-attribution.md index 73919cf..40e1274 100644 --- a/docs/license-attribution.md +++ b/docs/license-attribution.md @@ -12,32 +12,11 @@ UniFace is released under the [MIT License](https://opensource.org/licenses/MIT) |-------|--------|---------| | RetinaFace | [yakhyo/retinaface-pytorch](https://github.com/yakhyo/retinaface-pytorch) | MIT | | SCRFD | [InsightFace](https://github.com/deepinsight/insightface) | MIT | -| YOLOv5-Face | [deepcam-cn/yolov5-face](https://github.com/deepcam-cn/yolov5-face) | GPL-3.0 | +| YOLOv5-Face | [yakhyo/yolov5-face-onnx-inference](https://github.com/yakhyo/yolov5-face-onnx-inference) | GPL-3.0 | | ArcFace | [InsightFace](https://github.com/deepinsight/insightface) | MIT | | MobileFace | [yakhyo/face-recognition](https://github.com/yakhyo/face-recognition) | MIT | | SphereFace | [yakhyo/face-recognition](https://github.com/yakhyo/face-recognition) | MIT | | BiSeNet | [yakhyo/face-parsing](https://github.com/yakhyo/face-parsing) | MIT | | MobileGaze | [yakhyo/gaze-estimation](https://github.com/yakhyo/gaze-estimation) | MIT | -| MiniFASNet | [minivision-ai/Silent-Face-Anti-Spoofing](https://github.com/minivision-ai/Silent-Face-Anti-Spoofing) | Apache-2.0 | +| MiniFASNet | [yakhyo/face-anti-spoofing](https://github.com/yakhyo/face-anti-spoofing) | Apache-2.0 | | FairFace | [yakhyo/fairface-onnx](https://github.com/yakhyo/fairface-onnx) | CC BY 4.0 | - ---- - -## Papers - -- **RetinaFace**: [arXiv:1905.00641](https://arxiv.org/abs/1905.00641) -- **SCRFD**: [arXiv:2105.04714](https://arxiv.org/abs/2105.04714) -- **YOLOv5-Face**: [arXiv:2105.12931](https://arxiv.org/abs/2105.12931) -- **ArcFace**: [arXiv:1801.07698](https://arxiv.org/abs/1801.07698) -- **SphereFace**: [arXiv:1704.08063](https://arxiv.org/abs/1704.08063) -- **BiSeNet**: [arXiv:1808.00897](https://arxiv.org/abs/1808.00897) - ---- - -## Third-Party Libraries - -| Library | License | -|---------|---------| -| ONNX Runtime | MIT | -| OpenCV | Apache-2.0 | -| NumPy | BSD-3-Clause | diff --git a/docs/models.md b/docs/models.md new file mode 100644 index 0000000..55fc4fe --- /dev/null +++ b/docs/models.md @@ -0,0 +1,317 @@ +# Model Zoo + +Complete guide to all available models, their performance characteristics, and selection criteria. + +--- + +## Face Detection Models + +### RetinaFace Family + +RetinaFace models are trained on the WIDER FACE dataset and provide excellent accuracy-speed tradeoffs. + +| Model Name | Params | Size | Easy | Medium | Hard | Use Case | +| -------------- | ------ | ----- | ------ | ------ | ------ | ----------------------------- | +| `MNET_025` | 0.4M | 1.7MB | 88.48% | 87.02% | 80.61% | Mobile/Edge devices | +| `MNET_050` | 1.0M | 2.6MB | 89.42% | 87.97% | 82.40% | Mobile/Edge devices | +| `MNET_V1` | 3.5M | 3.8MB | 90.59% | 89.14% | 84.13% | Balanced mobile | +| `MNET_V2` :material-check-circle: | 3.2M | 3.5MB | 91.70% | 91.03% | 86.60% | **Default** | +| `RESNET18` | 11.7M | 27MB | 92.50% | 91.02% | 86.63% | Server/High accuracy | +| `RESNET34` | 24.8M | 56MB | 94.16% | 93.12% | 88.90% | Maximum accuracy | + +!!! info "Accuracy & Benchmarks" + **Accuracy**: WIDER FACE validation set (Easy/Medium/Hard subsets) - from [RetinaFace paper](https://arxiv.org/abs/1905.00641) + + **Speed**: Benchmark on your own hardware using `python tools/detection.py --source --iterations 100` + +--- + +### SCRFD Family + +SCRFD (Sample and Computation Redistribution for Efficient Face Detection) models offer state-of-the-art speed-accuracy tradeoffs. + +| Model Name | Params | Size | Easy | Medium | Hard | Use Case | +| ---------------- | ------ | ----- | ------ | ------ | ------ | ------------------------------- | +| `SCRFD_500M` | 0.6M | 2.5MB | 90.57% | 88.12% | 68.51% | Real-time applications | +| `SCRFD_10G` :material-check-circle: | 4.2M | 17MB | 95.16% | 93.87% | 83.05% | **High accuracy + speed** | + +!!! info "Accuracy & Benchmarks" + **Accuracy**: WIDER FACE validation set - from [SCRFD paper](https://arxiv.org/abs/2105.04714) + + **Speed**: Benchmark on your own hardware using `python tools/detection.py --source --iterations 100` + +--- + +### YOLOv5-Face Family + +YOLOv5-Face models provide excellent detection accuracy with 5-point facial landmarks, optimized for real-time applications. + +| Model Name | Size | Easy | Medium | Hard | Use Case | +| -------------- | ---- | ------ | ------ | ------ | ------------------------------ | +| `YOLOV5N` | 11MB | 93.61% | 91.52% | 80.53% | Lightweight/Mobile | +| `YOLOV5S` :material-check-circle: | 28MB | 94.33% | 92.61% | 83.15% | **Real-time + accuracy** | +| `YOLOV5M` | 82MB | 95.30% | 93.76% | 85.28% | High accuracy | + +!!! info "Accuracy & Benchmarks" + **Accuracy**: WIDER FACE validation set - from [YOLOv5-Face paper](https://arxiv.org/abs/2105.12931) + + **Speed**: Benchmark on your own hardware using `python tools/detection.py --source --iterations 100` + +!!! note "Fixed Input Size" + All YOLOv5-Face models use a fixed input size of 640×640. Models exported to ONNX from [deepcam-cn/yolov5-face](https://github.com/deepcam-cn/yolov5-face). + +--- + +## Face Recognition Models + +### ArcFace + +State-of-the-art face recognition using additive angular margin loss. + +| Model Name | Backbone | Params | Size | Use Case | +| ----------- | --------- | ------ | ----- | -------------------------------- | +| `MNET` :material-check-circle: | MobileNet | 2.0M | 8MB | **Balanced (recommended)** | +| `RESNET` | ResNet50 | 43.6M | 166MB | Maximum accuracy | + +!!! info "Training Data" + **Dataset**: Trained on MS1M-V2 (5.8M images, 85K identities) + + **Accuracy**: Benchmark on your own dataset or use standard face verification benchmarks + +--- + +### MobileFace + +Lightweight face recognition optimized for mobile devices. + +| Model Name | Backbone | Params | Size | LFW | CALFW | CPLFW | AgeDB-30 | Use Case | +| ----------------- | ---------------- | ------ | ---- | ------ | ------ | ------ | -------- | --------------------- | +| `MNET_025` | MobileNetV1 0.25 | 0.36M | 1MB | 98.76% | 92.02% | 82.37% | 90.02% | Ultra-lightweight | +| `MNET_V2` :material-check-circle: | MobileNetV2 | 2.29M | 4MB | 99.55% | 94.87% | 86.89% | 95.16% | **Mobile/Edge** | +| `MNET_V3_SMALL` | MobileNetV3-S | 1.25M | 3MB | 99.30% | 93.77% | 85.29% | 92.79% | Mobile optimized | +| `MNET_V3_LARGE` | MobileNetV3-L | 3.52M | 10MB | 99.53% | 94.56% | 86.79% | 95.13% | Balanced mobile | + +!!! info "Training Data" + **Dataset**: Trained on MS1M-V2 (5.8M images, 85K identities) + + **Accuracy**: Evaluated on LFW, CALFW, CPLFW, and AgeDB-30 benchmarks + +!!! tip "Use Case" + These models are lightweight alternatives to ArcFace for resource-constrained environments. + +--- + +### SphereFace + +Face recognition using angular softmax loss. + +| Model Name | Backbone | Params | Size | LFW | CALFW | CPLFW | AgeDB-30 | Use Case | +| ------------ | -------- | ------ | ---- | ------ | ------ | ------ | -------- | ------------------- | +| `SPHERE20` | Sphere20 | 24.5M | 50MB | 99.67% | 95.61% | 88.75% | 96.58% | Research/Comparison | +| `SPHERE36` | Sphere36 | 34.6M | 92MB | 99.72% | 95.64% | 89.92% | 96.83% | Research/Comparison | + +!!! info "Training Data" + **Dataset**: Trained on MS1M-V2 (5.8M images, 85K identities) + + **Accuracy**: Evaluated on LFW, CALFW, CPLFW, and AgeDB-30 benchmarks + +!!! note "Architecture" + SphereFace uses angular softmax loss, an earlier approach before ArcFace. These models provide good accuracy with moderate resource requirements. + +--- + +## Facial Landmark Models + +### 106-Point Landmark Detection + +High-precision facial landmark localization. + +| Model Name | Points | Params | Size | Use Case | +| ---------- | ------ | ------ | ---- | ------------------------ | +| `2D106` | 106 | 3.7M | 14MB | Face alignment, analysis | + +**Landmark Groups:** + +| Group | Points | Count | +|-------|--------|-------| +| Face contour | 0-32 | 33 points | +| Eyebrows | 33-50 | 18 points | +| Nose | 51-62 | 12 points | +| Eyes | 63-86 | 24 points | +| Mouth | 87-105 | 19 points | + +--- + +## Attribute Analysis Models + +### Age & Gender Detection + +| Model Name | Attributes | Params | Size | Use Case | +| ----------- | ----------- | ------ | ---- | --------------- | +| `AgeGender` | Age, Gender | 2.1M | 8MB | General purpose | + +!!! info "Training Data" + **Dataset**: Trained on CelebA + +!!! warning "Accuracy Note" + Accuracy varies by demographic and image quality. Test on your specific use case. + +--- + +### FairFace Attributes + +| Model Name | Attributes | Params | Size | Use Case | +| ----------- | --------------------- | ------ | ----- | --------------------------- | +| `FairFace` | Race, Gender, Age Group | - | 44MB | Balanced demographic prediction | + +!!! info "Training Data" + **Dataset**: Trained on FairFace dataset with balanced demographics + +!!! tip "Equitable Predictions" + FairFace provides more equitable predictions across different racial and gender groups. + +**Race Categories (7):** White, Black, Latino Hispanic, East Asian, Southeast Asian, Indian, Middle Eastern + +**Age Groups (9):** 0-2, 3-9, 10-19, 20-29, 30-39, 40-49, 50-59, 60-69, 70+ + +--- + +### Emotion Detection + +| Model Name | Classes | Params | Size | Use Case | +| ------------- | ------- | ------ | ---- | --------------- | +| `AFFECNET7` | 7 | 0.5M | 2MB | 7-class emotion | +| `AFFECNET8` | 8 | 0.5M | 2MB | 8-class emotion | + +**Classes (7)**: Neutral, Happy, Sad, Surprise, Fear, Disgust, Anger + +**Classes (8)**: Above + Contempt + +!!! info "Training Data" + **Dataset**: Trained on AffectNet + +!!! note "Accuracy Note" + Emotion detection accuracy depends heavily on facial expression clarity and cultural context. + +--- + +## Gaze Estimation Models + +### MobileGaze Family + +Real-time gaze direction prediction models trained on Gaze360 dataset. Returns pitch (vertical) and yaw (horizontal) angles in radians. + +| Model Name | Params | Size | MAE* | Use Case | +| -------------- | ------ | ------- | ----- | ----------------------------- | +| `RESNET18` | 11.7M | 43 MB | 12.84 | Balanced accuracy/speed | +| `RESNET34` :material-check-circle: | 24.8M | 81.6 MB | 11.33 | **Default** | +| `RESNET50` | 25.6M | 91.3 MB | 11.34 | High accuracy | +| `MOBILENET_V2` | 3.5M | 9.59 MB | 13.07 | Mobile/Edge devices | +| `MOBILEONE_S0` | 2.1M | 4.8 MB | 12.58 | Lightweight/Real-time | + +*MAE (Mean Absolute Error) in degrees on Gaze360 test set - lower is better + +!!! info "Training Data" + **Dataset**: Trained on Gaze360 (indoor/outdoor scenes with diverse head poses) + + **Training**: 200 epochs with classification-based approach (binned angles) + +!!! note "Input Requirements" + Requires face crop as input. Use face detection first to obtain bounding boxes. + +--- + +## Face Parsing Models + +### BiSeNet Family + +BiSeNet (Bilateral Segmentation Network) models for semantic face parsing. Segments face images into 19 facial component classes. + +| Model Name | Params | Size | Classes | Use Case | +| -------------- | ------ | ------- | ------- | ----------------------------- | +| `RESNET18` :material-check-circle: | 13.3M | 50.7 MB | 19 | **Default** | +| `RESNET34` | 24.1M | 89.2 MB | 19 | Higher accuracy | + +!!! info "Training Data" + **Dataset**: Trained on CelebAMask-HQ + + **Architecture**: BiSeNet with ResNet backbone + + **Input Size**: 512×512 (automatically resized) + +**19 Facial Component Classes:** + +| # | Class | # | Class | # | Class | +|---|-------|---|-------|---|-------| +| 1 | Background | 8 | Left Ear | 15 | Neck | +| 2 | Skin | 9 | Right Ear | 16 | Neck Lace | +| 3 | Left Eyebrow | 10 | Ear Ring | 17 | Cloth | +| 4 | Right Eyebrow | 11 | Nose | 18 | Hair | +| 5 | Left Eye | 12 | Mouth | 19 | Hat | +| 6 | Right Eye | 13 | Upper Lip | | | +| 7 | Eye Glasses | 14 | Lower Lip | | | + +**Applications:** + +- Face makeup and beauty applications +- Virtual try-on systems +- Face editing and manipulation +- Facial feature extraction +- Portrait segmentation + +!!! note "Input Requirements" + Input should be a cropped face image. For full pipeline, use face detection first to obtain face crops. + +--- + +## Anti-Spoofing Models + +### MiniFASNet Family + +Lightweight face anti-spoofing models for liveness detection. Detect if a face is real (live) or fake (photo, video replay, mask). + +| Model Name | Size | Scale | Use Case | +| ---------- | ------ | ----- | ----------------------------- | +| `V1SE` | 1.2 MB | 4.0 | Squeeze-and-excitation variant | +| `V2` :material-check-circle: | 1.2 MB | 2.7 | **Default** | + +!!! info "Output Format" + **Output**: Returns `SpoofingResult(is_real, confidence)` where is_real: True=Real, False=Fake + +!!! note "Input Requirements" + Requires face bounding box from a detector. Use with RetinaFace, SCRFD, or YOLOv5Face. + +--- + +## Model Management + +Models are automatically downloaded and cached on first use. + +- **Cache location**: `~/.uniface/models/` +- **Verification**: Models are verified with SHA-256 checksums +- **Manual download**: Use `python tools/download_model.py` to pre-download models + +--- + +## References + +### Model Training & Architectures + +- **RetinaFace Training**: [yakhyo/retinaface-pytorch](https://github.com/yakhyo/retinaface-pytorch) - PyTorch implementation and training code +- **YOLOv5-Face Original**: [deepcam-cn/yolov5-face](https://github.com/deepcam-cn/yolov5-face) - Original PyTorch implementation +- **YOLOv5-Face ONNX**: [yakhyo/yolov5-face-onnx-inference](https://github.com/yakhyo/yolov5-face-onnx-inference) - ONNX inference implementation +- **Face Recognition Training**: [yakhyo/face-recognition](https://github.com/yakhyo/face-recognition) - ArcFace, MobileFace, SphereFace training code +- **Gaze Estimation Training**: [yakhyo/gaze-estimation](https://github.com/yakhyo/gaze-estimation) - MobileGaze training code and pretrained weights +- **Face Parsing Training**: [yakhyo/face-parsing](https://github.com/yakhyo/face-parsing) - BiSeNet training code and pretrained weights +- **Face Anti-Spoofing**: [yakhyo/face-anti-spoofing](https://github.com/yakhyo/face-anti-spoofing) - MiniFASNet ONNX inference (weights from [minivision-ai/Silent-Face-Anti-Spoofing](https://github.com/minivision-ai/Silent-Face-Anti-Spoofing)) +- **FairFace**: [yakhyo/fairface-onnx](https://github.com/yakhyo/fairface-onnx) - FairFace ONNX inference for race, gender, age prediction +- **InsightFace**: [deepinsight/insightface](https://github.com/deepinsight/insightface) - Model architectures and pretrained weights + +### Papers + +- **RetinaFace**: [Single-Shot Multi-Level Face Localisation in the Wild](https://arxiv.org/abs/1905.00641) +- **SCRFD**: [Sample and Computation Redistribution for Efficient Face Detection](https://arxiv.org/abs/2105.04714) +- **YOLOv5-Face**: [YOLO5Face: Why Reinventing a Face Detector](https://arxiv.org/abs/2105.12931) +- **ArcFace**: [Additive Angular Margin Loss for Deep Face Recognition](https://arxiv.org/abs/1801.07698) +- **SphereFace**: [Deep Hypersphere Embedding for Face Recognition](https://arxiv.org/abs/1704.08063) +- **BiSeNet**: [Bilateral Segmentation Network for Real-time Semantic Segmentation](https://arxiv.org/abs/1808.00897) diff --git a/docs/modules/detection.md b/docs/modules/detection.md index 3d49b3b..372ef85 100644 --- a/docs/modules/detection.md +++ b/docs/modules/detection.md @@ -53,7 +53,7 @@ detector = RetinaFace(model_name=RetinaFaceWeights.RESNET34) | MNET_025 | 0.4M | 1.7 MB | 88.5% | 87.0% | 80.6% | | MNET_050 | 1.0M | 2.6 MB | 89.4% | 88.0% | 82.4% | | MNET_V1 | 3.5M | 3.8 MB | 90.6% | 89.1% | 84.1% | -| **MNET_V2** ⭐ | 3.2M | 3.5 MB | 91.7% | 91.0% | 86.6% | +| **MNET_V2** :material-check-circle: | 3.2M | 3.5 MB | 91.7% | 91.0% | 86.6% | | RESNET18 | 11.7M | 27 MB | 92.5% | 91.0% | 86.6% | | RESNET34 | 24.8M | 56 MB | 94.2% | 93.1% | 88.9% | @@ -100,7 +100,7 @@ detector = SCRFD(model_name=SCRFDWeights.SCRFD_10G_KPS) | Variant | Params | Size | Easy | Medium | Hard | |---------|--------|------|------|--------|------| | SCRFD_500M_KPS | 0.6M | 2.5 MB | 90.6% | 88.1% | 68.5% | -| **SCRFD_10G_KPS** ⭐ | 4.2M | 17 MB | 95.2% | 93.9% | 83.1% | +| **SCRFD_10G_KPS** :material-check-circle: | 4.2M | 17 MB | 95.2% | 93.9% | 83.1% | ### Configuration @@ -147,7 +147,7 @@ detector = YOLOv5Face(model_name=YOLOv5FaceWeights.YOLOV5M) | Variant | Size | Easy | Medium | Hard | |---------|------|------|--------|------| | YOLOV5N | 11 MB | 93.6% | 91.5% | 80.5% | -| **YOLOV5S** ⭐ | 28 MB | 94.3% | 92.6% | 83.2% | +| **YOLOV5S** :material-check-circle: | 28 MB | 94.3% | 92.6% | 83.2% | | YOLOV5M | 82 MB | 95.3% | 93.8% | 85.3% | !!! note "Fixed Input Size" @@ -244,8 +244,9 @@ python tools/detection.py --source image.jpg --iterations 100 --- -## Next Steps +## See Also -- [Recognition](recognition.md) - Extract face embeddings -- [Landmarks](landmarks.md) - 106-point landmarks -- [Image Pipeline Recipe](../recipes/image-pipeline.md) - Complete workflow +- [Recognition Module](recognition.md) - Extract embeddings from detected faces +- [Landmarks Module](landmarks.md) - Get 106-point landmarks +- [Image Pipeline Recipe](../recipes/image-pipeline.md) - Complete detection workflow +- [Concepts: Thresholds](../concepts/thresholds-calibration.md) - Tuning detection parameters diff --git a/docs/modules/gaze.md b/docs/modules/gaze.md index c55737a..985a4d1 100644 --- a/docs/modules/gaze.md +++ b/docs/modules/gaze.md @@ -9,7 +9,7 @@ Gaze estimation predicts where a person is looking (pitch and yaw angles). | Model | Backbone | Size | MAE* | Best For | |-------|----------|------|------|----------| | ResNet18 | ResNet18 | 43 MB | 12.84° | Balanced | -| **ResNet34** ⭐ | ResNet34 | 82 MB | 11.33° | Recommended | +| **ResNet34** :material-check-circle: | ResNet34 | 82 MB | 11.33° | Recommended | | ResNet50 | ResNet50 | 91 MB | 11.34° | High accuracy | | MobileNetV2 | MobileNetV2 | 9.6 MB | 13.07° | Mobile | | MobileOne-S0 | MobileOne | 4.8 MB | 12.58° | Lightweight | diff --git a/docs/modules/landmarks.md b/docs/modules/landmarks.md index bfe5407..22e3c4a 100644 --- a/docs/modules/landmarks.md +++ b/docs/modules/landmarks.md @@ -243,8 +243,9 @@ landmarker = create_landmarker() # Returns Landmark106 --- -## Next Steps +## See Also -- [Attributes](attributes.md) - Age, gender, emotion -- [Gaze](gaze.md) - Gaze estimation -- [Detection](detection.md) - Face detection with 5-point landmarks +- [Detection Module](detection.md) - Face detection with 5-point landmarks +- [Attributes Module](attributes.md) - Age, gender, emotion +- [Gaze Module](gaze.md) - Gaze estimation +- [Concepts: Coordinate Systems](../concepts/coordinate-systems.md) - Landmark formats diff --git a/docs/modules/parsing.md b/docs/modules/parsing.md index 46c7dd3..71e20b9 100644 --- a/docs/modules/parsing.md +++ b/docs/modules/parsing.md @@ -8,7 +8,7 @@ Face parsing segments faces into semantic components (skin, eyes, nose, mouth, h | Model | Backbone | Size | Classes | Best For | |-------|----------|------|---------|----------| -| **BiSeNet ResNet18** ⭐ | ResNet18 | 51 MB | 19 | Balanced (recommended) | +| **BiSeNet ResNet18** :material-check-circle: | ResNet18 | 51 MB | 19 | Balanced (recommended) | | **BiSeNet ResNet34** | ResNet34 | 89 MB | 19 | Higher accuracy | --- @@ -73,7 +73,7 @@ parser = BiSeNet(model_name=ParsingWeights.RESNET34) | Variant | Params | Size | Notes | |---------|--------|------|-------| -| **RESNET18** ⭐ | 13.3M | 51 MB | Recommended | +| **RESNET18** :material-check-circle: | 13.3M | 51 MB | Recommended | | RESNET34 | 24.1M | 89 MB | Higher accuracy | --- diff --git a/docs/modules/recognition.md b/docs/modules/recognition.md index fc4ef94..e4ea328 100644 --- a/docs/modules/recognition.md +++ b/docs/modules/recognition.md @@ -50,7 +50,7 @@ recognizer = ArcFace(model_name=ArcFaceWeights.RESNET) | Variant | Backbone | Size | Use Case | |---------|----------|------|----------| -| **MNET** ⭐ | MobileNet | 8 MB | Balanced (recommended) | +| **MNET** :material-check-circle: | MobileNet | 8 MB | Balanced (recommended) | | RESNET | ResNet50 | 166 MB | Maximum accuracy | --- @@ -87,7 +87,7 @@ recognizer = MobileFace(model_name=MobileFaceWeights.MNET_V3_LARGE) | Variant | Params | Size | LFW | Use Case | |---------|--------|------|-----|----------| | MNET_025 | 0.36M | 1 MB | 98.8% | Ultra-lightweight | -| **MNET_V2** ⭐ | 2.29M | 4 MB | 99.6% | Mobile/Edge | +| **MNET_V2** :material-check-circle: | 2.29M | 4 MB | 99.6% | Mobile/Edge | | MNET_V3_SMALL | 1.25M | 3 MB | 99.3% | Mobile optimized | | MNET_V3_LARGE | 3.52M | 10 MB | 99.5% | Balanced mobile | @@ -233,8 +233,8 @@ recognizer = create_recognizer('arcface') --- -## Next Steps +## See Also -- [Landmarks](landmarks.md) - 106-point landmarks +- [Detection Module](detection.md) - Detect faces first - [Face Search Recipe](../recipes/face-search.md) - Complete search system - [Thresholds](../concepts/thresholds-calibration.md) - Calibration guide diff --git a/docs/modules/spoofing.md b/docs/modules/spoofing.md index 1d2ff34..0bafd8f 100644 --- a/docs/modules/spoofing.md +++ b/docs/modules/spoofing.md @@ -9,7 +9,7 @@ Face anti-spoofing detects whether a face is real (live) or fake (photo, video r | Model | Size | Notes | |-------|------|-------| | MiniFASNet V1SE | 1.2 MB | Squeeze-and-Excitation variant | -| **MiniFASNet V2** ⭐ | 1.2 MB | Improved version (recommended) | +| **MiniFASNet V2** :material-check-circle: | 1.2 MB | Improved version (recommended) | --- @@ -63,7 +63,7 @@ spoofer = MiniFASNet(model_name=MiniFASNetWeights.V1SE) | Variant | Size | Scale Factor | |---------|------|--------------| | V1SE | 1.2 MB | 4.0 | -| **V2** ⭐ | 1.2 MB | 2.7 | +| **V2** :material-check-circle: | 1.2 MB | 2.7 | --- diff --git a/docs/overrides/home.html b/docs/overrides/home.html new file mode 100644 index 0000000..3b2124f --- /dev/null +++ b/docs/overrides/home.html @@ -0,0 +1,5 @@ +{% extends "main.html" %} + +{% block source %} + +{% endblock %} diff --git a/docs/quickstart.md b/docs/quickstart.md index 2825ed6..0a50a2f 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -4,7 +4,7 @@ Get up and running with UniFace in 5 minutes. This guide covers the most common --- -## 1. Face Detection +## Face Detection Detect faces in an image: @@ -40,7 +40,7 @@ Face 1: --- -## 2. Visualize Detections +## Visualize Detections Draw bounding boxes and landmarks: @@ -74,7 +74,7 @@ cv2.imwrite("output.jpg", image) --- -## 3. Face Recognition +## Face Recognition Compare two faces: @@ -117,7 +117,7 @@ if faces1 and faces2: --- -## 4. Age & Gender Detection +## Age & Gender Detection ```python import cv2 @@ -146,7 +146,7 @@ Face 2: Female, 28 years old --- -## 5. FairFace Attributes +## FairFace Attributes Detect race, gender, and age group: @@ -174,7 +174,7 @@ Face 2: Female, 20-29, White --- -## 6. Facial Landmarks (106 Points) +## Facial Landmarks (106 Points) ```python import cv2 @@ -199,7 +199,7 @@ if faces: --- -## 7. Gaze Estimation +## Gaze Estimation ```python import cv2 @@ -229,7 +229,7 @@ cv2.imwrite("gaze_output.jpg", image) --- -## 8. Face Parsing +## Face Parsing Segment face into semantic components: @@ -256,7 +256,7 @@ print(f"Detected {len(np.unique(mask))} facial components") --- -## 9. Face Anonymization +## Face Anonymization Blur faces for privacy protection: @@ -295,7 +295,7 @@ anonymized = blurrer.anonymize(image, faces) --- -## 10. Face Anti-Spoofing +## Face Anti-Spoofing Detect real vs. fake faces: @@ -318,7 +318,7 @@ for i, face in enumerate(faces): --- -## 11. Webcam Demo +## Webcam Demo Real-time face detection: @@ -355,8 +355,72 @@ cv2.destroyAllWindows() --- +## Model Selection + +For detailed model comparisons, benchmarks, and selection guidance, see the [Model Zoo](models.md). + +**Quick recommendations:** + +| Task | Recommended Model | Alternative | +|------|-------------------|-------------| +| Detection (balanced) | `RetinaFace` (MNET_V2) | `YOLOv5Face` (YOLOV5S) | +| Detection (speed) | `RetinaFace` (MNET_025) | `SCRFD` (SCRFD_500M) | +| Detection (accuracy) | `SCRFD` (SCRFD_10G) | `RetinaFace` (RESNET34) | +| Recognition | `ArcFace` (MNET) | `MobileFace` (MNET_V2) | +| Gaze | `MobileGaze` (RESNET34) | `MobileGaze` (MOBILEONE_S0) | +| Parsing | `BiSeNet` (RESNET18) | `BiSeNet` (RESNET34) | + +--- + +## Common Issues + +### Models Not Downloading + +```python +from uniface.model_store import verify_model_weights +from uniface.constants import RetinaFaceWeights + +# Manually download a model +model_path = verify_model_weights(RetinaFaceWeights.MNET_V2) +print(f"Model downloaded to: {model_path}") +``` + +### Check Hardware Acceleration + +```python +import onnxruntime as ort +print("Available providers:", ort.get_available_providers()) + +# macOS M-series should show: ['CoreMLExecutionProvider', ...] +# NVIDIA GPU should show: ['CUDAExecutionProvider', ...] +``` + +### Slow Performance on Mac + +Verify you're using the ARM64 build of Python: + +```bash +python -c "import platform; print(platform.machine())" +# Should show: arm64 (not x86_64) +``` + +### Import Errors + +```python +# Correct imports +from uniface.detection import RetinaFace +from uniface.recognition import ArcFace +from uniface.landmark import Landmark106 + +# Also works (re-exported at package level) +from uniface import RetinaFace, ArcFace, Landmark106 +``` + +--- + ## Next Steps -- [Concepts Overview](concepts/overview.md) - Understand the architecture -- [Detection Module](modules/detection.md) - Deep dive into detection models -- [Recipes](recipes/image-pipeline.md) - Complete workflow examples +- [Model Zoo](models.md) - All models, benchmarks, and selection guide +- [API Reference](modules/detection.md) - Explore individual modules and their APIs +- [Tutorials](recipes/image-pipeline.md) - Step-by-step examples for common workflows +- [Guides](concepts/overview.md) - Learn about the architecture and design principles diff --git a/docs/recipes/anonymize-stream.md b/docs/recipes/anonymize-stream.md index f361bda..6d9a0de 100644 --- a/docs/recipes/anonymize-stream.md +++ b/docs/recipes/anonymize-stream.md @@ -2,9 +2,12 @@ Blur faces in real-time video streams for privacy protection. +!!! note "Work in Progress" + This page contains example code patterns. Test thoroughly before using in production. + --- -## Webcam +## Webcam Anonymization ```python import cv2 @@ -33,7 +36,7 @@ cv2.destroyAllWindows() --- -## Video File +## Video File Anonymization ```python import cv2 @@ -64,7 +67,7 @@ out.release() --- -## One-Liner +## One-Liner for Images ```python from uniface.privacy import anonymize_faces @@ -77,12 +80,20 @@ cv2.imwrite("anonymized.jpg", result) --- -## Blur Methods +## Available Blur Methods -| Method | Code | -|--------|------| +| Method | Usage | +|--------|-------| | Pixelate | `BlurFace(method='pixelate', pixel_blocks=10)` | | Gaussian | `BlurFace(method='gaussian', blur_strength=3.0)` | | Blackout | `BlurFace(method='blackout', color=(0,0,0))` | | Elliptical | `BlurFace(method='elliptical', margin=20)` | | Median | `BlurFace(method='median', blur_strength=3.0)` | + +--- + +## See Also + +- [Privacy Module](../modules/privacy.md) - Privacy protection details +- [Video & Webcam](video-webcam.md) - Real-time processing +- [Detection Module](../modules/detection.md) - Face detection diff --git a/docs/recipes/batch-processing.md b/docs/recipes/batch-processing.md index 7fa8880..c20ec1e 100644 --- a/docs/recipes/batch-processing.md +++ b/docs/recipes/batch-processing.md @@ -2,6 +2,9 @@ Process multiple images efficiently. +!!! note "Work in Progress" + This page contains example code patterns. Test thoroughly before using in production. + --- ## Basic Batch Processing @@ -10,7 +13,6 @@ Process multiple images efficiently. import cv2 from pathlib import Path from uniface import RetinaFace -from uniface.visualization import draw_detections detector = RetinaFace() @@ -20,334 +22,62 @@ def process_directory(input_dir, output_dir): output_path = Path(output_dir) output_path.mkdir(parents=True, exist_ok=True) - # Supported image formats - extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp'] - image_files = [] - for ext in extensions: - image_files.extend(input_path.glob(ext)) - image_files.extend(input_path.glob(ext.upper())) - - print(f"Found {len(image_files)} images") - - results = {} - - for image_path in image_files: + for image_path in input_path.glob("*.jpg"): print(f"Processing {image_path.name}...") image = cv2.imread(str(image_path)) - if image is None: - print(f" Failed to load {image_path.name}") - continue - faces = detector.detect(image) + print(f" Found {len(faces)} face(s)") - # Store results - results[image_path.name] = { - 'num_faces': len(faces), - 'faces': [ - { - 'bbox': face.bbox.tolist(), - 'confidence': float(face.confidence) - } - for face in faces - ] - } - - # Visualize and save - if faces: - draw_detections( - image=image, - bboxes=[f.bbox for f in faces], - scores=[f.confidence for f in faces], - landmarks=[f.landmarks for f in faces] - ) - - output_file = output_path / image_path.name - cv2.imwrite(str(output_file), image) - - return results + # Process and save results + # ... your code here ... # Usage -results = process_directory("input_images/", "output_images/") -print(f"\nProcessed {len(results)} images") +process_directory("input_images/", "output_images/") ``` --- -## Parallel Processing - -Use multiprocessing for faster batch processing: - -```python -import cv2 -from pathlib import Path -from concurrent.futures import ProcessPoolExecutor, as_completed -from uniface import RetinaFace - -def process_single_image(image_path, output_dir): - """Process a single image (runs in worker process).""" - # Create detector in each process - detector = RetinaFace() - - image = cv2.imread(str(image_path)) - if image is None: - return image_path.name, {'error': 'Failed to load'} - - faces = detector.detect(image) - - result = { - 'num_faces': len(faces), - 'faces': [ - { - 'bbox': face.bbox.tolist(), - 'confidence': float(face.confidence) - } - for face in faces - ] - } - - # Save result - output_path = Path(output_dir) / image_path.name - cv2.imwrite(str(output_path), image) - - return image_path.name, result - -def batch_process_parallel(input_dir, output_dir, max_workers=4): - """Process images in parallel.""" - input_path = Path(input_dir) - output_path = Path(output_dir) - output_path.mkdir(parents=True, exist_ok=True) - - image_files = list(input_path.glob("*.jpg")) + list(input_path.glob("*.png")) - - results = {} - - with ProcessPoolExecutor(max_workers=max_workers) as executor: - futures = { - executor.submit(process_single_image, img, output_dir): img - for img in image_files - } - - for future in as_completed(futures): - name, result = future.result() - results[name] = result - print(f"Completed: {name} - {result.get('num_faces', 'error')} faces") - - return results - -# Usage -results = batch_process_parallel("input_images/", "output_images/", max_workers=4) -``` - ---- - -## Progress Tracking - -Use tqdm for progress bars: +## With Progress Bar ```python from tqdm import tqdm -def process_with_progress(input_dir, output_dir): - """Process with progress bar.""" - detector = RetinaFace() - - input_path = Path(input_dir) - output_path = Path(output_dir) - output_path.mkdir(parents=True, exist_ok=True) - - image_files = list(input_path.glob("*.jpg")) + list(input_path.glob("*.png")) - - results = {} - - for image_path in tqdm(image_files, desc="Processing images"): - image = cv2.imread(str(image_path)) - if image is None: - continue - - faces = detector.detect(image) - results[image_path.name] = len(faces) - - cv2.imwrite(str(output_path / image_path.name), image) - - return results - -# Usage -results = process_with_progress("input/", "output/") -print(f"Total faces found: {sum(results.values())}") +for image_path in tqdm(image_files, desc="Processing"): + # ... process image ... + pass ``` --- -## Batch Embedding Extraction - -Extract embeddings for a face database: +## Extract Embeddings ```python -import numpy as np -from pathlib import Path from uniface import RetinaFace, ArcFace +import numpy as np -def extract_embeddings(image_dir): - """Extract embeddings from all faces.""" - detector = RetinaFace() - recognizer = ArcFace() +detector = RetinaFace() +recognizer = ArcFace() - embeddings = {} +embeddings = {} +for image_path in Path("faces/").glob("*.jpg"): + image = cv2.imread(str(image_path)) + faces = detector.detect(image) - for image_path in Path(image_dir).glob("*.jpg"): - image = cv2.imread(str(image_path)) - if image is None: - continue + if faces: + embedding = recognizer.get_normalized_embedding(image, faces[0].landmarks) + embeddings[image_path.stem] = embedding - faces = detector.detect(image) - - if faces: - # Use first face - embedding = recognizer.get_normalized_embedding( - image, faces[0].landmarks - ) - embeddings[image_path.stem] = embedding - print(f"Extracted: {image_path.stem}") - - return embeddings - -def save_embeddings(embeddings, output_path): - """Save embeddings to file.""" - np.savez(output_path, **embeddings) - print(f"Saved {len(embeddings)} embeddings to {output_path}") - -def load_embeddings(input_path): - """Load embeddings from file.""" - data = np.load(input_path) - return {key: data[key] for key in data.files} - -# Usage -embeddings = extract_embeddings("faces/") -save_embeddings(embeddings, "embeddings.npz") - -# Later... -loaded = load_embeddings("embeddings.npz") +# Save embeddings +np.savez("embeddings.npz", **embeddings) ``` --- -## CSV Output - -Export results to CSV: - -```python -import csv -from pathlib import Path - -def export_to_csv(results, output_path): - """Export detection results to CSV.""" - with open(output_path, 'w', newline='') as f: - writer = csv.writer(f) - writer.writerow(['filename', 'face_id', 'x1', 'y1', 'x2', 'y2', 'confidence']) - - for filename, data in results.items(): - for i, face in enumerate(data['faces']): - bbox = face['bbox'] - writer.writerow([ - filename, i, - bbox[0], bbox[1], bbox[2], bbox[3], - face['confidence'] - ]) - - print(f"Exported to {output_path}") - -# Usage -results = process_directory("input/", "output/") -export_to_csv(results, "detections.csv") -``` - ---- - -## Memory-Efficient Processing - -For large batches, process in chunks: - -```python -def process_in_chunks(image_files, chunk_size=100): - """Process images in memory-efficient chunks.""" - detector = RetinaFace() - - all_results = {} - - for i in range(0, len(image_files), chunk_size): - chunk = image_files[i:i + chunk_size] - print(f"Processing chunk {i//chunk_size + 1}/{(len(image_files)-1)//chunk_size + 1}") - - for image_path in chunk: - image = cv2.imread(str(image_path)) - if image is None: - continue - - faces = detector.detect(image) - all_results[image_path.name] = len(faces) - - # Free memory - del image - - # Optional: force garbage collection - import gc - gc.collect() - - return all_results -``` - ---- - -## Error Handling - -Robust batch processing with error handling: - -```python -import logging -from pathlib import Path - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -def robust_batch_process(input_dir, output_dir): - """Batch process with error handling.""" - detector = RetinaFace() - - input_path = Path(input_dir) - output_path = Path(output_dir) - output_path.mkdir(parents=True, exist_ok=True) - - image_files = list(input_path.glob("*.[jJ][pP][gG]")) - - success_count = 0 - error_count = 0 - - for image_path in image_files: - try: - image = cv2.imread(str(image_path)) - if image is None: - raise ValueError("Failed to load image") - - faces = detector.detect(image) - - cv2.imwrite(str(output_path / image_path.name), image) - success_count += 1 - logger.info(f"Processed {image_path.name}: {len(faces)} faces") - - except Exception as e: - error_count += 1 - logger.error(f"Error processing {image_path.name}: {e}") - - logger.info(f"Completed: {success_count} success, {error_count} errors") - return success_count, error_count -``` - ---- - -## Next Steps +## See Also - [Video & Webcam](video-webcam.md) - Real-time processing - [Face Search](face-search.md) - Search through embeddings - [Image Pipeline](image-pipeline.md) - Full analysis pipeline +- [Detection Module](../modules/detection.md) - Detection options diff --git a/docs/recipes/custom-models.md b/docs/recipes/custom-models.md index fd077c5..4158300 100644 --- a/docs/recipes/custom-models.md +++ b/docs/recipes/custom-models.md @@ -2,9 +2,22 @@ Add your own ONNX models to UniFace. +!!! note "Work in Progress" + This page contains example code patterns for advanced users. Test thoroughly before using in production. + --- -## Add Detection Model +## Overview + +UniFace is designed to be extensible. You can add custom ONNX models by: + +1. Creating a class that inherits from the appropriate base class +2. Implementing required methods +3. Using the ONNX Runtime utilities provided by UniFace + +--- + +## Add Custom Detection Model ```python from uniface.detection.base import BaseDetector @@ -18,28 +31,30 @@ class MyDetector(BaseDetector): self.threshold = confidence_threshold def detect(self, image: np.ndarray) -> list[Face]: - # Preprocess + # 1. Preprocess image input_tensor = self._preprocess(image) - # Inference + # 2. Run inference outputs = self.session.run(None, {'input': input_tensor}) - # Postprocess + # 3. Postprocess outputs to Face objects faces = self._postprocess(outputs, image.shape) return faces def _preprocess(self, image): # Your preprocessing logic + # e.g., resize, normalize, transpose pass def _postprocess(self, outputs, shape): # Your postprocessing logic + # e.g., decode boxes, apply NMS, create Face objects pass ``` --- -## Add Recognition Model +## Add Custom Recognition Model ```python from uniface.recognition.base import BaseRecognizer @@ -51,17 +66,21 @@ class MyRecognizer(BaseRecognizer): def __init__(self, model_path: str): self.session = create_onnx_session(model_path) - def get_normalized_embedding(self, image: np.ndarray, landmarks: np.ndarray) -> np.ndarray: - # Align face + def get_normalized_embedding( + self, + image: np.ndarray, + landmarks: np.ndarray + ) -> np.ndarray: + # 1. Align face aligned = face_alignment(image, landmarks) - # Preprocess + # 2. Preprocess input_tensor = self._preprocess(aligned) - # Inference + # 3. Run inference embedding = self.session.run(None, {'input': input_tensor})[0] - # Normalize + # 4. Normalize embedding = embedding / np.linalg.norm(embedding) return embedding @@ -72,25 +91,24 @@ class MyRecognizer(BaseRecognizer): --- -## Register Weights - -Add to `uniface/constants.py`: +## Usage ```python -class MyModelWeights(str, Enum): - DEFAULT = "my_model" +from my_module import MyDetector, MyRecognizer -MODEL_URLS[MyModelWeights.DEFAULT] = 'https://...' -MODEL_SHA256[MyModelWeights.DEFAULT] = 'sha256hash...' +# Use custom models +detector = MyDetector("path/to/detection_model.onnx") +recognizer = MyRecognizer("path/to/recognition_model.onnx") + +# Use like built-in models +faces = detector.detect(image) +embedding = recognizer.get_normalized_embedding(image, faces[0].landmarks) ``` --- -## Use Custom Model +## See Also -```python -from my_module import MyDetector - -detector = MyDetector("path/to/model.onnx") -faces = detector.detect(image) -``` +- [Detection Module](../modules/detection.md) - Built-in detection models +- [Recognition Module](../modules/recognition.md) - Built-in recognition models +- [Concepts: Overview](../concepts/overview.md) - Architecture overview diff --git a/docs/recipes/face-search.md b/docs/recipes/face-search.md index ffedae2..f293035 100644 --- a/docs/recipes/face-search.md +++ b/docs/recipes/face-search.md @@ -2,9 +2,12 @@ Build a face search system for finding people in images. +!!! note "Work in Progress" + This page contains example code patterns. Test thoroughly before using in production. + --- -## Build Face Database +## Basic Face Database ```python import numpy as np @@ -17,38 +20,18 @@ class FaceDatabase: self.detector = RetinaFace() self.recognizer = ArcFace() self.embeddings = {} - self.metadata = {} - def add_face(self, person_id, image, metadata=None): + def add_face(self, person_id, image): """Add a face to the database.""" faces = self.detector.detect(image) - if not faces: raise ValueError(f"No face found for {person_id}") - # Use highest confidence face face = max(faces, key=lambda f: f.confidence) embedding = self.recognizer.get_normalized_embedding(image, face.landmarks) - self.embeddings[person_id] = embedding - self.metadata[person_id] = metadata or {} - return True - def add_from_directory(self, directory): - """Add faces from a directory (filename = person_id).""" - dir_path = Path(directory) - - for image_path in dir_path.glob("*.jpg"): - person_id = image_path.stem - image = cv2.imread(str(image_path)) - - try: - self.add_face(person_id, image, {'source': str(image_path)}) - print(f"Added: {person_id}") - except ValueError as e: - print(f"Skipped {person_id}: {e}") - def search(self, image, threshold=0.6): """Search for faces in an image.""" faces = self.detector.detect(image) @@ -62,55 +45,50 @@ class FaceDatabase: for person_id, db_embedding in self.embeddings.items(): similarity = np.dot(embedding, db_embedding.T)[0][0] - if similarity > best_similarity: best_similarity = similarity best_match = person_id results.append({ 'bbox': face.bbox, - 'confidence': face.confidence, 'match': best_match if best_similarity >= threshold else None, - 'similarity': best_similarity, - 'metadata': self.metadata.get(best_match, {}) + 'similarity': best_similarity }) return results def save(self, path): """Save database to file.""" - np.savez( - path, - embeddings=dict(self.embeddings), - metadata=self.metadata - ) - print(f"Saved database to {path}") + np.savez(path, embeddings=dict(self.embeddings)) def load(self, path): """Load database from file.""" data = np.load(path, allow_pickle=True) self.embeddings = data['embeddings'].item() - self.metadata = data['metadata'].item() - print(f"Loaded {len(self.embeddings)} faces from {path}") # Usage db = FaceDatabase() -# Add faces from directory -db.add_from_directory("known_faces/") +# Add faces +for image_path in Path("known_faces/").glob("*.jpg"): + person_id = image_path.stem + image = cv2.imread(str(image_path)) + try: + db.add_face(person_id, image) + print(f"Added: {person_id}") + except ValueError as e: + print(f"Skipped: {e}") -# Save for later +# Save database db.save("face_database.npz") -# Search for person +# Search query_image = cv2.imread("group_photo.jpg") results = db.search(query_image) for r in results: if r['match']: print(f"Found: {r['match']} (similarity: {r['similarity']:.3f})") - else: - print(f"Unknown face (best similarity: {r['similarity']:.3f})") ``` --- @@ -192,149 +170,9 @@ realtime_search(db) --- -## Top-K Search +## See Also -Find top K matches instead of best match only: - -```python -def search_top_k(self, embedding, k=5): - """Find top K matches for an embedding.""" - similarities = [] - - for person_id, db_embedding in self.embeddings.items(): - similarity = np.dot(embedding, db_embedding.T)[0][0] - similarities.append((person_id, similarity)) - - # Sort by similarity (descending) - similarities.sort(key=lambda x: x[1], reverse=True) - - return similarities[:k] - -# Usage -query_embedding = recognizer.get_normalized_embedding(image, face.landmarks) -top_matches = search_top_k(query_embedding, k=3) - -for person_id, similarity in top_matches: - print(f"{person_id}: {similarity:.4f}") -``` - ---- - -## Batch Search - -Search through multiple query images: - -```python -from pathlib import Path - -def batch_search(db, query_dir, threshold=0.6): - """Search for faces in multiple images.""" - all_results = {} - - for image_path in Path(query_dir).glob("*.jpg"): - image = cv2.imread(str(image_path)) - results = db.search(image, threshold) - - matches = [r['match'] for r in results if r['match']] - all_results[image_path.name] = matches - - print(f"{image_path.name}: {matches}") - - return all_results - -# Usage -results = batch_search(db, "query_images/") -``` - ---- - -## Find Person in Group Photo - -```python -def find_person_in_group(db, person_id, group_image, threshold=0.6): - """Find a specific person in a group photo.""" - if person_id not in db.embeddings: - raise ValueError(f"Person {person_id} not in database") - - reference_embedding = db.embeddings[person_id] - faces = db.detector.detect(group_image) - - best_match = None - best_similarity = -1 - - for face in faces: - embedding = db.recognizer.get_normalized_embedding( - group_image, face.landmarks - ) - similarity = np.dot(embedding, reference_embedding.T)[0][0] - - if similarity > best_similarity: - best_similarity = similarity - best_match = face - - if best_match and best_similarity >= threshold: - return { - 'found': True, - 'face': best_match, - 'similarity': best_similarity - } - - return {'found': False, 'similarity': best_similarity} - -# Usage -group = cv2.imread("group_photo.jpg") -result = find_person_in_group(db, "john_doe", group) - -if result['found']: - print(f"Found with similarity: {result['similarity']:.3f}") - # Draw the found face - x1, y1, x2, y2 = map(int, result['face'].bbox) - cv2.rectangle(group, (x1, y1), (x2, y2), (0, 255, 0), 3) - cv2.imwrite("found.jpg", group) -``` - ---- - -## Update Database - -Add or update faces: - -```python -def update_face(db, person_id, new_image): - """Update a person's face in the database.""" - faces = db.detector.detect(new_image) - - if not faces: - print(f"No face found in new image for {person_id}") - return False - - face = max(faces, key=lambda f: f.confidence) - new_embedding = db.recognizer.get_normalized_embedding( - new_image, face.landmarks - ) - - if person_id in db.embeddings: - # Average with existing embedding - old_embedding = db.embeddings[person_id] - db.embeddings[person_id] = (old_embedding + new_embedding) / 2 - # Re-normalize - db.embeddings[person_id] /= np.linalg.norm(db.embeddings[person_id]) - print(f"Updated: {person_id}") - else: - db.embeddings[person_id] = new_embedding - print(f"Added: {person_id}") - - return True - -# Usage -update_face(db, "john_doe", cv2.imread("john_new.jpg")) -db.save("face_database.npz") -``` - ---- - -## Next Steps - -- [Anonymize Stream](anonymize-stream.md) - Privacy protection +- [Recognition Module](../modules/recognition.md) - Face recognition details - [Batch Processing](batch-processing.md) - Process multiple files -- [Recognition Module](../modules/recognition.md) - Model details +- [Video & Webcam](video-webcam.md) - Real-time processing +- [Concepts: Thresholds](../concepts/thresholds-calibration.md) - Tuning similarity thresholds diff --git a/docs/recipes/image-pipeline.md b/docs/recipes/image-pipeline.md index 3e9745b..b80dbc1 100644 --- a/docs/recipes/image-pipeline.md +++ b/docs/recipes/image-pipeline.md @@ -277,3 +277,5 @@ with open('results.json', 'w') as f: - [Batch Processing](batch-processing.md) - Process multiple images - [Video & Webcam](video-webcam.md) - Real-time processing - [Face Search](face-search.md) - Build a search system +- [Detection Module](../modules/detection.md) - Detection options +- [Recognition Module](../modules/recognition.md) - Recognition details diff --git a/docs/recipes/video-webcam.md b/docs/recipes/video-webcam.md index 713faf9..496d589 100644 --- a/docs/recipes/video-webcam.md +++ b/docs/recipes/video-webcam.md @@ -2,6 +2,9 @@ Real-time face analysis for video streams. +!!! note "Work in Progress" + This page contains example code patterns. Test thoroughly before using in production. + --- ## Webcam Detection @@ -21,10 +24,8 @@ while True: if not ret: break - # Detect faces faces = detector.detect(frame) - # Draw results draw_detections( image=frame, bboxes=[f.bbox for f in faces], @@ -48,49 +49,33 @@ cv2.destroyAllWindows() ```python import cv2 from uniface import RetinaFace -from uniface.visualization import draw_detections def process_video(input_path, output_path): """Process a video file.""" detector = RetinaFace() - cap = cv2.VideoCapture(input_path) # Get video properties fps = cap.get(cv2.CAP_PROP_FPS) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) - # Setup output video + # Setup output fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) - frame_count = 0 - - while True: + while cap.read()[0]: ret, frame = cap.read() if not ret: break - # Detect and draw faces = detector.detect(frame) - draw_detections( - image=frame, - bboxes=[f.bbox for f in faces], - scores=[f.confidence for f in faces], - landmarks=[f.landmarks for f in faces] - ) + # ... process and draw ... out.write(frame) - frame_count += 1 - if frame_count % 100 == 0: - print(f"Processed {frame_count}/{total_frames} frames") - cap.release() out.release() - print(f"Saved to {output_path}") # Usage process_video("input.mp4", "output.mp4") @@ -98,295 +83,43 @@ process_video("input.mp4", "output.mp4") --- -## FPS Counter +## Performance Tips -Add frame rate display: +### Skip Frames ```python -import cv2 -import time -from uniface import RetinaFace - -detector = RetinaFace() -cap = cv2.VideoCapture(0) - -prev_time = time.time() -fps = 0 - -while True: - ret, frame = cap.read() - if not ret: - break - - # Calculate FPS - curr_time = time.time() - fps = 1 / (curr_time - prev_time) - prev_time = curr_time - - # Detect faces - faces = detector.detect(frame) - - # Draw FPS - cv2.putText(frame, f"FPS: {fps:.1f}", (10, 30), - cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) - cv2.putText(frame, f"Faces: {len(faces)}", (10, 70), - cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) - - # Draw detections - for face in faces: - x1, y1, x2, y2 = map(int, face.bbox) - cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) - - cv2.imshow("Face Detection", frame) - - if cv2.waitKey(1) & 0xFF == ord('q'): - break - -cap.release() -cv2.destroyAllWindows() -``` - ---- - -## Skip Frames for Performance - -Process every N frames for better performance: - -```python -import cv2 -from uniface import RetinaFace - -detector = RetinaFace() -cap = cv2.VideoCapture(0) - PROCESS_EVERY_N = 3 # Process every 3rd frame frame_count = 0 last_faces = [] while True: ret, frame = cap.read() - if not ret: - break - - frame_count += 1 - - # Only detect every N frames if frame_count % PROCESS_EVERY_N == 0: last_faces = detector.detect(frame) - - # Draw last detection results - for face in last_faces: - x1, y1, x2, y2 = map(int, face.bbox) - cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) - - cv2.imshow("Detection", frame) - - if cv2.waitKey(1) & 0xFF == ord('q'): - break - -cap.release() -cv2.destroyAllWindows() + frame_count += 1 + # Draw last_faces... ``` ---- - -## Full Analysis Pipeline - -Real-time detection with age/gender: +### FPS Counter ```python -import cv2 -from uniface import RetinaFace, AgeGender - -detector = RetinaFace() -age_gender = AgeGender() -cap = cv2.VideoCapture(0) +import time +prev_time = time.time() while True: - ret, frame = cap.read() - if not ret: - break + curr_time = time.time() + fps = 1 / (curr_time - prev_time) + prev_time = curr_time - faces = detector.detect(frame) - - for face in faces: - x1, y1, x2, y2 = map(int, face.bbox) - - # Draw box - cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) - - # Predict age/gender - result = age_gender.predict(frame, face.bbox) - label = f"{result.sex}, {result.age}y" - - cv2.putText(frame, label, (x1, y1 - 10), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) - - cv2.imshow("Age/Gender Detection", frame) - - if cv2.waitKey(1) & 0xFF == ord('q'): - break - -cap.release() -cv2.destroyAllWindows() + cv2.putText(frame, f"FPS: {fps:.1f}", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) ``` --- -## Gaze Tracking +## See Also -Real-time gaze estimation: - -```python -import cv2 -import numpy as np -from uniface import RetinaFace, MobileGaze -from uniface.visualization import draw_gaze - -detector = RetinaFace() -gaze = MobileGaze() -cap = cv2.VideoCapture(0) - -while True: - ret, frame = cap.read() - if not ret: - break - - faces = detector.detect(frame) - - for face in faces: - x1, y1, x2, y2 = map(int, face.bbox) - face_crop = frame[y1:y2, x1:x2] - - if face_crop.size > 0: - result = gaze.estimate(face_crop) - - # Draw box - cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) - - # Draw gaze arrow - draw_gaze(frame, face.bbox, result.pitch, result.yaw) - - # Display angles - label = f"P:{np.degrees(result.pitch):.0f} Y:{np.degrees(result.yaw):.0f}" - cv2.putText(frame, label, (x1, y1 - 10), - cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) - - cv2.imshow("Gaze Estimation", frame) - - if cv2.waitKey(1) & 0xFF == ord('q'): - break - -cap.release() -cv2.destroyAllWindows() -``` - ---- - -## Recording Output - -Record processed video: - -```python -import cv2 -from uniface import RetinaFace - -detector = RetinaFace() -cap = cv2.VideoCapture(0) - -# Get camera properties -fps = 30 -width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) -height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - -# Setup recording -fourcc = cv2.VideoWriter_fourcc(*'mp4v') -out = cv2.VideoWriter('recording.mp4', fourcc, fps, (width, height)) - -is_recording = False - -print("Press 'r' to start/stop recording, 'q' to quit") - -while True: - ret, frame = cap.read() - if not ret: - break - - faces = detector.detect(frame) - - # Draw detections - for face in faces: - x1, y1, x2, y2 = map(int, face.bbox) - cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) - - # Recording indicator - if is_recording: - cv2.circle(frame, (30, 30), 10, (0, 0, 255), -1) - out.write(frame) - - cv2.imshow("Detection", frame) - - key = cv2.waitKey(1) & 0xFF - if key == ord('r'): - is_recording = not is_recording - print(f"Recording: {is_recording}") - elif key == ord('q'): - break - -cap.release() -out.release() -cv2.destroyAllWindows() -``` - ---- - -## Multi-Camera - -Process multiple cameras: - -```python -import cv2 -from uniface import RetinaFace - -detector = RetinaFace() - -# Open multiple cameras -caps = [ - cv2.VideoCapture(0), - cv2.VideoCapture(1) # Second camera -] - -while True: - frames = [] - - for i, cap in enumerate(caps): - ret, frame = cap.read() - if ret: - faces = detector.detect(frame) - - for face in faces: - x1, y1, x2, y2 = map(int, face.bbox) - cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) - - frames.append(frame) - - # Display side by side - if len(frames) == 2: - combined = cv2.hconcat(frames) - cv2.imshow("Multi-Camera", combined) - - if cv2.waitKey(1) & 0xFF == ord('q'): - break - -for cap in caps: - cap.release() -cv2.destroyAllWindows() -``` - ---- - -## Next Steps - -- [Anonymize Stream](anonymize-stream.md) - Privacy in video -- [Face Search](face-search.md) - Identity search -- [Image Pipeline](image-pipeline.md) - Full analysis +- [Anonymize Stream](anonymize-stream.md) - Privacy protection in video +- [Batch Processing](batch-processing.md) - Process multiple files +- [Detection Module](../modules/detection.md) - Detection options +- [Gaze Module](../modules/gaze.md) - Gaze tracking diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 7fad898..fd8b225 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,43 +1,225 @@ /* UniFace Documentation - Custom Styles */ -/* Hero section */ -.hero { +/* ===== Hero Section ===== */ + +.md-content .hero { text-align: center; - padding: 2rem 0; + padding: 3rem 1rem 2rem; + margin: 0 auto; + max-width: 900px; } .hero-title { - font-size: 3rem !important; - font-weight: 700 !important; + font-size: 3.5rem !important; + font-weight: 800 !important; margin-bottom: 0.5rem !important; + background: linear-gradient(135deg, var(--md-primary-fg-color) 0%, #7c4dff 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } -/* Feature grid */ +.hero-tagline { + font-size: 1.5rem; + color: var(--md-default-fg-color); + margin-bottom: 0.5rem !important; + font-weight: 500; +} + +.hero-subtitle { + font-size: 1rem; + color: var(--md-default-fg-color--light); + margin-bottom: 1.5rem !important; + font-weight: 400; + letter-spacing: 0.5px; +} + +.hero .md-button { + margin: 0.5rem 0.25rem; + padding: 0.7rem 1.5rem; + font-weight: 600; + border-radius: 8px; + transition: all 0.2s ease; +} + +.hero .md-button--primary { + background: linear-gradient(135deg, var(--md-primary-fg-color) 0%, #5c6bc0 100%); + border: none; + box-shadow: 0 4px 14px rgba(63, 81, 181, 0.4); +} + +.hero .md-button--primary:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(63, 81, 181, 0.5); +} + +.hero .md-button:not(.md-button--primary) { + border: 2px solid var(--md-primary-fg-color); + background: transparent; + color: var(--md-primary-fg-color); +} + +.hero .md-button:not(.md-button--primary):hover { + background: var(--md-primary-fg-color); + border-color: var(--md-primary-fg-color); + color: white; + transform: translateY(-2px); +} + +/* Badge styling in hero */ +.hero p a img { + margin: 0 3px; + height: 24px !important; +} + +/* ===== Feature Grid ===== */ .feature-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 1rem; - margin: 1.5rem 0; + grid-template-columns: repeat(4, 1fr); + gap: 1.25rem; + margin: 2rem 0; } .feature-card { - padding: 1rem; - border-radius: 8px; + padding: 1.5rem; + border-radius: 12px; background: var(--md-code-bg-color); border: 1px solid var(--md-default-fg-color--lightest); + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.feature-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, var(--md-primary-fg-color), #7c4dff); + opacity: 0; + transition: opacity 0.3s ease; +} + +.feature-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1); + border-color: var(--md-primary-fg-color--light); +} + +.feature-card:hover::before { + opacity: 1; } .feature-card h3 { - margin-top: 0; - font-size: 0.9rem; + margin-top: 0 !important; + margin-bottom: 0.75rem !important; + font-size: 1rem !important; + font-weight: 600; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.feature-card p { + margin: 0; + font-size: 0.875rem; + color: var(--md-default-fg-color--light); + line-height: 1.5; +} + +.feature-card a { + display: inline-block; + margin-top: 0.75rem; + font-weight: 500; + font-size: 0.875rem; +} + +/* ===== Next Steps Grid (2 columns) ===== */ +.next-steps-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.25rem; + margin: 2rem 0; +} + +.next-steps-grid .feature-card { + padding: 2rem; +} + +.next-steps-grid .feature-card h3 { + font-size: 1.1rem !important; +} + +/* ===== Dark Mode Adjustments ===== */ +[data-md-color-scheme="slate"] .hero-title { + background: linear-gradient(135deg, #7c4dff 0%, #b388ff 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +[data-md-color-scheme="slate"] .feature-card:hover { + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.3); +} + +[data-md-color-scheme="slate"] .hero .md-button--primary { + background: linear-gradient(135deg, #7c4dff 0%, #b388ff 100%); + box-shadow: 0 4px 14px rgba(124, 77, 255, 0.4); +} + +[data-md-color-scheme="slate"] .hero .md-button--primary:hover { + box-shadow: 0 6px 20px rgba(124, 77, 255, 0.5); +} + +[data-md-color-scheme="slate"] .hero .md-button:not(.md-button--primary) { + border: 2px solid rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.9); +} + +[data-md-color-scheme="slate"] .hero .md-button:not(.md-button--primary):hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.5); + color: white; + transform: translateY(-2px); +} + +/* ===== Responsive Design ===== */ +@media (max-width: 1200px) { + .feature-grid { + grid-template-columns: repeat(2, 1fr); + } } @media (max-width: 768px) { + .hero-title { + font-size: 2.5rem !important; + } + + .hero-subtitle { + font-size: 1.1rem; + } + + .feature-grid, + .next-steps-grid { + grid-template-columns: 1fr; + } + + .hero .md-button { + display: block; + margin: 0.5rem auto; + max-width: 200px; + } +} + +@media (max-width: 480px) { .hero-title { font-size: 2rem !important; } - .feature-grid { - grid-template-columns: 1fr; + .feature-card { + padding: 1.25rem; } } diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md deleted file mode 100644 index 6009e59..0000000 --- a/docs/troubleshooting.md +++ /dev/null @@ -1,159 +0,0 @@ -# Troubleshooting - -Common issues and solutions. - ---- - -## Installation Issues - -### Import Error - -``` -ModuleNotFoundError: No module named 'uniface' -``` - -**Solution:** Install the package: - -```bash -pip install uniface -``` - -### Python Version - -``` -Python 3.10+ required -``` - -**Solution:** Check your Python version: - -```bash -python --version # Should be 3.11+ -``` - ---- - -## Model Issues - -### Model Download Failed - -``` -Failed to download model -``` - -**Solution:** Manually download: - -```python -from uniface.model_store import verify_model_weights -from uniface.constants import RetinaFaceWeights - -path = verify_model_weights(RetinaFaceWeights.MNET_V2) -``` - -### Model Not Found - -**Solution:** Check cache directory: - -```bash -ls ~/.uniface/models/ -``` - ---- - -## Performance Issues - -### Slow on Mac - -**Check:** Verify ARM64 Python: - -```bash -python -c "import platform; print(platform.machine())" -# Should show: arm64 -``` - -### No GPU Acceleration - -**Check:** Verify CUDA: - -```python -import onnxruntime as ort -print(ort.get_available_providers()) -# Should include 'CUDAExecutionProvider' -``` - -**Solution:** Install GPU version: - -```bash -pip install uniface[gpu] -``` - ---- - -## Detection Issues - -### No Faces Detected - -**Try:** - -1. Lower confidence threshold: - ```python - detector = RetinaFace(confidence_threshold=0.3) - ``` - -2. Check image format (should be BGR): - ```python - image = cv2.imread("photo.jpg") # BGR format - ``` - -### Wrong Bounding Boxes - -**Check:** Image orientation. Some cameras return rotated images. - ---- - -## Recognition Issues - -### Low Similarity Scores - -**Try:** - -1. Ensure face alignment is working -2. Use higher quality images -3. Check lighting conditions - -### Different Results Each Time - -**Note:** Results should be deterministic. If not, check: - -- Image preprocessing -- Model loading - ---- - -## Memory Issues - -### Out of Memory - -**Solutions:** - -1. Process images in batches -2. Use smaller input size: - ```python - detector = RetinaFace(input_size=(320, 320)) - ``` -3. Release resources: - ```python - del detector - import gc - gc.collect() - ``` - ---- - -## Still Having Issues? - -1. Check [GitHub Issues](https://github.com/yakhyo/uniface/issues) -2. Open a new issue with: - - Python version - - UniFace version - - Error message - - Minimal code to reproduce diff --git a/examples/01_face_detection.ipynb b/examples/01_face_detection.ipynb index ef0a068..4eeb8c7 100644 --- a/examples/01_face_detection.ipynb +++ b/examples/01_face_detection.ipynb @@ -25,7 +25,14 @@ } ], "source": [ - "%pip install -q uniface" + "%pip install -q uniface\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')" ] }, { @@ -71,15 +78,7 @@ "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "✓ Model loaded (CoreML (Apple Silicon))\n" - ] - } - ], + "outputs": [], "source": [ "detector = RetinaFace(\n", " confidence_threshold=0.5,\n", diff --git a/examples/02_face_alignment.ipynb b/examples/02_face_alignment.ipynb index 0920e53..36c8342 100644 --- a/examples/02_face_alignment.ipynb +++ b/examples/02_face_alignment.ipynb @@ -29,7 +29,14 @@ } ], "source": [ - "%pip install -q uniface" + "%pip install -q uniface\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')" ] }, { @@ -76,15 +83,7 @@ "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "✓ Model loaded (CoreML (Apple Silicon))\n" - ] - } - ], + "outputs": [], "source": [ "detector = RetinaFace(\n", " confidence_threshold=0.5,\n", diff --git a/examples/03_face_verification.ipynb b/examples/03_face_verification.ipynb index b075b84..c3dc15d 100644 --- a/examples/03_face_verification.ipynb +++ b/examples/03_face_verification.ipynb @@ -25,7 +25,14 @@ } ], "source": [ - "%pip install -q uniface" + "%pip install -q uniface\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')" ] }, { @@ -66,16 +73,7 @@ "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "✓ Model loaded (CoreML (Apple Silicon))\n", - "✓ Model loaded (CoreML (Apple Silicon))\n" - ] - } - ], + "outputs": [], "source": [ "analyzer = FaceAnalyzer(\n", " detector=RetinaFace(confidence_threshold=0.5),\n", diff --git a/examples/04_face_search.ipynb b/examples/04_face_search.ipynb index cd0deb5..7d48f6f 100644 --- a/examples/04_face_search.ipynb +++ b/examples/04_face_search.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -23,7 +23,14 @@ } ], "source": [ - "%pip install -q uniface" + "%pip install -q uniface\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')" ] }, { diff --git a/examples/05_face_analyzer.ipynb b/examples/05_face_analyzer.ipynb index b58e189..22a21b0 100644 --- a/examples/05_face_analyzer.ipynb +++ b/examples/05_face_analyzer.ipynb @@ -25,7 +25,14 @@ } ], "source": [ - "%pip install -q uniface" + "%pip install -q uniface\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')" ] }, { @@ -75,17 +82,7 @@ "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "✓ Model loaded (CoreML (Apple Silicon))\n", - "✓ Model loaded (CoreML (Apple Silicon))\n", - "✓ Model loaded (CoreML (Apple Silicon))\n" - ] - } - ], + "outputs": [], "source": [ "analyzer = FaceAnalyzer(\n", " detector=RetinaFace(confidence_threshold=0.5),\n", diff --git a/examples/06_face_parsing.ipynb b/examples/06_face_parsing.ipynb index 2ba1fe3..f706699 100644 --- a/examples/06_face_parsing.ipynb +++ b/examples/06_face_parsing.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -27,7 +27,14 @@ } ], "source": [ - "%pip install -q uniface" + "%pip install -q uniface\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')" ] }, { diff --git a/examples/07_face_anonymization.ipynb b/examples/07_face_anonymization.ipynb index e2ae258..27d767c 100644 --- a/examples/07_face_anonymization.ipynb +++ b/examples/07_face_anonymization.ipynb @@ -25,7 +25,14 @@ } ], "source": [ - "%pip install -q uniface" + "%pip install -q uniface\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')" ] }, { @@ -37,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -120,13 +127,6 @@ "execution_count": 4, "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "✓ Model loaded (CoreML (Apple Silicon))\n" - ] - }, { "data": { "image/jpeg": "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", @@ -166,7 +166,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "✓ Model loaded (CoreML (Apple Silicon))\n", "Detected 27 faces\n", "\\nGAUSSIAN:\n" ] diff --git a/examples/08_gaze_estimation.ipynb b/examples/08_gaze_estimation.ipynb index f5deb46..e538a3f 100644 --- a/examples/08_gaze_estimation.ipynb +++ b/examples/08_gaze_estimation.ipynb @@ -25,7 +25,14 @@ } ], "source": [ - "%pip install -q uniface" + "%pip install -q uniface\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')" ] }, { @@ -74,16 +81,7 @@ "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "✓ Model loaded (CoreML (Apple Silicon))\n", - "✓ Model loaded (CoreML (Apple Silicon))\n" - ] - } - ], + "outputs": [], "source": [ "# Initialize face detector\n", "detector = RetinaFace(confidence_threshold=0.5)\n", @@ -103,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -156,14 +154,14 @@ " face_crop = image[y1:y2, x1:x2]\n", "\n", " if face_crop.size > 0:\n", - " pitch, yaw = gaze_estimator.estimate(face_crop)\n", - " pitch_deg = np.degrees(pitch)\n", - " yaw_deg = np.degrees(yaw)\n", + " gaze = gaze_estimator.estimate(face_crop)\n", + " pitch_deg = np.degrees(gaze.pitch)\n", + " yaw_deg = np.degrees(gaze.yaw)\n", "\n", " print(f' Face {i+1}: pitch={pitch_deg:.1f}°, yaw={yaw_deg:.1f}°')\n", "\n", " # Draw gaze without angle text\n", - " draw_gaze(image, face.bbox, pitch, yaw, draw_angles=False)\n", + " draw_gaze(image, face.bbox, gaze.pitch, gaze.yaw, draw_angles=False)\n", "\n", " # Convert BGR to RGB for display\n", " original_rgb = cv2.cvtColor(original, cv2.COLOR_BGR2RGB)\n", @@ -234,7 +232,7 @@ "## Notes\n", "\n", "- **Input**: Gaze estimation requires a face crop (obtained from face detection)\n", - "- **Output**: Returns (pitch, yaw) angles in radians\n", + "- **Output**: Returns a `GazeResult` object with `pitch` and `yaw` attributes (angles in radians)\n", "- **Visualization**: `draw_gaze()` automatically draws bounding box and gaze arrow\n", "- **Models**: Trained on Gaze360 dataset with diverse head poses\n", "- **Performance**: MAE (Mean Absolute Error) ranges from 11-13 degrees\n", diff --git a/mkdocs.yml b/mkdocs.yml index b55d392..bfed697 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,28 +11,31 @@ copyright: Copyright © 2025 Yakhyokhuja Valikhujaev theme: name: material + custom_dir: docs/overrides palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/link + name: Switch to light mode - media: "(prefers-color-scheme: light)" scheme: default - primary: custom - accent: custom + primary: indigo + accent: indigo toggle: - icon: material/brightness-7 + icon: material/toggle-switch name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate - primary: custom - accent: custom + primary: black + accent: indigo toggle: - icon: material/brightness-4 - name: Switch to light mode + icon: material/toggle-switch-off-outline + name: Switch to system preference font: text: Roboto code: Roboto Mono features: - navigation.tabs - - navigation.sections - - navigation.path - navigation.top - navigation.footer - navigation.indexes @@ -43,11 +46,13 @@ theme: - content.code.copy - content.code.annotate - content.action.edit + - content.action.view - content.tabs.link - toc.follow + icon: logo: material/book-open-page-variant - repo: fontawesome/brands/github + repo: fontawesome/brands/git-alt admonition: note: octicons/tag-16 abstract: octicons/checklist-16 @@ -68,8 +73,8 @@ extra: link: https://github.com/yakhyo - icon: fontawesome/brands/python link: https://pypi.org/project/uniface/ - version: - provider: mike + - icon: fontawesome/brands/x-twitter + link: https://x.com/y_valikhujaev analytics: provider: google property: G-XXXXXXXXXX @@ -85,7 +90,7 @@ markdown_extensions: - def_list - tables - toc: - permalink: true + permalink: false toc_depth: 3 - pymdownx.superfences: custom_fences: @@ -114,20 +119,28 @@ markdown_extensions: plugins: - search + - git-committers: + repository: yakhyo/uniface + branch: main + token: !ENV MKDOCS_GIT_COMMITTERS_APIKEY + - git-revision-date-localized: + enable_creation_date: true + type: timeago nav: - Home: index.md - - Getting started: + - Getting Started: - Installation: installation.md - Quickstart: quickstart.md - - Concepts: - - Overview: concepts/overview.md - - Inputs & Outputs: concepts/inputs-outputs.md - - Coordinate Systems: concepts/coordinate-systems.md - - Execution Providers: concepts/execution-providers.md - - Model Cache: concepts/model-cache-offline.md - - Thresholds: concepts/thresholds-calibration.md - - API: + - Model Zoo: models.md + - Tutorials: + - Image Pipeline: recipes/image-pipeline.md + - Video & Webcam: recipes/video-webcam.md + - Face Search: recipes/face-search.md + - Batch Processing: recipes/batch-processing.md + - Anonymize Stream: recipes/anonymize-stream.md + - Custom Models: recipes/custom-models.md + - API Reference: - Detection: modules/detection.md - Recognition: modules/recognition.md - Landmarks: modules/landmarks.md @@ -136,17 +149,15 @@ nav: - Gaze: modules/gaze.md - Anti-Spoofing: modules/spoofing.md - Privacy: modules/privacy.md - - Examples: - - Image Pipeline: recipes/image-pipeline.md - - Batch Processing: recipes/batch-processing.md - - Video & Webcam: recipes/video-webcam.md - - Face Search: recipes/face-search.md - - Anonymize Stream: recipes/anonymize-stream.md - - Custom Models: recipes/custom-models.md - - Reference: - - API Reference: api/reference.md - - Troubleshooting: troubleshooting.md - - FAQ: faq.md - - Changelog: changelog.md + - Guides: + - Overview: concepts/overview.md + - Inputs & Outputs: concepts/inputs-outputs.md + - Coordinate Systems: concepts/coordinate-systems.md + - Execution Providers: concepts/execution-providers.md + - Model Cache: concepts/model-cache-offline.md + - Thresholds: concepts/thresholds-calibration.md + - Resources: - Contributing: contributing.md - License: license-attribution.md + - Releases: https://github.com/yakhyo/uniface/releases + - Discussions: https://github.com/yakhyo/uniface/discussions