2025-05-28 19:16:17 +08:00

715 lines
33 KiB
Python

import os
import time
import numpy as np
import pandas as pd
import cv2
from deepface import DeepFace
from deepface.commons import functions
# dependency configuration
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
# pylint: disable=too-many-nested-blocks
def analysis(
db_path,
model_name="VGG-Face",
detector_backend="opencv",
distance_metric="cosine",
enable_face_analysis=True,
source=0,
time_threshold=5,
frame_threshold=5,
):
# global variables
text_color = (255, 255, 255)
pivot_img_size = 112 # face recognition result image
enable_emotion = True
enable_age_gender = True
# ------------------------
# find custom values for this input set
target_size = functions.find_target_size(model_name=model_name)
# ------------------------
# build models once to store them in the memory
# otherwise, they will be built after cam started and this will cause delays
DeepFace.build_model(model_name=model_name)
print(f"facial recognition model {model_name} is just built")
if enable_face_analysis:
DeepFace.build_model(model_name="Age")
print("Age model is just built")
DeepFace.build_model(model_name="Gender")
print("Gender model is just built")
DeepFace.build_model(model_name="Emotion")
print("Emotion model is just built")
# -----------------------
# call a dummy find function for db_path once to create embeddings in the initialization
DeepFace.find(
img_path=np.zeros([224, 224, 3]),
db_path=db_path,
model_name=model_name,
detector_backend=detector_backend,
distance_metric=distance_metric,
enforce_detection=False,
)
# -----------------------
# visualization
freeze = False
face_detected = False
face_included_frames = 0 # freeze screen if face detected sequantially 5 frames
freezed_frame = 0
tic = time.time()
cap = cv2.VideoCapture(source) # webcam
while True:
_, img = cap.read()
if img is None:
break
# cv2.namedWindow('img', cv2.WINDOW_FREERATIO)
# cv2.setWindowProperty('img', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
raw_img = img.copy()
resolution_x = img.shape[1]
resolution_y = img.shape[0]
if freeze == False:
try:
# just extract the regions to highlight in webcam
face_objs = DeepFace.extract_faces(
img_path=img,
target_size=target_size,
detector_backend=detector_backend,
enforce_detection=False,
)
faces = []
for face_obj in face_objs:
facial_area = face_obj["facial_area"]
faces.append(
(
facial_area["x"],
facial_area["y"],
facial_area["w"],
facial_area["h"],
)
)
except: # to avoid exception if no face detected
faces = []
if len(faces) == 0:
face_included_frames = 0
else:
faces = []
detected_faces = []
face_index = 0
for x, y, w, h in faces:
if w > 130: # discard small detected faces
face_detected = True
if face_index == 0:
face_included_frames = (
face_included_frames + 1
) # increase frame for a single face
cv2.rectangle(
img, (x, y), (x + w, y + h), (67, 67, 67), 1
) # draw rectangle to main image
cv2.putText(
img,
str(frame_threshold - face_included_frames),
(int(x + w / 4), int(y + h / 1.5)),
cv2.FONT_HERSHEY_SIMPLEX,
4,
(255, 255, 255),
2,
)
detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] # crop detected face
# -------------------------------------
detected_faces.append((x, y, w, h))
face_index = face_index + 1
# -------------------------------------
if face_detected == True and face_included_frames == frame_threshold and freeze == False:
freeze = True
# base_img = img.copy()
base_img = raw_img.copy()
detected_faces_final = detected_faces.copy()
tic = time.time()
if freeze == True:
toc = time.time()
if (toc - tic) < time_threshold:
if freezed_frame == 0:
freeze_img = base_img.copy()
# here, np.uint8 handles showing white area issue
# freeze_img = np.zeros(resolution, np.uint8)
for detected_face in detected_faces_final:
x = detected_face[0]
y = detected_face[1]
w = detected_face[2]
h = detected_face[3]
cv2.rectangle(
freeze_img, (x, y), (x + w, y + h), (67, 67, 67), 1
) # draw rectangle to main image
# -------------------------------
# extract detected face
custom_face = base_img[y : y + h, x : x + w]
# -------------------------------
# facial attribute analysis
if enable_face_analysis == True:
demographies = DeepFace.analyze(
img_path=custom_face,
detector_backend=detector_backend,
enforce_detection=False,
silent=True,
)
if len(demographies) > 0:
# directly access 1st face cos img is extracted already
demography = demographies[0]
if enable_emotion:
emotion = demography["emotion"]
emotion_df = pd.DataFrame(
emotion.items(), columns=["emotion", "score"]
)
emotion_df = emotion_df.sort_values(
by=["score"], ascending=False
).reset_index(drop=True)
# background of mood box
# transparency
overlay = freeze_img.copy()
opacity = 0.4
if x + w + pivot_img_size < resolution_x:
# right
cv2.rectangle(
freeze_img
# , (x+w,y+20)
,
(x + w, y),
(x + w + pivot_img_size, y + h),
(64, 64, 64),
cv2.FILLED,
)
cv2.addWeighted(
overlay, opacity, freeze_img, 1 - opacity, 0, freeze_img
)
elif x - pivot_img_size > 0:
# left
cv2.rectangle(
freeze_img
# , (x-pivot_img_size,y+20)
,
(x - pivot_img_size, y),
(x, y + h),
(64, 64, 64),
cv2.FILLED,
)
cv2.addWeighted(
overlay, opacity, freeze_img, 1 - opacity, 0, freeze_img
)
for index, instance in emotion_df.iterrows():
current_emotion = instance["emotion"]
emotion_label = f"{current_emotion} "
emotion_score = instance["score"] / 100
bar_x = 35 # this is the size if an emotion is 100%
bar_x = int(bar_x * emotion_score)
if x + w + pivot_img_size < resolution_x:
text_location_y = y + 20 + (index + 1) * 20
text_location_x = x + w
if text_location_y < y + h:
cv2.putText(
freeze_img,
emotion_label,
(text_location_x, text_location_y),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(255, 255, 255),
1,
)
cv2.rectangle(
freeze_img,
(x + w + 70, y + 13 + (index + 1) * 20),
(
x + w + 70 + bar_x,
y + 13 + (index + 1) * 20 + 5,
),
(255, 255, 255),
cv2.FILLED,
)
elif x - pivot_img_size > 0:
text_location_y = y + 20 + (index + 1) * 20
text_location_x = x - pivot_img_size
if text_location_y <= y + h:
cv2.putText(
freeze_img,
emotion_label,
(text_location_x, text_location_y),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(255, 255, 255),
1,
)
cv2.rectangle(
freeze_img,
(
x - pivot_img_size + 70,
y + 13 + (index + 1) * 20,
),
(
x - pivot_img_size + 70 + bar_x,
y + 13 + (index + 1) * 20 + 5,
),
(255, 255, 255),
cv2.FILLED,
)
if enable_age_gender:
apparent_age = demography["age"]
dominant_gender = demography["dominant_gender"]
gender = "M" if dominant_gender == "Man" else "W"
# print(f"{apparent_age} years old {dominant_emotion}")
analysis_report = str(int(apparent_age)) + " " + gender
# -------------------------------
info_box_color = (46, 200, 255)
# top
if y - pivot_img_size + int(pivot_img_size / 5) > 0:
triangle_coordinates = np.array(
[
(x + int(w / 2), y),
(
x + int(w / 2) - int(w / 10),
y - int(pivot_img_size / 3),
),
(
x + int(w / 2) + int(w / 10),
y - int(pivot_img_size / 3),
),
]
)
cv2.drawContours(
freeze_img,
[triangle_coordinates],
0,
info_box_color,
-1,
)
cv2.rectangle(
freeze_img,
(
x + int(w / 5),
y - pivot_img_size + int(pivot_img_size / 5),
),
(x + w - int(w / 5), y - int(pivot_img_size / 3)),
info_box_color,
cv2.FILLED,
)
cv2.putText(
freeze_img,
analysis_report,
(x + int(w / 3.5), y - int(pivot_img_size / 2.1)),
cv2.FONT_HERSHEY_SIMPLEX,
1,
(0, 111, 255),
2,
)
# bottom
elif (
y + h + pivot_img_size - int(pivot_img_size / 5)
< resolution_y
):
triangle_coordinates = np.array(
[
(x + int(w / 2), y + h),
(
x + int(w / 2) - int(w / 10),
y + h + int(pivot_img_size / 3),
),
(
x + int(w / 2) + int(w / 10),
y + h + int(pivot_img_size / 3),
),
]
)
cv2.drawContours(
freeze_img,
[triangle_coordinates],
0,
info_box_color,
-1,
)
cv2.rectangle(
freeze_img,
(x + int(w / 5), y + h + int(pivot_img_size / 3)),
(
x + w - int(w / 5),
y + h + pivot_img_size - int(pivot_img_size / 5),
),
info_box_color,
cv2.FILLED,
)
cv2.putText(
freeze_img,
analysis_report,
(x + int(w / 3.5), y + h + int(pivot_img_size / 1.5)),
cv2.FONT_HERSHEY_SIMPLEX,
1,
(0, 111, 255),
2,
)
# --------------------------------
# face recognition
# call find function for custom_face
dfs = DeepFace.find(
img_path=custom_face,
db_path=db_path,
model_name=model_name,
detector_backend=detector_backend,
distance_metric=distance_metric,
enforce_detection=False,
silent=True,
)
if len(dfs) > 0:
# directly access 1st item because custom face is extracted already
df = dfs[0]
if df.shape[0] > 0:
candidate = df.iloc[0]
label = candidate["identity"]
# to use this source image as is
display_img = cv2.imread(label)
# to use extracted face
source_objs = DeepFace.extract_faces(
img_path=label,
target_size=(pivot_img_size, pivot_img_size),
detector_backend=detector_backend,
enforce_detection=False,
align=False,
)
if len(source_objs) > 0:
# extract 1st item directly
source_obj = source_objs[0]
display_img = source_obj["face"]
display_img *= 255
display_img = display_img[:, :, ::-1]
# --------------------
label = label.split("/")[-1]
try:
if (
y - pivot_img_size > 0
and x + w + pivot_img_size < resolution_x
):
# top right
freeze_img[
y - pivot_img_size : y,
x + w : x + w + pivot_img_size,
] = display_img
overlay = freeze_img.copy()
opacity = 0.4
cv2.rectangle(
freeze_img,
(x + w, y),
(x + w + pivot_img_size, y + 20),
(46, 200, 255),
cv2.FILLED,
)
cv2.addWeighted(
overlay,
opacity,
freeze_img,
1 - opacity,
0,
freeze_img,
)
cv2.putText(
freeze_img,
label,
(x + w, y + 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
text_color,
1,
)
# connect face and text
cv2.line(
freeze_img,
(x + int(w / 2), y),
(x + 3 * int(w / 4), y - int(pivot_img_size / 2)),
(67, 67, 67),
1,
)
cv2.line(
freeze_img,
(x + 3 * int(w / 4), y - int(pivot_img_size / 2)),
(x + w, y - int(pivot_img_size / 2)),
(67, 67, 67),
1,
)
elif (
y + h + pivot_img_size < resolution_y
and x - pivot_img_size > 0
):
# bottom left
freeze_img[
y + h : y + h + pivot_img_size,
x - pivot_img_size : x,
] = display_img
overlay = freeze_img.copy()
opacity = 0.4
cv2.rectangle(
freeze_img,
(x - pivot_img_size, y + h - 20),
(x, y + h),
(46, 200, 255),
cv2.FILLED,
)
cv2.addWeighted(
overlay,
opacity,
freeze_img,
1 - opacity,
0,
freeze_img,
)
cv2.putText(
freeze_img,
label,
(x - pivot_img_size, y + h - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
text_color,
1,
)
# connect face and text
cv2.line(
freeze_img,
(x + int(w / 2), y + h),
(
x + int(w / 2) - int(w / 4),
y + h + int(pivot_img_size / 2),
),
(67, 67, 67),
1,
)
cv2.line(
freeze_img,
(
x + int(w / 2) - int(w / 4),
y + h + int(pivot_img_size / 2),
),
(x, y + h + int(pivot_img_size / 2)),
(67, 67, 67),
1,
)
elif y - pivot_img_size > 0 and x - pivot_img_size > 0:
# top left
freeze_img[
y - pivot_img_size : y, x - pivot_img_size : x
] = display_img
overlay = freeze_img.copy()
opacity = 0.4
cv2.rectangle(
freeze_img,
(x - pivot_img_size, y),
(x, y + 20),
(46, 200, 255),
cv2.FILLED,
)
cv2.addWeighted(
overlay,
opacity,
freeze_img,
1 - opacity,
0,
freeze_img,
)
cv2.putText(
freeze_img,
label,
(x - pivot_img_size, y + 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
text_color,
1,
)
# connect face and text
cv2.line(
freeze_img,
(x + int(w / 2), y),
(
x + int(w / 2) - int(w / 4),
y - int(pivot_img_size / 2),
),
(67, 67, 67),
1,
)
cv2.line(
freeze_img,
(
x + int(w / 2) - int(w / 4),
y - int(pivot_img_size / 2),
),
(x, y - int(pivot_img_size / 2)),
(67, 67, 67),
1,
)
elif (
x + w + pivot_img_size < resolution_x
and y + h + pivot_img_size < resolution_y
):
# bottom righ
freeze_img[
y + h : y + h + pivot_img_size,
x + w : x + w + pivot_img_size,
] = display_img
overlay = freeze_img.copy()
opacity = 0.4
cv2.rectangle(
freeze_img,
(x + w, y + h - 20),
(x + w + pivot_img_size, y + h),
(46, 200, 255),
cv2.FILLED,
)
cv2.addWeighted(
overlay,
opacity,
freeze_img,
1 - opacity,
0,
freeze_img,
)
cv2.putText(
freeze_img,
label,
(x + w, y + h - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
text_color,
1,
)
# connect face and text
cv2.line(
freeze_img,
(x + int(w / 2), y + h),
(
x + int(w / 2) + int(w / 4),
y + h + int(pivot_img_size / 2),
),
(67, 67, 67),
1,
)
cv2.line(
freeze_img,
(
x + int(w / 2) + int(w / 4),
y + h + int(pivot_img_size / 2),
),
(x + w, y + h + int(pivot_img_size / 2)),
(67, 67, 67),
1,
)
except Exception as err: # pylint: disable=broad-except
print(str(err))
tic = time.time() # in this way, freezed image can show 5 seconds
# -------------------------------
time_left = int(time_threshold - (toc - tic) + 1)
cv2.rectangle(freeze_img, (10, 10), (90, 50), (67, 67, 67), -10)
cv2.putText(
freeze_img,
str(time_left),
(40, 40),
cv2.FONT_HERSHEY_SIMPLEX,
1,
(255, 255, 255),
1,
)
cv2.imshow("img", freeze_img)
freezed_frame = freezed_frame + 1
else:
face_detected = False
face_included_frames = 0
freeze = False
freezed_frame = 0
else:
cv2.imshow("img", img)
if cv2.waitKey(1) & 0xFF == ord("q"): # press q to quit
break
# kill open cv things
cap.release()
cv2.destroyAllWindows()