From da8a5cf35b8418d1ca9f8efaf5201da342efa5e2 Mon Sep 17 00:00:00 2001 From: yakhyo Date: Thu, 11 Dec 2025 01:02:18 +0900 Subject: [PATCH] feat: Add yolov5n, update docs and ruff code format --- CONTRIBUTING.md | 1 + MODELS.md | 16 ++++++++++++---- README.md | 3 ++- pyproject.toml | 2 +- scripts/batch_process.py | 4 +++- scripts/run_age_gender.py | 4 +++- scripts/run_detection.py | 12 ++++++++++-- scripts/run_emotion.py | 4 +++- scripts/run_face_analyzer.py | 2 +- scripts/run_video_detection.py | 4 +++- uniface/__init__.py | 2 +- uniface/constants.py | 12 ++++++++---- uniface/face.py | 1 + 13 files changed, 49 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 122640a..c708035 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,3 +57,4 @@ Example notebooks demonstrating library usage: Open an issue or start a discussion on GitHub. + diff --git a/MODELS.md b/MODELS.md index 3d4db9b..93583f1 100644 --- a/MODELS.md +++ b/MODELS.md @@ -80,10 +80,11 @@ detector = SCRFD( YOLOv5-Face models provide excellent detection accuracy with 5-point facial landmarks, optimized for real-time applications. -| Model Name | Params | Size | Easy | Medium | Hard | FLOPs (G) | Use Case | -| -------------- | ------ | ---- | ------ | ------ | ------ | --------- | ------------------------------ | -| `YOLOV5S` ⭐ | 7.1M | 28MB | 94.33% | 92.61% | 83.15% | 5.751 | **Real-time + accuracy** | -| `YOLOV5M` | 21.1M | 84MB | 95.30% | 93.76% | 85.28% | 18.146 | High accuracy | +| 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 `scripts/run_detection.py --iterations 100` @@ -95,6 +96,13 @@ YOLOv5-Face models provide excellent detection accuracy with 5-point facial land from uniface import YOLOv5Face from uniface.constants import YOLOv5FaceWeights +# Lightweight/Mobile +detector = YOLOv5Face( + model_name=YOLOv5FaceWeights.YOLOV5N, + conf_thresh=0.6, + nms_thresh=0.5 +) + # Real-time detection (recommended) detector = YOLOv5Face( model_name=YOLOv5FaceWeights.YOLOV5S, diff --git a/README.md b/README.md index a4cd70f..14f7fcf 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,7 @@ faces = detect_faces(image, method='retinaface', conf_thresh=0.8) # methods: re | -------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | | `RetinaFace` | `model_name=RetinaFaceWeights.MNET_V2`, `conf_thresh=0.5`, `nms_thresh=0.4`, `input_size=(640, 640)`, `dynamic_size=False` | Supports 5-point landmarks | | `SCRFD` | `model_name=SCRFDWeights.SCRFD_10G_KPS`, `conf_thresh=0.5`, `nms_thresh=0.4`, `input_size=(640, 640)` | Supports 5-point landmarks | -| `YOLOv5Face` | `model_name=YOLOv5FaceWeights.YOLOV5S`, `conf_thresh=0.6`, `nms_thresh=0.5`, `input_size=640` (fixed) | Landmarks supported;`input_size` must be 640 | +| `YOLOv5Face` | `model_name=YOLOv5FaceWeights.YOLOV5S`, `conf_thresh=0.6`, `nms_thresh=0.5`, `input_size=640` (fixed) | Supports 5-point landmarks; models: YOLOV5N/S/M; `input_size` must be 640 | **Recognition** @@ -265,6 +265,7 @@ faces = detect_faces(image, method='retinaface', conf_thresh=0.8) # methods: re | 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 | diff --git a/pyproject.toml b/pyproject.toml index 1848579..dfa57f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uniface" -version = "1.3.1" +version = "1.3.2" description = "UniFace: A Comprehensive Library for Face Detection, Recognition, Landmark Analysis, Age, and Gender Detection" readme = "README.md" license = { text = "MIT" } diff --git a/scripts/batch_process.py b/scripts/batch_process.py index 3db9c3e..da46748 100644 --- a/scripts/batch_process.py +++ b/scripts/batch_process.py @@ -31,7 +31,9 @@ def process_image(detector, image_path: Path, output_path: Path, threshold: floa bboxes = [f['bbox'] for f in faces] scores = [f['confidence'] for f in faces] landmarks = [f['landmarks'] for f in faces] - draw_detections(image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True) + draw_detections( + image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True + ) cv2.putText( image, diff --git a/scripts/run_age_gender.py b/scripts/run_age_gender.py index af6ff59..a7cd9fd 100644 --- a/scripts/run_age_gender.py +++ b/scripts/run_age_gender.py @@ -43,7 +43,9 @@ def process_image( bboxes = [f['bbox'] for f in faces] scores = [f['confidence'] for f in faces] landmarks = [f['landmarks'] for f in faces] - draw_detections(image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True) + draw_detections( + image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True + ) for i, face in enumerate(faces): gender_id, age = age_gender.predict(image, face['bbox']) diff --git a/scripts/run_detection.py b/scripts/run_detection.py index 3d30d81..9754ad4 100644 --- a/scripts/run_detection.py +++ b/scripts/run_detection.py @@ -51,7 +51,15 @@ def run_webcam(detector, threshold: float = 0.6): 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=threshold, draw_score=True, fancy_bbox=True) + draw_detections( + image=frame, + bboxes=bboxes, + scores=scores, + landmarks=landmarks, + vis_threshold=threshold, + draw_score=True, + fancy_bbox=True, + ) cv2.putText( frame, @@ -90,7 +98,7 @@ def main(): else: from uniface.constants import YOLOv5FaceWeights - detector = YOLOv5Face(model_name=YOLOv5FaceWeights.YOLOV5M) + detector = YOLOv5Face(model_name=YOLOv5FaceWeights.YOLOV5N) if args.webcam: run_webcam(detector, args.threshold) diff --git a/scripts/run_emotion.py b/scripts/run_emotion.py index 50d3d6e..cf4d41c 100644 --- a/scripts/run_emotion.py +++ b/scripts/run_emotion.py @@ -42,7 +42,9 @@ def process_image( bboxes = [f['bbox'] for f in faces] scores = [f['confidence'] for f in faces] landmarks = [f['landmarks'] for f in faces] - draw_detections(image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True) + draw_detections( + image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True + ) for i, face in enumerate(faces): emotion, confidence = emotion_predictor.predict(image, face['landmarks']) diff --git a/scripts/run_face_analyzer.py b/scripts/run_face_analyzer.py index 72486c2..fabe6e4 100644 --- a/scripts/run_face_analyzer.py +++ b/scripts/run_face_analyzer.py @@ -82,7 +82,7 @@ def process_image(analyzer, image_path: str, save_dir: str = 'outputs', show_sim bboxes = [f.bbox for f in faces] scores = [f.confidence for f in faces] landmarks = [f.landmarks for f in faces] - draw_detections(image=image, bboxes=bboxes, scores=scores, landmarks=landmarks,fancy_bbox=True) + draw_detections(image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, fancy_bbox=True) for i, face in enumerate(faces, 1): draw_face_info(image, face, i) diff --git a/scripts/run_video_detection.py b/scripts/run_video_detection.py index b1c6796..01efbd9 100644 --- a/scripts/run_video_detection.py +++ b/scripts/run_video_detection.py @@ -55,7 +55,9 @@ def process_video( 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=threshold, fancy_bbox=True) + draw_detections( + image=frame, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True + ) cv2.putText( frame, diff --git a/uniface/__init__.py b/uniface/__init__.py index 947e048..7f54b55 100644 --- a/uniface/__init__.py +++ b/uniface/__init__.py @@ -13,7 +13,7 @@ __license__ = 'MIT' __author__ = 'Yakhyokhuja Valikhujaev' -__version__ = '1.3.1' +__version__ = '1.3.2' from uniface.face_utils import compute_similarity, face_alignment diff --git a/uniface/constants.py b/uniface/constants.py index 586ce9e..c467963 100644 --- a/uniface/constants.py +++ b/uniface/constants.py @@ -62,11 +62,13 @@ class YOLOv5FaceWeights(str, Enum): Exported to ONNX from: https://github.com/yakhyo/yolov5-face-onnx-inference Model Performance (WIDER FACE): - - YOLOV5S: 7.1M params, 28MB, 94.33% Easy / 92.61% Medium / 83.15% Hard - - YOLOV5M: 21.1M params, 84MB, 95.30% Easy / 93.76% Medium / 85.28% Hard + - YOLOV5N: 11MB, 93.61% Easy / 91.52% Medium / 80.53% Hard + - YOLOV5S: 28MB, 94.33% Easy / 92.61% Medium / 83.15% Hard + - YOLOV5M: 82MB, 95.30% Easy / 93.76% Medium / 85.28% Hard """ - YOLOV5S = "yolov5s_face" - YOLOV5M = "yolov5m_face" + YOLOV5N = "yolov5n" + YOLOV5S = "yolov5s" + YOLOV5M = "yolov5m" class DDAMFNWeights(str, Enum): @@ -117,6 +119,7 @@ MODEL_URLS: Dict[Enum, str] = { SCRFDWeights.SCRFD_10G_KPS: 'https://github.com/yakhyo/uniface/releases/download/weights/scrfd_10g_kps.onnx', SCRFDWeights.SCRFD_500M_KPS: 'https://github.com/yakhyo/uniface/releases/download/weights/scrfd_500m_kps.onnx', # YOLOv5-Face + YOLOv5FaceWeights.YOLOV5N: 'https://github.com/yakhyo/yolov5-face-onnx-inference/releases/download/weights/yolov5n_face.onnx', YOLOv5FaceWeights.YOLOV5S: 'https://github.com/yakhyo/yolov5-face-onnx-inference/releases/download/weights/yolov5s_face.onnx', YOLOv5FaceWeights.YOLOV5M: 'https://github.com/yakhyo/yolov5-face-onnx-inference/releases/download/weights/yolov5m_face.onnx', # DDAFM @@ -151,6 +154,7 @@ MODEL_SHA256: Dict[Enum, str] = { SCRFDWeights.SCRFD_10G_KPS: '5838f7fe053675b1c7a08b633df49e7af5495cee0493c7dcf6697200b85b5b91', SCRFDWeights.SCRFD_500M_KPS: '5e4447f50245bbd7966bd6c0fa52938c61474a04ec7def48753668a9d8b4ea3a', # YOLOv5-Face + YOLOv5FaceWeights.YOLOV5N: 'eb244a06e36999db732b317c2b30fa113cd6cfc1a397eaf738f2d6f33c01f640', YOLOv5FaceWeights.YOLOV5S: 'fc682801cd5880e1e296184a14aea0035486b5146ec1a1389d2e7149cb134bb2', YOLOv5FaceWeights.YOLOV5M: '04302ce27a15bde3e20945691b688e2dd018a10e92dd8932146bede6a49207b2', # DDAFM diff --git a/uniface/face.py b/uniface/face.py index 42a4f47..6807773 100644 --- a/uniface/face.py +++ b/uniface/face.py @@ -17,6 +17,7 @@ class Face: """ Detected face with analysis results. """ + # Required attributes bbox: np.ndarray confidence: float