mirror of
https://github.com/serengil/deepface.git
synced 2025-06-06 11:35:21 +00:00
Merge pull request #1 from NatLee/feat/merge-predicts-functions
[update] merge predicting funcs
This commit is contained in:
commit
c31268463b
@ -1,5 +1,6 @@
|
||||
# stdlib dependencies
|
||||
from typing import List
|
||||
|
||||
from typing import List, Union
|
||||
|
||||
# 3rd party dependencies
|
||||
import numpy as np
|
||||
@ -40,11 +41,40 @@ class ApparentAgeClient(Demography):
|
||||
self.model = load_model()
|
||||
self.model_name = "Age"
|
||||
|
||||
def predict(self, img: np.ndarray) -> np.float64:
|
||||
# model.predict causes memory issue when it is called in a for loop
|
||||
# age_predictions = self.model.predict(img, verbose=0)[0, :]
|
||||
age_predictions = self.model(img, training=False).numpy()[0, :]
|
||||
return find_apparent_age(age_predictions)
|
||||
def predict(self, img: Union[np.ndarray, List[np.ndarray]]) -> np.ndarray:
|
||||
"""
|
||||
Predict apparent age(s) for single or multiple faces
|
||||
Args:
|
||||
img: Single image as np.ndarray (224, 224, 3) or
|
||||
List of images as List[np.ndarray] or
|
||||
Batch of images as np.ndarray (n, 224, 224, 3)
|
||||
Returns:
|
||||
np.ndarray (n,)
|
||||
"""
|
||||
# Convert to numpy array if input is list
|
||||
if isinstance(img, list):
|
||||
imgs = np.array(img)
|
||||
else:
|
||||
imgs = img
|
||||
|
||||
# Remove batch dimension if exists
|
||||
imgs = imgs.squeeze()
|
||||
|
||||
# Check input dimension
|
||||
if len(imgs.shape) == 3:
|
||||
# Single image - add batch dimension
|
||||
imgs = np.expand_dims(imgs, axis=0)
|
||||
|
||||
# Batch prediction
|
||||
age_predictions = self.model.predict_on_batch(imgs)
|
||||
|
||||
# Calculate apparent ages
|
||||
apparent_ages = np.array(
|
||||
[find_apparent_age(age_prediction) for age_prediction in age_predictions]
|
||||
)
|
||||
|
||||
return apparent_ages
|
||||
|
||||
|
||||
def predicts(self, imgs: List[np.ndarray]) -> np.ndarray:
|
||||
"""
|
||||
@ -70,6 +100,7 @@ class ApparentAgeClient(Demography):
|
||||
return apparent_ages
|
||||
|
||||
|
||||
|
||||
def load_model(
|
||||
url=WEIGHTS_URL,
|
||||
) -> Model:
|
||||
|
@ -1,3 +1,6 @@
|
||||
# stdlib dependencies
|
||||
from typing import List, Union
|
||||
|
||||
# 3rd party dependencies
|
||||
import numpy as np
|
||||
import cv2
|
||||
@ -43,16 +46,53 @@ class EmotionClient(Demography):
|
||||
self.model = load_model()
|
||||
self.model_name = "Emotion"
|
||||
|
||||
def predict(self, img: np.ndarray) -> np.ndarray:
|
||||
img_gray = cv2.cvtColor(img[0], cv2.COLOR_BGR2GRAY)
|
||||
def _preprocess_image(self, img: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Preprocess single image for emotion detection
|
||||
Args:
|
||||
img: Input image (224, 224, 3)
|
||||
Returns:
|
||||
Preprocessed grayscale image (48, 48)
|
||||
"""
|
||||
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||
img_gray = cv2.resize(img_gray, (48, 48))
|
||||
img_gray = np.expand_dims(img_gray, axis=0)
|
||||
return img_gray
|
||||
|
||||
# model.predict causes memory issue when it is called in a for loop
|
||||
# emotion_predictions = self.model.predict(img_gray, verbose=0)[0, :]
|
||||
emotion_predictions = self.model(img_gray, training=False).numpy()[0, :]
|
||||
def predict(self, img: Union[np.ndarray, List[np.ndarray]]) -> np.ndarray:
|
||||
"""
|
||||
Predict emotion probabilities for single or multiple faces
|
||||
Args:
|
||||
img: Single image as np.ndarray (224, 224, 3) or
|
||||
List of images as List[np.ndarray] or
|
||||
Batch of images as np.ndarray (n, 224, 224, 3)
|
||||
Returns:
|
||||
np.ndarray (n, n_emotions)
|
||||
where n_emotions is the number of emotion categories
|
||||
"""
|
||||
# Convert to numpy array if input is list
|
||||
if isinstance(img, list):
|
||||
imgs = np.array(img)
|
||||
else:
|
||||
imgs = img
|
||||
|
||||
return emotion_predictions
|
||||
# Remove batch dimension if exists
|
||||
imgs = imgs.squeeze()
|
||||
|
||||
# Check input dimension
|
||||
if len(imgs.shape) == 3:
|
||||
# Single image - add batch dimension
|
||||
imgs = np.expand_dims(imgs, axis=0)
|
||||
|
||||
# Preprocess each image
|
||||
processed_imgs = np.array([self._preprocess_image(img) for img in imgs])
|
||||
|
||||
# Add channel dimension for grayscale images
|
||||
processed_imgs = np.expand_dims(processed_imgs, axis=-1)
|
||||
|
||||
# Batch prediction
|
||||
predictions = self.model.predict_on_batch(processed_imgs)
|
||||
|
||||
return predictions
|
||||
|
||||
|
||||
def load_model(
|
||||
|
@ -1,5 +1,6 @@
|
||||
# stdlib dependencies
|
||||
from typing import List
|
||||
|
||||
from typing import List, Union
|
||||
|
||||
# 3rd party dependencies
|
||||
import numpy as np
|
||||
@ -40,10 +41,35 @@ class GenderClient(Demography):
|
||||
self.model = load_model()
|
||||
self.model_name = "Gender"
|
||||
|
||||
def predict(self, img: np.ndarray) -> np.ndarray:
|
||||
# model.predict causes memory issue when it is called in a for loop
|
||||
# return self.model.predict(img, verbose=0)[0, :]
|
||||
return self.model(img, training=False).numpy()[0, :]
|
||||
def predict(self, img: Union[np.ndarray, List[np.ndarray]]) -> np.ndarray:
|
||||
"""
|
||||
Predict gender probabilities for single or multiple faces
|
||||
Args:
|
||||
img: Single image as np.ndarray (224, 224, 3) or
|
||||
List of images as List[np.ndarray] or
|
||||
Batch of images as np.ndarray (n, 224, 224, 3)
|
||||
Returns:
|
||||
np.ndarray (n, 2)
|
||||
"""
|
||||
# Convert to numpy array if input is list
|
||||
if isinstance(img, list):
|
||||
imgs = np.array(img)
|
||||
else:
|
||||
imgs = img
|
||||
|
||||
# Remove batch dimension if exists
|
||||
imgs = imgs.squeeze()
|
||||
|
||||
# Check input dimension
|
||||
if len(imgs.shape) == 3:
|
||||
# Single image - add batch dimension
|
||||
imgs = np.expand_dims(imgs, axis=0)
|
||||
|
||||
# Batch prediction
|
||||
predictions = self.model.predict_on_batch(imgs)
|
||||
|
||||
return predictions
|
||||
|
||||
|
||||
def predicts(self, imgs: List[np.ndarray]) -> np.ndarray:
|
||||
"""
|
||||
@ -64,6 +90,7 @@ class GenderClient(Demography):
|
||||
return self.model.predict_on_batch(imgs_)
|
||||
|
||||
|
||||
|
||||
def load_model(
|
||||
url=WEIGHTS_URL,
|
||||
) -> Model:
|
||||
|
@ -1,3 +1,6 @@
|
||||
# stdlib dependencies
|
||||
from typing import List, Union
|
||||
|
||||
# 3rd party dependencies
|
||||
import numpy as np
|
||||
|
||||
@ -37,10 +40,35 @@ class RaceClient(Demography):
|
||||
self.model = load_model()
|
||||
self.model_name = "Race"
|
||||
|
||||
def predict(self, img: np.ndarray) -> np.ndarray:
|
||||
# model.predict causes memory issue when it is called in a for loop
|
||||
# return self.model.predict(img, verbose=0)[0, :]
|
||||
return self.model(img, training=False).numpy()[0, :]
|
||||
def predict(self, img: Union[np.ndarray, List[np.ndarray]]) -> np.ndarray:
|
||||
"""
|
||||
Predict race probabilities for single or multiple faces
|
||||
Args:
|
||||
img: Single image as np.ndarray (224, 224, 3) or
|
||||
List of images as List[np.ndarray] or
|
||||
Batch of images as np.ndarray (n, 224, 224, 3)
|
||||
Returns:
|
||||
np.ndarray (n, n_races)
|
||||
where n_races is the number of race categories
|
||||
"""
|
||||
# Convert to numpy array if input is list
|
||||
if isinstance(img, list):
|
||||
imgs = np.array(img)
|
||||
else:
|
||||
imgs = img
|
||||
|
||||
# Remove batch dimension if exists
|
||||
imgs = imgs.squeeze()
|
||||
|
||||
# Check input dimension
|
||||
if len(imgs.shape) == 3:
|
||||
# Single image - add batch dimension
|
||||
imgs = np.expand_dims(imgs, axis=0)
|
||||
|
||||
# Batch prediction
|
||||
predictions = self.model.predict_on_batch(imgs)
|
||||
|
||||
return predictions
|
||||
|
||||
|
||||
def load_model(
|
||||
@ -62,7 +90,7 @@ def load_model(
|
||||
|
||||
# --------------------------
|
||||
|
||||
race_model = Model(inputs=model.input, outputs=base_model_output)
|
||||
race_model = Model(inputs=model.inputs, outputs=base_model_output)
|
||||
|
||||
# --------------------------
|
||||
|
||||
|
@ -9,7 +9,7 @@ from tqdm import tqdm
|
||||
from deepface.modules import modeling, detection, preprocessing
|
||||
from deepface.models.demography import Gender, Race, Emotion
|
||||
|
||||
|
||||
# pylint: disable=trailing-whitespace
|
||||
def analyze(
|
||||
img_path: Union[str, np.ndarray],
|
||||
actions: Union[tuple, list] = ("emotion", "age", "gender", "race"),
|
||||
@ -130,83 +130,107 @@ def analyze(
|
||||
anti_spoofing=anti_spoofing,
|
||||
)
|
||||
|
||||
for img_obj in img_objs:
|
||||
if anti_spoofing is True and img_obj.get("is_real", True) is False:
|
||||
raise ValueError("Spoof detected in the given image.")
|
||||
# Anti-spoofing check
|
||||
if anti_spoofing:
|
||||
for img_obj in img_objs:
|
||||
if img_obj.get("is_real", True) is False:
|
||||
raise ValueError("Spoof detected in the given image.")
|
||||
|
||||
# Prepare the input for the model
|
||||
valid_faces = []
|
||||
face_regions = []
|
||||
face_confidences = []
|
||||
|
||||
for img_obj in img_objs:
|
||||
# Extract the face content
|
||||
img_content = img_obj["face"]
|
||||
img_region = img_obj["facial_area"]
|
||||
img_confidence = img_obj["confidence"]
|
||||
# Check if the face content is empty
|
||||
if img_content.shape[0] == 0 or img_content.shape[1] == 0:
|
||||
continue
|
||||
|
||||
# rgb to bgr
|
||||
# Convert the image to RGB format from BGR
|
||||
img_content = img_content[:, :, ::-1]
|
||||
|
||||
# resize input image
|
||||
# Resize the image to the target size for the model
|
||||
img_content = preprocessing.resize_image(img=img_content, target_size=(224, 224))
|
||||
|
||||
obj = {}
|
||||
# facial attribute analysis
|
||||
pbar = tqdm(
|
||||
range(0, len(actions)),
|
||||
desc="Finding actions",
|
||||
disable=silent if len(actions) > 1 else True,
|
||||
)
|
||||
for index in pbar:
|
||||
action = actions[index]
|
||||
pbar.set_description(f"Action: {action}")
|
||||
valid_faces.append(img_content)
|
||||
face_regions.append(img_obj["facial_area"])
|
||||
face_confidences.append(img_obj["confidence"])
|
||||
|
||||
if action == "emotion":
|
||||
emotion_predictions = modeling.build_model(
|
||||
task="facial_attribute", model_name="Emotion"
|
||||
).predict(img_content)
|
||||
sum_of_predictions = emotion_predictions.sum()
|
||||
# If no valid faces are found, return an empty list
|
||||
if not valid_faces:
|
||||
return []
|
||||
|
||||
obj["emotion"] = {}
|
||||
# Convert the list of valid faces to a numpy array
|
||||
faces_array = np.array(valid_faces)
|
||||
resp_objects = [{} for _ in range(len(valid_faces))]
|
||||
|
||||
# For each action, predict the corresponding attribute
|
||||
pbar = tqdm(
|
||||
range(0, len(actions)),
|
||||
desc="Finding actions",
|
||||
disable=silent if len(actions) > 1 else True,
|
||||
)
|
||||
|
||||
for index in pbar:
|
||||
action = actions[index]
|
||||
pbar.set_description(f"Action: {action}")
|
||||
|
||||
if action == "emotion":
|
||||
# Build the emotion model
|
||||
model = modeling.build_model(task="facial_attribute", model_name="Emotion")
|
||||
emotion_predictions = model.predict(faces_array)
|
||||
|
||||
for idx, predictions in enumerate(emotion_predictions):
|
||||
sum_of_predictions = predictions.sum()
|
||||
resp_objects[idx]["emotion"] = {}
|
||||
|
||||
for i, emotion_label in enumerate(Emotion.labels):
|
||||
emotion_prediction = 100 * emotion_predictions[i] / sum_of_predictions
|
||||
obj["emotion"][emotion_label] = emotion_prediction
|
||||
emotion_prediction = 100 * predictions[i] / sum_of_predictions
|
||||
resp_objects[idx]["emotion"][emotion_label] = emotion_prediction
|
||||
|
||||
resp_objects[idx]["dominant_emotion"] = Emotion.labels[np.argmax(predictions)]
|
||||
|
||||
obj["dominant_emotion"] = Emotion.labels[np.argmax(emotion_predictions)]
|
||||
elif action == "age":
|
||||
# Build the age model
|
||||
model = modeling.build_model(task="facial_attribute", model_name="Age")
|
||||
age_predictions = model.predict(faces_array)
|
||||
|
||||
for idx, age in enumerate(age_predictions):
|
||||
resp_objects[idx]["age"] = int(age)
|
||||
|
||||
elif action == "age":
|
||||
apparent_age = modeling.build_model(
|
||||
task="facial_attribute", model_name="Age"
|
||||
).predict(img_content)
|
||||
# int cast is for exception - object of type 'float32' is not JSON serializable
|
||||
obj["age"] = int(apparent_age)
|
||||
|
||||
elif action == "gender":
|
||||
gender_predictions = modeling.build_model(
|
||||
task="facial_attribute", model_name="Gender"
|
||||
).predict(img_content)
|
||||
obj["gender"] = {}
|
||||
elif action == "gender":
|
||||
# Build the gender model
|
||||
model = modeling.build_model(task="facial_attribute", model_name="Gender")
|
||||
gender_predictions = model.predict(faces_array)
|
||||
|
||||
for idx, predictions in enumerate(gender_predictions):
|
||||
resp_objects[idx]["gender"] = {}
|
||||
|
||||
for i, gender_label in enumerate(Gender.labels):
|
||||
gender_prediction = 100 * gender_predictions[i]
|
||||
obj["gender"][gender_label] = gender_prediction
|
||||
gender_prediction = 100 * predictions[i]
|
||||
resp_objects[idx]["gender"][gender_label] = gender_prediction
|
||||
|
||||
resp_objects[idx]["dominant_gender"] = Gender.labels[np.argmax(predictions)]
|
||||
|
||||
obj["dominant_gender"] = Gender.labels[np.argmax(gender_predictions)]
|
||||
|
||||
elif action == "race":
|
||||
race_predictions = modeling.build_model(
|
||||
task="facial_attribute", model_name="Race"
|
||||
).predict(img_content)
|
||||
sum_of_predictions = race_predictions.sum()
|
||||
|
||||
obj["race"] = {}
|
||||
elif action == "race":
|
||||
# Build the race model
|
||||
model = modeling.build_model(task="facial_attribute", model_name="Race")
|
||||
race_predictions = model.predict(faces_array)
|
||||
|
||||
for idx, predictions in enumerate(race_predictions):
|
||||
sum_of_predictions = predictions.sum()
|
||||
resp_objects[idx]["race"] = {}
|
||||
|
||||
for i, race_label in enumerate(Race.labels):
|
||||
race_prediction = 100 * race_predictions[i] / sum_of_predictions
|
||||
obj["race"][race_label] = race_prediction
|
||||
race_prediction = 100 * predictions[i] / sum_of_predictions
|
||||
resp_objects[idx]["race"][race_label] = race_prediction
|
||||
|
||||
resp_objects[idx]["dominant_race"] = Race.labels[np.argmax(predictions)]
|
||||
|
||||
obj["dominant_race"] = Race.labels[np.argmax(race_predictions)]
|
||||
|
||||
# -----------------------------
|
||||
# mention facial areas
|
||||
obj["region"] = img_region
|
||||
# include image confidence
|
||||
obj["face_confidence"] = img_confidence
|
||||
|
||||
resp_objects.append(obj)
|
||||
# Add the face region and confidence to the response objects
|
||||
for idx, resp_obj in enumerate(resp_objects):
|
||||
resp_obj["region"] = face_regions[idx]
|
||||
resp_obj["face_confidence"] = face_confidences[idx]
|
||||
|
||||
return resp_objects
|
||||
|
@ -135,3 +135,15 @@ def test_analyze_for_different_detectors():
|
||||
assert result["gender"]["Man"] > result["gender"]["Woman"]
|
||||
else:
|
||||
assert result["gender"]["Man"] < result["gender"]["Woman"]
|
||||
|
||||
def test_analyze_for_multiple_faces():
|
||||
img = "dataset/img4.jpg"
|
||||
# Copy and combine the same image to create multiple faces
|
||||
img = cv2.imread(img)
|
||||
img = cv2.hconcat([img, img])
|
||||
demography_objs = DeepFace.analyze(img, silent=True)
|
||||
for demography in demography_objs:
|
||||
logger.debug(demography)
|
||||
assert demography["age"] > 20 and demography["age"] < 40
|
||||
assert demography["dominant_gender"] == "Woman"
|
||||
logger.info("✅ test analyze for multiple faces done")
|
||||
|
Loading…
x
Reference in New Issue
Block a user