Merge pull request #5 from yakhyo/feat-inv
add inverse matrix for face alignment to rotate back
2
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
26
README.md
@@ -1,6 +1,5 @@
|
||||
# UniFace: All-in-One Face Analysis Library
|
||||
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||

|
||||
[](https://pypi.org/project/uniface/)
|
||||
@@ -10,21 +9,20 @@
|
||||
[](https://www.python.org/dev/peps/pep-0008/)
|
||||
[](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
|
After Width: | Height: | Size: 996 KiB |
BIN
assets/test_images/image0.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/test_images/image1.jpg
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
assets/test_images/image2.jpg
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
assets/test_images/image3.jpg
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
assets/test_images/image4.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/test_result.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
4
setup.py
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2024 Yakhyokhuja Valikhujaev
|
||||
# Copyright 2025 Yakhyokhuja Valikhujaev
|
||||
# Author: Yakhyokhuja Valikhujaev
|
||||
# GitHub: https://github.com/yakhyo
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2024 Yakhyokhuja Valikhujaev
|
||||
# Copyright 2025 Yakhyokhuja Valikhujaev
|
||||
# Author: Yakhyokhuja Valikhujaev
|
||||
# GitHub: https://github.com/yakhyo
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2024 Yakhyokhuja Valikhujaev
|
||||
# Copyright 2025 Yakhyokhuja Valikhujaev
|
||||
# Author: Yakhyokhuja Valikhujaev
|
||||
# GitHub: https://github.com/yakhyo
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2024 Yakhyokhuja Valikhujaev
|
||||
# Copyright 2025 Yakhyokhuja Valikhujaev
|
||||
# Author: Yakhyokhuja Valikhujaev
|
||||
# GitHub: https://github.com/yakhyo
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||