Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE]: adding yolov11 into face detection portfolio #1397

Merged
merged 12 commits into from
Dec 11, 2024
Merged
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ backends = [
'retinaface',
'mediapipe',
'yolov8',
'yolov11n',
serengil marked this conversation as resolved.
Show resolved Hide resolved
'yolov11m',
'yunet',
'centerface',
]
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/Evaluate-Results.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"outputs": [],
"source": [
"alignment = [False, True]\n",
"models = [\"Facenet512\", \"Facenet\", \"VGG-Face\", \"ArcFace\", \"Dlib\", \"GhostFaceNet\", \"SFace\", \"OpenFace\", \"DeepFace\", \"DeepID\"]\n",
"detectors = [\"retinaface\", \"mtcnn\", \"fastmtcnn\", \"dlib\", \"yolov8\", \"yunet\", \"centerface\", \"mediapipe\", \"ssd\", \"opencv\", \"skip\"]\n",
"models = [\"Facenet512\", \"Facenet\", \"VGG-Face\", \"ArcFace\", \"Dlib\", \"GhostFaceNet\", \"SFace\", \"OpenFace\", \"DeepFace\", \"DeepID\", \"yolov8\", \"yolov11n\", \"yolov11s\", \"yolov11m\"]\n",
serengil marked this conversation as resolved.
Show resolved Hide resolved
"detectors = [\"retinaface\", \"mtcnn\", \"fastmtcnn\", \"dlib\", \"yolov8\", \"yolov11n\", \"yolov11s\", \"yolov11m\", \"yunet\", \"centerface\", \"mediapipe\", \"ssd\", \"opencv\", \"skip\"]\n",
"distance_metrics = [\"euclidean\", \"euclidean_l2\", \"cosine\"]"
]
},
Expand Down
16 changes: 8 additions & 8 deletions deepface/DeepFace.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def build_model(model_name: str, task: str = "facial_recognition") -> Any:
- VGG-Face, Facenet, Facenet512, OpenFace, DeepFace, DeepID, Dlib,
serengil marked this conversation as resolved.
Show resolved Hide resolved
ArcFace, SFace, GhostFaceNet for face recognition
- Age, Gender, Emotion, Race for facial attributes
- opencv, mtcnn, ssd, dlib, retinaface, mediapipe, yolov8, yunet,
- opencv, mtcnn, ssd, dlib, retinaface, mediapipe, yolov8, 'yolov11n', 'yolov11s','yolov11m', yunet,
fastmtcnn or centerface for face detectors
- Fasnet for spoofing
task (str): facial_recognition, facial_attribute, face_detector, spoofing
Expand Down Expand Up @@ -96,7 +96,7 @@ def verify(
OpenFace, DeepFace, DeepID, Dlib, ArcFace, SFace and GhostFaceNet (default is VGG-Face).
serengil marked this conversation as resolved.
Show resolved Hide resolved

detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'centerface' or 'skip'
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', 'centerface' or 'skip'
(default is opencv).

distance_metric (string): Metric for measuring similarity. Options: 'cosine',
Expand Down Expand Up @@ -187,7 +187,7 @@ def analyze(
Set to False to avoid the exception for low-resolution images (default is True).

detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'centerface' or 'skip'
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', 'centerface' or 'skip'
(default is opencv).

distance_metric (string): Metric for measuring similarity. Options: 'cosine',
Expand Down Expand Up @@ -298,7 +298,7 @@ def find(
Set to False to avoid the exception for low-resolution images (default is True).

serengil marked this conversation as resolved.
Show resolved Hide resolved
detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'centerface' or 'skip'
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', 'centerface' or 'skip'
(default is opencv).

align (boolean): Perform alignment based on the eye positions (default is True).
Expand Down Expand Up @@ -396,7 +396,7 @@ def represent(
(default is True).

serengil marked this conversation as resolved.
Show resolved Hide resolved
detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'centerface' or 'skip'
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', 'centerface' or 'skip'
(default is opencv).

align (boolean): Perform alignment based on the eye positions (default is True).
Expand Down Expand Up @@ -462,7 +462,7 @@ def stream(
OpenFace, DeepFace, DeepID, Dlib, ArcFace, SFace and GhostFaceNet (default is VGG-Face).

serengil marked this conversation as resolved.
Show resolved Hide resolved
detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'centerface' or 'skip'
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', 'centerface' or 'skip'
(default is opencv).

distance_metric (string): Metric for measuring similarity. Options: 'cosine',
Expand Down Expand Up @@ -517,7 +517,7 @@ def extract_faces(
as a string, numpy array (BGR), or base64 encoded images.

detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'centerface' or 'skip'
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', 'centerface' or 'skip'
(default is opencv).

enforce_detection (boolean): If no face is detected in an image, raise an exception.
Expand Down Expand Up @@ -601,7 +601,7 @@ def detectFace(
added to resize the image (default is (224, 224)).

detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'centerface' or 'skip'
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', 'centerface' or 'skip'
(default is opencv).

enforce_detection (boolean): If no face is detected in an image, raise an exception.
Expand Down
23 changes: 18 additions & 5 deletions deepface/commons/weight_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ def download_all_models_in_one_shot() -> None:
MODEL_URL as SSD_MODEL,
WEIGHTS_URL as SSD_WEIGHTS,
)
from deepface.models.face_detection.Yolo import (
WEIGHT_URL as YOLOV8_WEIGHTS,
WEIGHT_NAME as YOLOV8_WEIGHT_NAME,
from deepface.models.YoloModel import (
WEIGHT_URLS as YOLO_WEIGHTS,
WEIGHT_NAMES as YOLO_WEIGHT_NAMES,
YoloModel
)
from deepface.models.face_detection.YuNet import WEIGHTS_URL as YUNET_WEIGHTS
from deepface.models.face_detection.Dlib import WEIGHTS_URL as DLIB_FD_WEIGHTS
Expand Down Expand Up @@ -162,8 +163,20 @@ def download_all_models_in_one_shot() -> None:
SSD_MODEL,
SSD_WEIGHTS,
{
"filename": YOLOV8_WEIGHT_NAME,
"url": YOLOV8_WEIGHTS,
"filename": YOLO_WEIGHT_NAMES[YoloModel.V8N.value],
"url": YOLO_WEIGHTS[YoloModel.V8N.value],
},
{
"filename": YOLO_WEIGHT_NAMES[YoloModel.V11N.value],
"url": YOLO_WEIGHTS[YoloModel.V11N.value],
},
{
"filename": YOLO_WEIGHT_NAMES[YoloModel.V11S.value],
"url": YOLO_WEIGHTS[YoloModel.V11S.value],
},
{
"filename": YOLO_WEIGHT_NAMES[YoloModel.V11M.value],
"url": YOLO_WEIGHTS[YoloModel.V11M.value],
},
YUNET_WEIGHTS,
DLIB_FD_WEIGHTS,
Expand Down
37 changes: 37 additions & 0 deletions deepface/models/YoloClientBase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# built-in dependencies
from typing import Any

# project dependencies
from deepface.models.YoloModel import YoloModel, WEIGHT_URLS, WEIGHT_NAMES
from deepface.commons import weight_utils
from deepface.commons.logger import Logger

logger = Logger()


class YoloClientBase:
def __init__(self, model: YoloModel):
self.model = self.build_model(model)

def build_model(self, model: YoloModel) -> Any:
"""
Build a yolo detector model
Returns:
model (Any)
"""

# Import the optional Ultralytics YOLO model
try:
from ultralytics import YOLO
except ModuleNotFoundError as e:
raise ImportError(
"Yolo is an optional detector, ensure the library is installed. "
"Please install using 'pip install ultralytics'"
) from e

weight_file = weight_utils.download_weights_if_necessary(
file_name=WEIGHT_NAMES[model.value], source_url=WEIGHT_URLS[model.value]
)

# Return face_detector
return YOLO(weight_file)
21 changes: 21 additions & 0 deletions deepface/models/YoloModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from enum import Enum
serengil marked this conversation as resolved.
Show resolved Hide resolved


class YoloModel(Enum):
V8N = 0
V11N = 1
V11S = 2
V11M = 3


# Model's weights paths
WEIGHT_NAMES = ["yolov8n-face.pt",
"yolov11n-face.pt",
"yolov11s-face.pt",
"yolov11m-face.pt"]

# Google Drive URL from repo (https://github.com/derronqi/yolov8-face) ~6MB
WEIGHT_URLS = ["https://drive.google.com/uc?id=1qcr9DbgsX3ryrz2uU8w4Xm3cOrRywXqb",
"https://github.com/akanametov/yolo-face/releases/download/v0.0.0/yolov11n-face.pt",
"https://github.com/akanametov/yolo-face/releases/download/v0.0.0/yolov11s-face.pt",
"https://github.com/akanametov/yolo-face/releases/download/v0.0.0/yolov11m-face.pt"]
79 changes: 37 additions & 42 deletions deepface/models/face_detection/Yolo.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,22 @@
# built-in dependencies
import os
from typing import Any, List
from typing import List

# 3rd party dependencies
import numpy as np

# project dependencies
from deepface.models.YoloClientBase import YoloClientBase
from deepface.models.YoloModel import YoloModel
from deepface.models.Detector import Detector, FacialAreaRegion
from deepface.commons import weight_utils
from deepface.commons.logger import Logger

logger = Logger()

# Model's weights paths
WEIGHT_NAME = "yolov8n-face.pt"

# Google Drive URL from repo (https://github.com/derronqi/yolov8-face) ~6MB
WEIGHT_URL = "https://drive.google.com/uc?id=1qcr9DbgsX3ryrz2uU8w4Xm3cOrRywXqb"


class YoloClient(Detector):
def __init__(self):
self.model = self.build_model()

def build_model(self) -> Any:
"""
Build a yolo detector model
Returns:
model (Any)
"""

# Import the optional Ultralytics YOLO model
try:
from ultralytics import YOLO
except ModuleNotFoundError as e:
raise ImportError(
"Yolo is an optional detector, ensure the library is installed. "
"Please install using 'pip install ultralytics'"
) from e

weight_file = weight_utils.download_weights_if_necessary(
file_name=WEIGHT_NAME, source_url=WEIGHT_URL
)

# Return face_detector
return YOLO(weight_file)
class YoloDetectorClient(YoloClientBase, Detector):
def __init__(self, model: YoloModel):
super().__init__(model)

def detect_faces(self, img: np.ndarray) -> List[FacialAreaRegion]:
"""
Expand All @@ -69,21 +41,24 @@ def detect_faces(self, img: np.ndarray) -> List[FacialAreaRegion]:
# For each face, extract the bounding box, the landmarks and confidence
for result in results:

if result.boxes is None or result.keypoints is None:
if result.boxes is None:
continue

# Extract the bounding box and the confidence
x, y, w, h = result.boxes.xywh.tolist()[0]
confidence = result.boxes.conf.tolist()[0]

# right_eye_conf = result.keypoints.conf[0][0]
# left_eye_conf = result.keypoints.conf[0][1]
right_eye = result.keypoints.xy[0][0].tolist()
left_eye = result.keypoints.xy[0][1].tolist()
right_eye = None
left_eye = None
if result.keypoints is not None:
# right_eye_conf = result.keypoints.conf[0][0]
# left_eye_conf = result.keypoints.conf[0][1]
right_eye = result.keypoints.xy[0][0].tolist()
left_eye = result.keypoints.xy[0][1].tolist()

# eyes are list of float, need to cast them tuple of int
left_eye = tuple(int(i) for i in left_eye)
right_eye = tuple(int(i) for i in right_eye)
# eyes are list of float, need to cast them tuple of int
left_eye = tuple(int(i) for i in left_eye)
right_eye = tuple(int(i) for i in right_eye)

x, y, w, h = int(x - w / 2), int(y - h / 2), int(w), int(h)
facial_area = FacialAreaRegion(
Expand All @@ -98,3 +73,23 @@ def detect_faces(self, img: np.ndarray) -> List[FacialAreaRegion]:
resp.append(facial_area)

return resp


class YoloDetectorClientV8n(YoloDetectorClient):
def __init__(self):
super().__init__(YoloModel.V8N)


class YoloDetectorClientV11n(YoloDetectorClient):
def __init__(self):
super().__init__(YoloModel.V11N)


class YoloDetectorClientV11s(YoloDetectorClient):
def __init__(self):
super().__init__(YoloModel.V11S)


class YoloDetectorClientV11m(YoloDetectorClient):
def __init__(self):
super().__init__(YoloModel.V11M)
44 changes: 44 additions & 0 deletions deepface/models/facial_recognition/Yolo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# built-in dependencies
from typing import List

# 3rd party dependencies
import numpy as np

# project dependencies
from deepface.models.YoloClientBase import YoloClientBase
from deepface.models.YoloModel import YoloModel
from deepface.models.FacialRecognition import FacialRecognition
from deepface.commons.logger import Logger

logger = Logger()


class YoloFacialRecognitionClient(YoloClientBase, FacialRecognition):
def __init__(self, model: YoloModel):
super().__init__(model)
self.model_name = "Yolo"
self.input_shape = None
serengil marked this conversation as resolved.
Show resolved Hide resolved
self.output_shape = 512

def forward(self, img: np.ndarray) -> List[float]:
return self.model.embed(img)[0].tolist()


class YoloFacialRecognitionClientV8n(YoloFacialRecognitionClient):
def __init__(self):
super().__init__(YoloModel.V8N)


class YoloFacialRecognitionClientV11n(YoloFacialRecognitionClient):
def __init__(self):
super().__init__(YoloModel.V11N)


class YoloFacialRecognitionClientV11s(YoloFacialRecognitionClient):
def __init__(self):
super().__init__(YoloModel.V11S)


class YoloFacialRecognitionClientV11m(YoloFacialRecognitionClient):
def __init__(self):
super().__init__(YoloModel.V11M)
2 changes: 1 addition & 1 deletion deepface/modules/demography.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def analyze(
Set to False to avoid the exception for low-resolution images (default is True).

detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'centerface' or 'skip'
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', 'centerface' or 'skip'
(default is opencv).

distance_metric (string): Metric for measuring similarity. Options: 'cosine',
Expand Down
2 changes: 1 addition & 1 deletion deepface/modules/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def extract_faces(
as a string, numpy array (BGR), or base64 encoded images.

detector_backend (string): face detector backend. Options: 'opencv', 'retinaface',
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'centerface' or 'skip'
'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', 'centerface' or 'skip'
(default is opencv)

enforce_detection (boolean): If no face is detected in an image, raise an exception.
Expand Down
Loading
Loading