From 33a502f60943a16eb57e0061b4d6791186013ada Mon Sep 17 00:00:00 2001 From: Mehrab Shahbazi Date: Wed, 1 Jan 2025 12:29:20 +0330 Subject: [PATCH 1/6] video path is enabled in stream --- deepface/modules/streaming.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deepface/modules/streaming.py b/deepface/modules/streaming.py index cc44783..3f16f82 100644 --- a/deepface/modules/streaming.py +++ b/deepface/modules/streaming.py @@ -22,6 +22,7 @@ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" IDENTIFIED_IMG_SIZE = 112 TEXT_COLOR = (255, 255, 255) + # pylint: disable=unused-variable def analysis( db_path: str, @@ -82,7 +83,11 @@ def analysis( num_frames_with_faces = 0 tic = time.time() - cap = cv2.VideoCapture(source) # webcam + # If source is an integer, use it as a webcam index. Otherwise, treat it as a video file path. + if isinstance(source, int): + cap = cv2.VideoCapture(source) # webcam + else: + cap = cv2.VideoCapture(str(source)) # video file while True: has_frame, img = cap.read() if not has_frame: From 2f9ef19e097ead76d61545a6f9c5e25fa110fa96 Mon Sep 17 00:00:00 2001 From: Mehrab Shahbazi Date: Wed, 1 Jan 2025 13:15:39 +0330 Subject: [PATCH 2/6] saving video output in stream is enabled --- deepface/DeepFace.py | 137 ++++++++++++++++++---------------- deepface/modules/streaming.py | 99 +++++++++++------------- 2 files changed, 115 insertions(+), 121 deletions(-) diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index 6eb31ac..a948f37 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -68,18 +68,18 @@ def build_model(model_name: str, task: str = "facial_recognition") -> Any: def verify( - img1_path: Union[str, np.ndarray, List[float]], - img2_path: Union[str, np.ndarray, List[float]], - model_name: str = "VGG-Face", - detector_backend: str = "opencv", - distance_metric: str = "cosine", - enforce_detection: bool = True, - align: bool = True, - expand_percentage: int = 0, - normalization: str = "base", - silent: bool = False, - threshold: Optional[float] = None, - anti_spoofing: bool = False, + img1_path: Union[str, np.ndarray, List[float]], + img2_path: Union[str, np.ndarray, List[float]], + model_name: str = "VGG-Face", + detector_backend: str = "opencv", + distance_metric: str = "cosine", + enforce_detection: bool = True, + align: bool = True, + expand_percentage: int = 0, + normalization: str = "base", + silent: bool = False, + threshold: Optional[float] = None, + anti_spoofing: bool = False, ) -> Dict[str, Any]: """ Verify if an image pair represents the same person or different persons. @@ -164,14 +164,14 @@ def verify( def analyze( - img_path: Union[str, np.ndarray], - actions: Union[tuple, list] = ("emotion", "age", "gender", "race"), - enforce_detection: bool = True, - detector_backend: str = "opencv", - align: bool = True, - expand_percentage: int = 0, - silent: bool = False, - anti_spoofing: bool = False, + img_path: Union[str, np.ndarray], + actions: Union[tuple, list] = ("emotion", "age", "gender", "race"), + enforce_detection: bool = True, + detector_backend: str = "opencv", + align: bool = True, + expand_percentage: int = 0, + silent: bool = False, + anti_spoofing: bool = False, ) -> List[Dict[str, Any]]: """ Analyze facial attributes such as age, gender, emotion, and race in the provided image. @@ -263,20 +263,20 @@ def analyze( def find( - img_path: Union[str, np.ndarray], - db_path: str, - model_name: str = "VGG-Face", - distance_metric: str = "cosine", - enforce_detection: bool = True, - detector_backend: str = "opencv", - align: bool = True, - expand_percentage: int = 0, - threshold: Optional[float] = None, - normalization: str = "base", - silent: bool = False, - refresh_database: bool = True, - anti_spoofing: bool = False, - batched: bool = False, + img_path: Union[str, np.ndarray], + db_path: str, + model_name: str = "VGG-Face", + distance_metric: str = "cosine", + enforce_detection: bool = True, + detector_backend: str = "opencv", + align: bool = True, + expand_percentage: int = 0, + threshold: Optional[float] = None, + normalization: str = "base", + silent: bool = False, + refresh_database: bool = True, + anti_spoofing: bool = False, + batched: bool = False, ) -> Union[List[pd.DataFrame], List[List[Dict[str, Any]]]]: """ Identify individuals in a database @@ -369,15 +369,15 @@ def find( def represent( - img_path: Union[str, np.ndarray], - model_name: str = "VGG-Face", - enforce_detection: bool = True, - detector_backend: str = "opencv", - align: bool = True, - expand_percentage: int = 0, - normalization: str = "base", - anti_spoofing: bool = False, - max_faces: Optional[int] = None, + img_path: Union[str, np.ndarray], + model_name: str = "VGG-Face", + enforce_detection: bool = True, + detector_backend: str = "opencv", + align: bool = True, + expand_percentage: int = 0, + normalization: str = "base", + anti_spoofing: bool = False, + max_faces: Optional[int] = None, ) -> List[Dict[str, Any]]: """ Represent facial images as multi-dimensional vector embeddings. @@ -441,15 +441,16 @@ def represent( def stream( - db_path: str = "", - model_name: str = "VGG-Face", - detector_backend: str = "opencv", - distance_metric: str = "cosine", - enable_face_analysis: bool = True, - source: Any = 0, - time_threshold: int = 5, - frame_threshold: int = 5, - anti_spoofing: bool = False, + db_path: str = "", + model_name: str = "VGG-Face", + detector_backend: str = "opencv", + distance_metric: str = "cosine", + enable_face_analysis: bool = True, + source: Any = 0, + time_threshold: int = 5, + frame_threshold: int = 5, + anti_spoofing: bool = False, + output_path: Optional[str] = None, # New parameter ) -> None: """ Run real time face recognition and facial attribute analysis @@ -478,6 +479,9 @@ def stream( frame_threshold (int): The frame threshold for face recognition (default is 5). anti_spoofing (boolean): Flag to enable anti spoofing (default is False). + + output_path (str): Path to save the output video. If None, no video is saved. + Returns: None """ @@ -495,19 +499,20 @@ def stream( time_threshold=time_threshold, frame_threshold=frame_threshold, anti_spoofing=anti_spoofing, + output_path=output_path, # Pass the output_path to analysis ) def extract_faces( - img_path: Union[str, np.ndarray], - detector_backend: str = "opencv", - enforce_detection: bool = True, - align: bool = True, - expand_percentage: int = 0, - grayscale: bool = False, - color_face: str = "rgb", - normalize_face: bool = True, - anti_spoofing: bool = False, + img_path: Union[str, np.ndarray], + detector_backend: str = "opencv", + enforce_detection: bool = True, + align: bool = True, + expand_percentage: int = 0, + grayscale: bool = False, + color_face: str = "rgb", + normalize_face: bool = True, + anti_spoofing: bool = False, ) -> List[Dict[str, Any]]: """ Extract faces from a given image @@ -584,11 +589,11 @@ def cli() -> None: def detectFace( - img_path: Union[str, np.ndarray], - target_size: tuple = (224, 224), - detector_backend: str = "opencv", - enforce_detection: bool = True, - align: bool = True, + img_path: Union[str, np.ndarray], + target_size: tuple = (224, 224), + detector_backend: str = "opencv", + enforce_detection: bool = True, + align: bool = True, ) -> Union[np.ndarray, None]: """ Deprecated face detection function. Use extract_faces for same functionality. diff --git a/deepface/modules/streaming.py b/deepface/modules/streaming.py index 3f16f82..b79e4f4 100644 --- a/deepface/modules/streaming.py +++ b/deepface/modules/streaming.py @@ -34,42 +34,29 @@ def analysis( time_threshold=5, frame_threshold=5, anti_spoofing: bool = False, + output_path: Optional[str] = None, # New parameter ): """ - Run real time face recognition and facial attribute analysis + Run real-time face recognition and facial attribute analysis, with optional video output. Args: - db_path (string): Path to the folder containing image files. All detected faces - in the database will be considered in the decision-making process. - - model_name (str): Model for face recognition. Options: VGG-Face, Facenet, Facenet512, - OpenFace, DeepFace, DeepID, Dlib, ArcFace, SFace and GhostFaceNet (default is VGG-Face) - - detector_backend (string): face detector backend. Options: 'opencv', 'retinaface', - 'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', - 'centerface' or 'skip' (default is opencv). - - distance_metric (string): Metric for measuring similarity. Options: 'cosine', - 'euclidean', 'euclidean_l2' (default is cosine). - - enable_face_analysis (bool): Flag to enable face analysis (default is True). - - source (Any): The source for the video stream (default is 0, which represents the - default camera). - - time_threshold (int): The time threshold (in seconds) for face recognition (default is 5). - - frame_threshold (int): The frame threshold for face recognition (default is 5). - - anti_spoofing (boolean): Flag to enable anti spoofing (default is False). + db_path (str): Path to the folder containing image files. + model_name (str): Model for face recognition. + detector_backend (str): Face detector backend. + distance_metric (str): Metric for measuring similarity. + enable_face_analysis (bool): Flag to enable face analysis. + source (Any): The source for the video stream (camera index or video file path). + time_threshold (int): Time threshold (in seconds) for face recognition. + frame_threshold (int): Frame threshold for face recognition. + anti_spoofing (bool): Flag to enable anti-spoofing. + output_path (str): Path to save the output video. If None, no video is saved. Returns: None """ - # initialize models + # Initialize models build_demography_models(enable_face_analysis=enable_face_analysis) build_facial_recognition_model(model_name=model_name) - # call a dummy find function for db_path once to create embeddings before starting webcam _ = search_identity( detected_face=np.zeros([224, 224, 3]), db_path=db_path, @@ -78,35 +65,40 @@ def analysis( model_name=model_name, ) + cap = cv2.VideoCapture(source if isinstance(source, str) else int(source)) + if not cap.isOpened(): + logger.error(f"Cannot open video source: {source}") + return + + # Get video properties + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + fps = cap.get(cv2.CAP_PROP_FPS) + fourcc = cv2.VideoWriter_fourcc(*"mp4v") # Codec for output file + + # Initialize video writer if output_path is provided + video_writer = None + if output_path: + video_writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) + freezed_img = None freeze = False num_frames_with_faces = 0 tic = time.time() - # If source is an integer, use it as a webcam index. Otherwise, treat it as a video file path. - if isinstance(source, int): - cap = cv2.VideoCapture(source) # webcam - else: - cap = cv2.VideoCapture(str(source)) # video file while True: has_frame, img = cap.read() if not has_frame: break - # we are adding some figures into img such as identified facial image, age, gender - # that is why, we need raw image itself to make analysis raw_img = img.copy() - faces_coordinates = [] - if freeze is False: + + if not freeze: faces_coordinates = grab_facial_areas( img=img, detector_backend=detector_backend, anti_spoofing=anti_spoofing ) - - # we will pass img to analyze modules (identity, demography) and add some illustrations - # that is why, we will not be able to extract detected face from img clearly detected_faces = extract_facial_areas(img=img, faces_coordinates=faces_coordinates) - img = highlight_facial_areas(img=img, faces_coordinates=faces_coordinates) img = countdown_to_freeze( img=img, @@ -116,22 +108,18 @@ def analysis( ) num_frames_with_faces = num_frames_with_faces + 1 if len(faces_coordinates) else 0 - freeze = num_frames_with_faces > 0 and num_frames_with_faces % frame_threshold == 0 + if freeze: - # add analyze results into img - derive from raw_img img = highlight_facial_areas( img=raw_img, faces_coordinates=faces_coordinates, anti_spoofing=anti_spoofing ) - - # age, gender and emotion analysis img = perform_demography_analysis( enable_face_analysis=enable_face_analysis, img=raw_img, faces_coordinates=faces_coordinates, detected_faces=detected_faces, ) - # facial recogntion analysis img = perform_facial_recognition( img=img, faces_coordinates=faces_coordinates, @@ -141,30 +129,31 @@ def analysis( distance_metric=distance_metric, model_name=model_name, ) - - # freeze the img after analysis freezed_img = img.copy() - - # start counter for freezing tic = time.time() - logger.info("freezed") + logger.info("Image frozen for analysis") - elif freeze is True and time.time() - tic > time_threshold: + elif freeze and time.time() - tic > time_threshold: freeze = False freezed_img = None - # reset counter for freezing tic = time.time() - logger.info("freeze released") + logger.info("Freeze released") freezed_img = countdown_to_release(img=freezed_img, tic=tic, time_threshold=time_threshold) + display_img = img if freezed_img is None else freezed_img - cv2.imshow("img", img if freezed_img is None else freezed_img) + # Save the frame to output video if writer is initialized + if video_writer: + video_writer.write(display_img) - if cv2.waitKey(1) & 0xFF == ord("q"): # press q to quit + cv2.imshow("img", display_img) + if cv2.waitKey(1) & 0xFF == ord("q"): break - # kill open cv things + # Release resources cap.release() + if video_writer: + video_writer.release() cv2.destroyAllWindows() From c513b26d6d8ecaf3cff0f9d4c2e41c4abf90a0a3 Mon Sep 17 00:00:00 2001 From: Mehrab Shahbazi Date: Thu, 2 Jan 2025 21:51:20 +0330 Subject: [PATCH 3/6] solved pr comments: restore comments and used one line if --- deepface/DeepFace.py | 6 +-- deepface/modules/streaming.py | 70 +++++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index a948f37..b2a8ca1 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -450,7 +450,7 @@ def stream( time_threshold: int = 5, frame_threshold: int = 5, anti_spoofing: bool = False, - output_path: Optional[str] = None, # New parameter + output_path: Optional[str] = None, ) -> None: """ Run real time face recognition and facial attribute analysis @@ -480,7 +480,7 @@ def stream( anti_spoofing (boolean): Flag to enable anti spoofing (default is False). - output_path (str): Path to save the output video. If None, no video is saved. + output_path (str): Path to save the output video. If None, no video is saved (default is None). Returns: None @@ -499,7 +499,7 @@ def stream( time_threshold=time_threshold, frame_threshold=frame_threshold, anti_spoofing=anti_spoofing, - output_path=output_path, # Pass the output_path to analysis + output_path=output_path, ) diff --git a/deepface/modules/streaming.py b/deepface/modules/streaming.py index b79e4f4..335f6ee 100644 --- a/deepface/modules/streaming.py +++ b/deepface/modules/streaming.py @@ -34,29 +34,45 @@ def analysis( time_threshold=5, frame_threshold=5, anti_spoofing: bool = False, - output_path: Optional[str] = None, # New parameter + output_path: Optional[str] = None, ): """ - Run real-time face recognition and facial attribute analysis, with optional video output. + Run real time face recognition and facial attribute analysis Args: - db_path (str): Path to the folder containing image files. - model_name (str): Model for face recognition. - detector_backend (str): Face detector backend. - distance_metric (str): Metric for measuring similarity. - enable_face_analysis (bool): Flag to enable face analysis. - source (Any): The source for the video stream (camera index or video file path). - time_threshold (int): Time threshold (in seconds) for face recognition. - frame_threshold (int): Frame threshold for face recognition. - anti_spoofing (bool): Flag to enable anti-spoofing. - output_path (str): Path to save the output video. If None, no video is saved. + db_path (string): Path to the folder containing image files. All detected faces + in the database will be considered in the decision-making process. + + model_name (str): Model for face recognition. Options: VGG-Face, Facenet, Facenet512, + OpenFace, DeepFace, DeepID, Dlib, ArcFace, SFace and GhostFaceNet (default is VGG-Face) + + detector_backend (string): face detector backend. Options: 'opencv', 'retinaface', + 'mtcnn', 'ssd', 'dlib', 'mediapipe', 'yolov8', 'yolov11n', 'yolov11s', 'yolov11m', + 'centerface' or 'skip' (default is opencv). + + distance_metric (string): Metric for measuring similarity. Options: 'cosine', + 'euclidean', 'euclidean_l2' (default is cosine). + + enable_face_analysis (bool): Flag to enable face analysis (default is True). + + source (Any): The source for the video stream (default is 0, which represents the + default camera). + + time_threshold (int): The time threshold (in seconds) for face recognition (default is 5). + + frame_threshold (int): The frame threshold for face recognition (default is 5). + + anti_spoofing (boolean): Flag to enable anti spoofing (default is False). + + output_path (str): Path to save the output video. If None, no video is saved (default is None). Returns: None """ - # Initialize models + # initialize models build_demography_models(enable_face_analysis=enable_face_analysis) build_facial_recognition_model(model_name=model_name) + # call a dummy find function for db_path once to create embeddings before starting webcam _ = search_identity( detected_face=np.zeros([224, 224, 3]), db_path=db_path, @@ -77,9 +93,11 @@ def analysis( fourcc = cv2.VideoWriter_fourcc(*"mp4v") # Codec for output file # Initialize video writer if output_path is provided - video_writer = None - if output_path: - video_writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) + video_writer = ( + cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height)) + if output_path + else None + ) freezed_img = None freeze = False @@ -91,6 +109,8 @@ def analysis( if not has_frame: break + # we are adding some figures into img such as identified facial image, age, gender + # that is why, we need raw image itself to make analysis raw_img = img.copy() faces_coordinates = [] @@ -98,6 +118,9 @@ def analysis( faces_coordinates = grab_facial_areas( img=img, detector_backend=detector_backend, anti_spoofing=anti_spoofing ) + + # we will pass img to analyze modules (identity, demography) and add some illustrations + # that is why, we will not be able to extract detected face from img clearly detected_faces = extract_facial_areas(img=img, faces_coordinates=faces_coordinates) img = highlight_facial_areas(img=img, faces_coordinates=faces_coordinates) img = countdown_to_freeze( @@ -111,15 +134,19 @@ def analysis( freeze = num_frames_with_faces > 0 and num_frames_with_faces % frame_threshold == 0 if freeze: + # add analyze results into img - derive from raw_img img = highlight_facial_areas( img=raw_img, faces_coordinates=faces_coordinates, anti_spoofing=anti_spoofing ) + + # age, gender and emotion analysis img = perform_demography_analysis( enable_face_analysis=enable_face_analysis, img=raw_img, faces_coordinates=faces_coordinates, detected_faces=detected_faces, ) + # facial recogntion analysis img = perform_facial_recognition( img=img, faces_coordinates=faces_coordinates, @@ -129,13 +156,18 @@ def analysis( distance_metric=distance_metric, model_name=model_name, ) + + # freeze the img after analysis freezed_img = img.copy() + + # start counter for freezing tic = time.time() - logger.info("Image frozen for analysis") + logger.info("freezed") elif freeze and time.time() - tic > time_threshold: freeze = False freezed_img = None + # reset counter for freezing tic = time.time() logger.info("Freeze released") @@ -222,10 +254,10 @@ def search_identity( # detected face is coming from parent, safe to access 1st index df = dfs[0] - if df.shape[0] == 0: + if df.shape[0] == 0: # type: ignore return None, None - candidate = df.iloc[0] + candidate = df.iloc[0] # type: ignore target_path = candidate["identity"] logger.info(f"Hello, {target_path}") From aa47e33cf47ff2a875ef6133cadfbc93bf523d4b Mon Sep 17 00:00:00 2001 From: Mehrab Shahbazi Date: Thu, 2 Jan 2025 21:54:59 +0330 Subject: [PATCH 4/6] remove #type ignore comments --- deepface/modules/streaming.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deepface/modules/streaming.py b/deepface/modules/streaming.py index 335f6ee..c16816d 100644 --- a/deepface/modules/streaming.py +++ b/deepface/modules/streaming.py @@ -254,10 +254,10 @@ def search_identity( # detected face is coming from parent, safe to access 1st index df = dfs[0] - if df.shape[0] == 0: # type: ignore + if df.shape[0] == 0: return None, None - candidate = df.iloc[0] # type: ignore + candidate = df.iloc[0] target_path = candidate["identity"] logger.info(f"Hello, {target_path}") From 5af32fa841a246e131d3faff59c8f0802205bf5e Mon Sep 17 00:00:00 2001 From: Mehrab Shahbazi Date: Fri, 3 Jan 2025 14:55:14 +0330 Subject: [PATCH 5/6] ensure output path --- deepface/modules/streaming.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/deepface/modules/streaming.py b/deepface/modules/streaming.py index c16816d..78eaa1e 100644 --- a/deepface/modules/streaming.py +++ b/deepface/modules/streaming.py @@ -91,7 +91,9 @@ def analysis( height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = cap.get(cv2.CAP_PROP_FPS) fourcc = cv2.VideoWriter_fourcc(*"mp4v") # Codec for output file - + # Ensure the output directory exists if output_path is provided + if output_path: + os.makedirs(os.path.dirname(output_path), exist_ok=True) # Initialize video writer if output_path is provided video_writer = ( cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height)) @@ -254,10 +256,10 @@ def search_identity( # detected face is coming from parent, safe to access 1st index df = dfs[0] - if df.shape[0] == 0: + if df.shape[0] == 0: return None, None - candidate = df.iloc[0] + candidate = df.iloc[0] target_path = candidate["identity"] logger.info(f"Hello, {target_path}") From 15a4f46608f6f754bbede332df9653f806ea1651 Mon Sep 17 00:00:00 2001 From: Mehrab Shahbazi Date: Sat, 4 Jan 2025 11:23:51 +0330 Subject: [PATCH 6/6] write small dockstring for lint --- deepface/DeepFace.py | 3 ++- deepface/modules/streaming.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/deepface/DeepFace.py b/deepface/DeepFace.py index b2a8ca1..f8930e5 100644 --- a/deepface/DeepFace.py +++ b/deepface/DeepFace.py @@ -480,7 +480,8 @@ def stream( anti_spoofing (boolean): Flag to enable anti spoofing (default is False). - output_path (str): Path to save the output video. If None, no video is saved (default is None). + output_path (str): Path to save the output video. (default is None + If None, no video is saved). Returns: None diff --git a/deepface/modules/streaming.py b/deepface/modules/streaming.py index 78eaa1e..e461e56 100644 --- a/deepface/modules/streaming.py +++ b/deepface/modules/streaming.py @@ -64,8 +64,8 @@ def analysis( anti_spoofing (boolean): Flag to enable anti spoofing (default is False). - output_path (str): Path to save the output video. If None, no video is saved (default is None). - + output_path (str): Path to save the output video. (default is None + If None, no video is saved). Returns: None """