Merge pull request #5 from yakhyo/feat-inv

add inverse matrix for face alignment to rotate back
This commit is contained in:
Yakhyokhuja Valikhujaev
2025-03-13 23:36:57 +09:00
committed by GitHub
20 changed files with 195 additions and 84 deletions

View File

@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- name: Checkout code

View File

@@ -1,6 +1,5 @@
# UniFace: All-in-One Face Analysis Library
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
![Python](https://img.shields.io/badge/Python-3.8%2B-blue)
[![PyPI Version](https://img.shields.io/pypi/v/uniface.svg)](https://pypi.org/project/uniface/)
@@ -10,21 +9,20 @@
[![Code Style: PEP8](https://img.shields.io/badge/code%20style-PEP8-green.svg)](https://www.python.org/dev/peps/pep-0008/)
[![GitHub Release Downloads](https://img.shields.io/github/downloads/yakhyo/uniface/total.svg?label=Model%20Downloads)](https://github.com/yakhyo/uniface/releases)
**uniface** is a lightweight face detection library designed for high-performance face localization, landmark detection and face alignment. The library supports ONNX models and provides utilities for bounding box visualization and landmark plotting. To train RetinaFace model, see https://github.com/yakhyo/retinaface-pytorch.
---
## Features
| Date | Feature Description |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Planned | 🎭 **Age and Gender Detection**: Planned feature for predicting age and gender from facial images. |
| Planned | 🧩 **Face Recognition**: Upcoming capability to identify and verify faces. |
| 2024-11-21 | 🔄 **Face Alignment**: Added precise face alignment for better downstream tasks. |
| 2024-11-20 | ⚡ **High-Speed Face Detection**: ONNX model integration for faster and efficient face detection. |
| 2024-11-20 | 🎯 **Facial Landmark Localization**: Accurate detection of key facial features like eyes, nose, and mouth. |
| 2024-11-20 | 🛠 **API for Inference and Visualization**: Simplified API for seamless inference and visual results generation. |
| Date | Feature Description |
| ---------- | --------------------------------------------------------------------------------------------------------------- |
| Planned | 🎭 **Age and Gender Detection**: Planned feature for predicting age and gender from facial images. |
| Planned | 🧩 **Face Recognition**: Upcoming capability to identify and verify faces. |
| 2024-11-21 | 🔄 **Face Alignment**: Added precise face alignment for better downstream tasks. |
| 2024-11-20 | ⚡ **High-Speed Face Detection**: ONNX model integration for faster and efficient face detection. |
| 2024-11-20 | 🎯 **Facial Landmark Localization**: Accurate detection of key facial features like eyes, nose, and mouth. |
| 2024-11-20 | 🛠 **API for Inference and Visualization**: Simplified API for seamless inference and visual results generation. |
---
@@ -55,6 +53,10 @@ It demonstrates how to initialize the model, run inference, and visualize the re
## Examples
<div align="center">
<img src="assets/alignment_result.png">
</div>
Explore the following example notebooks to learn how to use **UniFace** effectively:
- [Face Detection](examples/face_detection.ipynb): Demonstrates how to perform face detection, draw bounding boxes, and landmarks on an image.
@@ -153,6 +155,10 @@ cv2.destroyAllWindows()
| retinaface_r18 | 92.50% | 91.02% | 86.63% |
| retinaface_r34 | **94.16%** | **93.12%** | **88.90%** |
<div align="center">
<img src="assets/test_result.png">
</div>
## API Reference
### `RetinaFace` Class

BIN
assets/alignment_result.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/test_result.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,7 @@ if os.path.exists("README.md"):
setup(
name="uniface",
version="0.1.5",
version="0.1.6",
packages=find_packages(),
install_requires=[
"numpy",
@@ -35,6 +35,8 @@ setup(
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",

View File

@@ -1,4 +1,4 @@
# Copyright 2024 Yakhyokhuja Valikhujaev
# Copyright 2025 Yakhyokhuja Valikhujaev
#
# Licensed under the MIT License.
# You may obtain a copy of the License at
@@ -11,17 +11,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__license__ = "MIT"
__author__ = "Yakhyokhuja Valikhujaev"
__version__ = "0.1.6"
from uniface.retinaface import RetinaFace
from uniface.log import Logger
from uniface.model_store import verify_model_weights
from uniface.version import __version__, __author__
from uniface.alignment import face_alignment
from uniface.visualization import draw_detections
__all__ = [
"__version__",
"__author__"
"__author__",
"__license__",
"RetinaFace",
"Logger",
"verify_model_weights",

View File

@@ -1,4 +1,4 @@
# Copyright 2024 Yakhyokhuja Valikhujaev
# Copyright 2025 Yakhyokhuja Valikhujaev
# Author: Yakhyokhuja Valikhujaev
# GitHub: https://github.com/yakhyo
@@ -20,7 +20,7 @@ reference_alignment: np.ndarray = np.array(
)
def estimate_norm(landmark: np.ndarray, image_size: int = 112) -> np.ndarray:
def estimate_norm(landmark: np.ndarray, image_size: int = 112) -> Tuple[np.ndarray, np.ndarray]:
"""
Estimate the normalization transformation matrix for facial landmarks.
@@ -30,6 +30,7 @@ def estimate_norm(landmark: np.ndarray, image_size: int = 112) -> np.ndarray:
Returns:
np.ndarray: The 2x3 transformation matrix for aligning the landmarks.
np.ndarray: The 2x3 inverse transformation matrix for aligning the landmarks.
Raises:
AssertionError: If the input landmark array does not have the shape (5, 2)
@@ -52,11 +53,14 @@ def estimate_norm(landmark: np.ndarray, image_size: int = 112) -> np.ndarray:
# Compute the transformation matrix
transform = SimilarityTransform()
transform.estimate(landmark, alignment)
matrix = transform.params[0:2, :]
return matrix
inverse_matrix = np.linalg.inv(transform.params)[0:2, :]
return matrix, inverse_matrix
def face_alignment(image: np.ndarray, landmark: np.ndarray, image_size: int = 112) -> np.ndarray:
def face_alignment(image: np.ndarray, landmark: np.ndarray, image_size: int = 112) -> Tuple[np.ndarray, np.ndarray]:
"""
Align the face in the input image based on the given facial landmarks.
@@ -67,9 +71,12 @@ def face_alignment(image: np.ndarray, landmark: np.ndarray, image_size: int = 11
Returns:
np.ndarray: The aligned face as a NumPy array.
np.ndarray: The 2x3 transformation matrix used for alignment.
"""
# Get the transformation matrix
M = estimate_norm(landmark, image_size)
M, M_inv = estimate_norm(landmark, image_size)
# Warp the input image to align the face
warped = cv2.warpAffine(image, M, (image_size, image_size), borderValue=0.0)
return warped
return warped, M_inv

View File

@@ -1,4 +1,4 @@
# Copyright 2024 Yakhyokhuja Valikhujaev
# Copyright 2025 Yakhyokhuja Valikhujaev
# Author: Yakhyokhuja Valikhujaev
# GitHub: https://github.com/yakhyo

View File

@@ -1,4 +1,4 @@
# Copyright 2024 Yakhyokhuja Valikhujaev
# Copyright 2025 Yakhyokhuja Valikhujaev
# Author: Yakhyokhuja Valikhujaev
# GitHub: https://github.com/yakhyo

View File

@@ -1,4 +1,4 @@
# Copyright 2024 Yakhyokhuja Valikhujaev
# Copyright 2025 Yakhyokhuja Valikhujaev
# Author: Yakhyokhuja Valikhujaev
# GitHub: https://github.com/yakhyo

View File

@@ -1,4 +1,4 @@
# Copyright 2024 Yakhyokhuja Valikhujaev
# Copyright 2025 Yakhyokhuja Valikhujaev
# Author: Yakhyokhuja Valikhujaev
# GitHub: https://github.com/yakhyo

View File

@@ -1,15 +0,0 @@
# Copyright 2024 Yakhyokhuja Valikhujaev
#
# Licensed under the MIT License.
# You may obtain a copy of the License at
#
# https://opensource.org/licenses/MIT
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.1.5"
__author__ = "Yakhyokhuja Valikhujaev"

View File

@@ -1,4 +1,4 @@
# Copyright 2024 Yakhyokhuja Valikhujaev
# Copyright 2025 Yakhyokhuja Valikhujaev
# Author: Yakhyokhuja Valikhujaev
# GitHub: https://github.com/yakhyo
@@ -8,7 +8,7 @@ import numpy as np
def draw_detections(image, detections, vis_threshold: float = 0.6):
"""
Draw bounding boxes and landmarks on the image.
Draw bounding boxes and landmarks on the image with thickness scaled by bbox size.
Args:
image (ndarray): Image to draw detections on.
@@ -30,7 +30,16 @@ def draw_detections(image, detections, vis_threshold: float = 0.6):
# Draw bounding boxes, scores, and landmarks
for box, score, landmark in zip(boxes, scores, landmarks):
cv2.rectangle(image, box[:2], box[2:], (0, 0, 255), 2)
cv2.putText(image, f"{score:.2f}", (box[0], box[1] + 12), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
# Calculate thickness proportional to the bbox size
thickness = max(1, int(min(box[2] - box[0], box[3] - box[1]) / 100))
# Draw rectangle
cv2.rectangle(image, tuple(box[:2]), tuple(box[2:]), (0, 0, 255), thickness)
# Draw score
cv2.putText(image, f"{score:.2f}", (box[0], box[1] + 12),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), thickness)
# Draw landmarks
for point, color in zip(landmark, _colors):
cv2.circle(image, tuple(point), 2, color, -1)
cv2.circle(image, tuple(point), thickness, color, -1)