mirror of
https://github.com/serengil/deepface.git
synced 2025-06-07 20:15:21 +00:00
Merge pull request #959 from serengil/feat-task-1303-datastore-update
some improvements
This commit is contained in:
commit
51bb1808d0
@ -21,9 +21,16 @@ class _Layer:
|
|||||||
class SFaceModel:
|
class SFaceModel:
|
||||||
def __init__(self, model_path):
|
def __init__(self, model_path):
|
||||||
|
|
||||||
self.model = cv.FaceRecognizerSF.create(
|
try:
|
||||||
model=model_path, config="", backend_id=0, target_id=0
|
self.model = cv.FaceRecognizerSF.create(
|
||||||
)
|
model=model_path, config="", backend_id=0, target_id=0
|
||||||
|
)
|
||||||
|
except Exception as err:
|
||||||
|
raise ValueError(
|
||||||
|
"Exception while calling opencv.FaceRecognizerSF module."
|
||||||
|
+ "This is an optional dependency."
|
||||||
|
+ "You can install it as pip install opencv-contrib-python."
|
||||||
|
) from err
|
||||||
|
|
||||||
self.layers = [_Layer()]
|
self.layers = [_Layer()]
|
||||||
|
|
||||||
|
@ -90,7 +90,19 @@ def detect_face(detector: dict, img: np.ndarray, align: bool = True) -> list:
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
def align_face(eye_detector, img):
|
def align_face(eye_detector: Any, img: np.ndarray) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
Align a given image with the pre-built eye_detector
|
||||||
|
Args:
|
||||||
|
eye_detector (Any): cascade classifier object
|
||||||
|
img (np.ndarray): given image
|
||||||
|
Returns:
|
||||||
|
aligned_img (np.ndarray)
|
||||||
|
"""
|
||||||
|
# if image has unexpectedly 0 dimension then skip alignment
|
||||||
|
if img.shape[0] == 0 or img.shape[1] == 0:
|
||||||
|
return img
|
||||||
|
|
||||||
detected_face_gray = cv2.cvtColor(
|
detected_face_gray = cv2.cvtColor(
|
||||||
img, cv2.COLOR_BGR2GRAY
|
img, cv2.COLOR_BGR2GRAY
|
||||||
) # eye detector expects gray scale image
|
) # eye detector expects gray scale image
|
||||||
@ -130,7 +142,12 @@ def align_face(eye_detector, img):
|
|||||||
return img # return img anyway
|
return img # return img anyway
|
||||||
|
|
||||||
|
|
||||||
def get_opencv_path():
|
def get_opencv_path() -> str:
|
||||||
|
"""
|
||||||
|
Returns where opencv installed
|
||||||
|
Returns:
|
||||||
|
installation_path (str)
|
||||||
|
"""
|
||||||
opencv_home = cv2.__file__
|
opencv_home = cv2.__file__
|
||||||
folders = opencv_home.split(os.path.sep)[0:-1]
|
folders = opencv_home.split(os.path.sep)[0:-1]
|
||||||
|
|
||||||
|
@ -43,10 +43,17 @@ def build_model() -> dict:
|
|||||||
|
|
||||||
gdown.download(url, output, quiet=False)
|
gdown.download(url, output, quiet=False)
|
||||||
|
|
||||||
face_detector = cv2.dnn.readNetFromCaffe(
|
try:
|
||||||
home + "/.deepface/weights/deploy.prototxt",
|
face_detector = cv2.dnn.readNetFromCaffe(
|
||||||
home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel",
|
home + "/.deepface/weights/deploy.prototxt",
|
||||||
)
|
home + "/.deepface/weights/res10_300x300_ssd_iter_140000.caffemodel",
|
||||||
|
)
|
||||||
|
except Exception as err:
|
||||||
|
raise ValueError(
|
||||||
|
"Exception while calling opencv.dnn module."
|
||||||
|
+ "This is an optional dependency."
|
||||||
|
+ "You can install it as pip install opencv-contrib-python."
|
||||||
|
) from err
|
||||||
|
|
||||||
eye_detector = OpenCvWrapper.build_cascade("haarcascade_eye")
|
eye_detector = OpenCvWrapper.build_cascade("haarcascade_eye")
|
||||||
|
|
||||||
|
@ -24,7 +24,17 @@ def build_model() -> Any:
|
|||||||
logger.info(f"{file_name} will be downloaded...")
|
logger.info(f"{file_name} will be downloaded...")
|
||||||
output = home + f"/.deepface/weights/{file_name}"
|
output = home + f"/.deepface/weights/{file_name}"
|
||||||
gdown.download(url, output, quiet=False)
|
gdown.download(url, output, quiet=False)
|
||||||
face_detector = cv2.FaceDetectorYN_create(home + f"/.deepface/weights/{file_name}", "", (0, 0))
|
|
||||||
|
try:
|
||||||
|
face_detector = cv2.FaceDetectorYN_create(
|
||||||
|
home + f"/.deepface/weights/{file_name}", "", (0, 0)
|
||||||
|
)
|
||||||
|
except Exception as err:
|
||||||
|
raise ValueError(
|
||||||
|
"Exception while calling opencv.FaceDetectorYN_create module."
|
||||||
|
+ "This is an optional dependency."
|
||||||
|
+ "You can install it as pip install opencv-contrib-python."
|
||||||
|
) from err
|
||||||
return face_detector
|
return face_detector
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,6 +75,7 @@ def find(
|
|||||||
|
|
||||||
file_name = f"representations_{model_name}.pkl"
|
file_name = f"representations_{model_name}.pkl"
|
||||||
file_name = file_name.replace("-", "_").lower()
|
file_name = file_name.replace("-", "_").lower()
|
||||||
|
datastore_path = f"{db_path}/{file_name}"
|
||||||
|
|
||||||
df_cols = [
|
df_cols = [
|
||||||
"identity",
|
"identity",
|
||||||
@ -85,100 +86,93 @@ def find(
|
|||||||
"target_h",
|
"target_h",
|
||||||
]
|
]
|
||||||
|
|
||||||
if os.path.exists(db_path + "/" + file_name):
|
if os.path.exists(datastore_path):
|
||||||
if not silent:
|
with open(datastore_path, "rb") as f:
|
||||||
logger.warn(
|
|
||||||
f"Representations for images in {db_path} folder were previously stored"
|
|
||||||
f" in {file_name}. If you added new instances after the creation, then please "
|
|
||||||
"delete this file and call find function again. It will create it again."
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(f"{db_path}/{file_name}", "rb") as f:
|
|
||||||
representations = pickle.load(f)
|
representations = pickle.load(f)
|
||||||
|
|
||||||
if len(representations) > 0 and len(representations[0]) != len(df_cols):
|
if len(representations) > 0 and len(representations[0]) != len(df_cols):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Seems existing {db_path}/{file_name} is out-of-the-date."
|
f"Seems existing {datastore_path} is out-of-the-date."
|
||||||
"Delete it and re-run."
|
"Please delete it and re-run."
|
||||||
|
)
|
||||||
|
|
||||||
|
alpha_employees = __list_images(path=db_path)
|
||||||
|
beta_employees = [representation[0] for representation in representations]
|
||||||
|
|
||||||
|
newbies = list(set(alpha_employees) - set(beta_employees))
|
||||||
|
oldies = list(set(beta_employees) - set(alpha_employees))
|
||||||
|
|
||||||
|
if newbies:
|
||||||
|
logger.warn(
|
||||||
|
f"Items {newbies} were added into {db_path}"
|
||||||
|
f" just after data source {datastore_path} created!"
|
||||||
|
)
|
||||||
|
newbies_representations = __find_bulk_embeddings(
|
||||||
|
employees=newbies,
|
||||||
|
model_name=model_name,
|
||||||
|
target_size=target_size,
|
||||||
|
detector_backend=detector_backend,
|
||||||
|
enforce_detection=enforce_detection,
|
||||||
|
align=align,
|
||||||
|
normalization=normalization,
|
||||||
|
silent=silent,
|
||||||
|
)
|
||||||
|
representations = representations + newbies_representations
|
||||||
|
|
||||||
|
if oldies:
|
||||||
|
logger.warn(
|
||||||
|
f"Items {oldies} were dropped from {db_path}"
|
||||||
|
f" just after data source {datastore_path} created!"
|
||||||
|
)
|
||||||
|
representations = [rep for rep in representations if rep[0] not in oldies]
|
||||||
|
|
||||||
|
if newbies or oldies:
|
||||||
|
if len(representations) == 0:
|
||||||
|
raise ValueError(f"There is no image in {db_path} anymore!")
|
||||||
|
|
||||||
|
# save new representations
|
||||||
|
with open(datastore_path, "wb") as f:
|
||||||
|
pickle.dump(representations, f)
|
||||||
|
|
||||||
|
if not silent:
|
||||||
|
logger.info(
|
||||||
|
f"{len(newbies)} new representations are just added"
|
||||||
|
f" whereas {len(oldies)} represented one(s) are just dropped"
|
||||||
|
f" in {db_path}/{file_name} file."
|
||||||
)
|
)
|
||||||
|
|
||||||
if not silent:
|
if not silent:
|
||||||
logger.info(f"There are {len(representations)} representations found in {file_name}")
|
logger.info(f"There are {len(representations)} representations found in {file_name}")
|
||||||
|
|
||||||
else: # create representation.pkl from scratch
|
else: # create representation.pkl from scratch
|
||||||
employees = []
|
employees = __list_images(path=db_path)
|
||||||
|
|
||||||
for r, _, f in os.walk(db_path):
|
|
||||||
for file in f:
|
|
||||||
if (
|
|
||||||
(".jpg" in file.lower())
|
|
||||||
or (".jpeg" in file.lower())
|
|
||||||
or (".png" in file.lower())
|
|
||||||
):
|
|
||||||
exact_path = r + "/" + file
|
|
||||||
employees.append(exact_path)
|
|
||||||
|
|
||||||
if len(employees) == 0:
|
if len(employees) == 0:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"There is no image in ",
|
f"There is no image in {db_path} folder!"
|
||||||
db_path,
|
"Validate .jpg, .jpeg or .png files exist in this path.",
|
||||||
" folder! Validate .jpg or .png files exist in this path.",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# ------------------------
|
# ------------------------
|
||||||
# find representations for db images
|
# find representations for db images
|
||||||
|
representations = __find_bulk_embeddings(
|
||||||
representations = []
|
employees=employees,
|
||||||
|
model_name=model_name,
|
||||||
# for employee in employees:
|
target_size=target_size,
|
||||||
pbar = tqdm(
|
detector_backend=detector_backend,
|
||||||
range(0, len(employees)),
|
enforce_detection=enforce_detection,
|
||||||
desc="Finding representations",
|
align=align,
|
||||||
disable=silent,
|
normalization=normalization,
|
||||||
|
silent=silent,
|
||||||
)
|
)
|
||||||
for index in pbar:
|
|
||||||
employee = employees[index]
|
|
||||||
|
|
||||||
img_objs = functions.extract_faces(
|
|
||||||
img=employee,
|
|
||||||
target_size=target_size,
|
|
||||||
detector_backend=detector_backend,
|
|
||||||
grayscale=False,
|
|
||||||
enforce_detection=enforce_detection,
|
|
||||||
align=align,
|
|
||||||
)
|
|
||||||
|
|
||||||
for img_content, img_region, _ in img_objs:
|
|
||||||
embedding_obj = representation.represent(
|
|
||||||
img_path=img_content,
|
|
||||||
model_name=model_name,
|
|
||||||
enforce_detection=enforce_detection,
|
|
||||||
detector_backend="skip",
|
|
||||||
align=align,
|
|
||||||
normalization=normalization,
|
|
||||||
)
|
|
||||||
|
|
||||||
img_representation = embedding_obj[0]["embedding"]
|
|
||||||
|
|
||||||
instance = []
|
|
||||||
instance.append(employee)
|
|
||||||
instance.append(img_representation)
|
|
||||||
instance.append(img_region["x"])
|
|
||||||
instance.append(img_region["y"])
|
|
||||||
instance.append(img_region["w"])
|
|
||||||
instance.append(img_region["h"])
|
|
||||||
representations.append(instance)
|
|
||||||
|
|
||||||
# -------------------------------
|
# -------------------------------
|
||||||
|
|
||||||
with open(f"{db_path}/{file_name}", "wb") as f:
|
with open(datastore_path, "wb") as f:
|
||||||
pickle.dump(representations, f)
|
pickle.dump(representations, f)
|
||||||
|
|
||||||
if not silent:
|
if not silent:
|
||||||
logger.info(
|
logger.info(f"Representations stored in {db_path}/{file_name} file.")
|
||||||
f"Representations stored in {db_path}/{file_name} file."
|
|
||||||
+ "Please delete this file when you add new identities in your database."
|
|
||||||
)
|
|
||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# now, we got representations for facial database
|
# now, we got representations for facial database
|
||||||
@ -218,7 +212,7 @@ def find(
|
|||||||
result_df["source_h"] = source_region["h"]
|
result_df["source_h"] = source_region["h"]
|
||||||
|
|
||||||
distances = []
|
distances = []
|
||||||
for index, instance in df.iterrows():
|
for _, instance in df.iterrows():
|
||||||
source_representation = instance[f"{model_name}_representation"]
|
source_representation = instance[f"{model_name}_representation"]
|
||||||
|
|
||||||
target_dims = len(list(target_representation))
|
target_dims = len(list(target_representation))
|
||||||
@ -266,3 +260,87 @@ def find(
|
|||||||
logger.info(f"find function lasts {toc - tic} seconds")
|
logger.info(f"find function lasts {toc - tic} seconds")
|
||||||
|
|
||||||
return resp_obj
|
return resp_obj
|
||||||
|
|
||||||
|
|
||||||
|
def __list_images(path: str) -> list:
|
||||||
|
"""
|
||||||
|
List images in a given path
|
||||||
|
Args:
|
||||||
|
path (str): path's location
|
||||||
|
Returns:
|
||||||
|
images (list): list of exact image paths
|
||||||
|
"""
|
||||||
|
images = []
|
||||||
|
for r, _, f in os.walk(path):
|
||||||
|
for file in f:
|
||||||
|
if file.lower().endswith((".jpg", ".jpeg", ".png")):
|
||||||
|
exact_path = f"{r}/{file}"
|
||||||
|
images.append(exact_path)
|
||||||
|
return images
|
||||||
|
|
||||||
|
|
||||||
|
def __find_bulk_embeddings(
|
||||||
|
employees: List[str],
|
||||||
|
model_name: str = "VGG-Face",
|
||||||
|
target_size: tuple = (224, 224),
|
||||||
|
detector_backend: str = "opencv",
|
||||||
|
enforce_detection: bool = True,
|
||||||
|
align: bool = True,
|
||||||
|
normalization: str = "base",
|
||||||
|
silent: bool = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Find embeddings of a list of images
|
||||||
|
|
||||||
|
Args:
|
||||||
|
employees (list): list of exact image paths
|
||||||
|
model_name (str): facial recognition model name
|
||||||
|
target_size (tuple): expected input shape of facial
|
||||||
|
recognition model
|
||||||
|
detector_backend (str): face detector model name
|
||||||
|
enforce_detection (bool): set this to False if you
|
||||||
|
want to proceed when you cannot detect any face
|
||||||
|
align (bool): enable or disable alignment of image
|
||||||
|
before feeding to facial recognition model
|
||||||
|
normalization (bool): normalization technique
|
||||||
|
silent (bool): enable or disable informative logging
|
||||||
|
Returns:
|
||||||
|
representations (list): pivot list of embeddings with
|
||||||
|
image name and detected face area's coordinates
|
||||||
|
"""
|
||||||
|
representations = []
|
||||||
|
for employee in tqdm(
|
||||||
|
employees,
|
||||||
|
desc="Finding representations",
|
||||||
|
disable=silent,
|
||||||
|
):
|
||||||
|
img_objs = functions.extract_faces(
|
||||||
|
img=employee,
|
||||||
|
target_size=target_size,
|
||||||
|
detector_backend=detector_backend,
|
||||||
|
grayscale=False,
|
||||||
|
enforce_detection=enforce_detection,
|
||||||
|
align=align,
|
||||||
|
)
|
||||||
|
|
||||||
|
for img_content, img_region, _ in img_objs:
|
||||||
|
embedding_obj = representation.represent(
|
||||||
|
img_path=img_content,
|
||||||
|
model_name=model_name,
|
||||||
|
enforce_detection=enforce_detection,
|
||||||
|
detector_backend="skip",
|
||||||
|
align=align,
|
||||||
|
normalization=normalization,
|
||||||
|
)
|
||||||
|
|
||||||
|
img_representation = embedding_obj[0]["embedding"]
|
||||||
|
|
||||||
|
instance = []
|
||||||
|
instance.append(employee)
|
||||||
|
instance.append(img_representation)
|
||||||
|
instance.append(img_region["x"])
|
||||||
|
instance.append(img_region["y"])
|
||||||
|
instance.append(img_region["w"])
|
||||||
|
instance.append(img_region["h"])
|
||||||
|
representations.append(instance)
|
||||||
|
return representations
|
||||||
|
4
setup.py
4
setup.py
@ -8,11 +8,11 @@ with open("requirements.txt", "r", encoding="utf-8") as f:
|
|||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name="deepface",
|
name="deepface",
|
||||||
version="0.0.81",
|
version="0.0.82",
|
||||||
author="Sefik Ilkin Serengil",
|
author="Sefik Ilkin Serengil",
|
||||||
author_email="serengil@gmail.com",
|
author_email="serengil@gmail.com",
|
||||||
description="A Lightweight Face Recognition and Facial Attribute Analysis Framework (Age, Gender, Emotion, Race) for Python",
|
description="A Lightweight Face Recognition and Facial Attribute Analysis Framework (Age, Gender, Emotion, Race) for Python",
|
||||||
data_files=[('', ['README.md', 'requirements.txt'])],
|
data_files=[("", ["README.md", "requirements.txt"])],
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
url="https://github.com/serengil/deepface",
|
url="https://github.com/serengil/deepface",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import cv2
|
import cv2
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from deepface import DeepFace
|
from deepface import DeepFace
|
||||||
|
from deepface.commons import distance
|
||||||
from deepface.commons.logger import Logger
|
from deepface.commons.logger import Logger
|
||||||
|
|
||||||
logger = Logger("tests/test_find.py")
|
logger = Logger("tests/test_find.py")
|
||||||
@ -41,9 +42,32 @@ def test_find_with_array_input():
|
|||||||
# validate reproducability
|
# validate reproducability
|
||||||
assert identity_df["VGG-Face_cosine"].values[0] == 0
|
assert identity_df["VGG-Face_cosine"].values[0] == 0
|
||||||
|
|
||||||
|
|
||||||
df = df[df["identity"] != img_path]
|
df = df[df["identity"] != img_path]
|
||||||
logger.debug(df.head())
|
logger.debug(df.head())
|
||||||
assert df.shape[0] > 0
|
assert df.shape[0] > 0
|
||||||
|
|
||||||
logger.info("✅ test find for array input done")
|
logger.info("✅ test find for array input done")
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_with_extracted_faces():
|
||||||
|
img_path = "dataset/img1.jpg"
|
||||||
|
face_objs = DeepFace.extract_faces(img_path)
|
||||||
|
img = face_objs[0]["face"]
|
||||||
|
dfs = DeepFace.find(img, db_path="dataset", detector_backend="skip", silent=True)
|
||||||
|
assert len(dfs) > 0
|
||||||
|
for df in dfs:
|
||||||
|
assert isinstance(df, pd.DataFrame)
|
||||||
|
|
||||||
|
# one is img1.jpg itself
|
||||||
|
identity_df = df[df["identity"] == img_path]
|
||||||
|
assert identity_df.shape[0] > 0
|
||||||
|
|
||||||
|
# validate reproducability
|
||||||
|
assert identity_df["VGG-Face_cosine"].values[0] < (
|
||||||
|
distance.findThreshold(model_name="VGG-Face", distance_metric="cosine")
|
||||||
|
)
|
||||||
|
|
||||||
|
df = df[df["identity"] != img_path]
|
||||||
|
logger.debug(df.head())
|
||||||
|
assert df.shape[0] > 0
|
||||||
|
logger.info("✅ test find for extracted face input done")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user