import os from typing import Any, List import cv2 import numpy as np from deepface.models.Detector import Detector, FacialAreaRegion class OpenCvClient(Detector): """ Class to cover common face detection functionalitiy for OpenCv backend """ def __init__(self): self.model = self.build_model() def build_model(self): """ Build opencv's face and eye detector models Returns: model (dict): including face_detector and eye_detector keys """ detector = {} detector["face_detector"] = self.__build_cascade("haarcascade") detector["eye_detector"] = self.__build_cascade("haarcascade_eye") return detector def detect_faces(self, img: np.ndarray) -> List[FacialAreaRegion]: """ Detect and align face with opencv Args: img (np.ndarray): pre-loaded image as numpy array Returns: results (List[FacialAreaRegion]): A list of FacialAreaRegion objects """ resp = [] detected_face = None faces = [] try: # faces = detector["face_detector"].detectMultiScale(img, 1.3, 5) # note that, by design, opencv's haarcascade scores are >0 but not capped at 1 faces, _, scores = self.model["face_detector"].detectMultiScale3( img, 1.1, 10, outputRejectLevels=True ) except: pass if len(faces) > 0: for (x, y, w, h), confidence in zip(faces, scores): detected_face = img[int(y) : int(y + h), int(x) : int(x + w)] left_eye, right_eye = self.find_eyes(img=detected_face) # eyes found in the detected face instead image itself # detected face's coordinates should be added if left_eye is not None: left_eye = (int(x + left_eye[0]), int(y + left_eye[1])) if right_eye is not None: right_eye = (int(x + right_eye[0]), int(y + right_eye[1])) facial_area = FacialAreaRegion( x=x, y=y, w=w, h=h, left_eye=left_eye, right_eye=right_eye, confidence=(100 - confidence) / 100, ) resp.append(facial_area) return resp def find_eyes(self, img: np.ndarray) -> tuple: """ Find the left and right eye coordinates of given image Args: img (np.ndarray): given image Returns: left and right eye (tuple) """ left_eye = None right_eye = None # if image has unexpectedly 0 dimension then skip alignment if img.shape[0] == 0 or img.shape[1] == 0: return left_eye, right_eye detected_face_gray = cv2.cvtColor( img, cv2.COLOR_BGR2GRAY ) # eye detector expects gray scale image eyes = self.model["eye_detector"].detectMultiScale(detected_face_gray, 1.1, 10) # ---------------------------------------------------------------- # opencv eye detection module is not strong. it might find more than 2 eyes! # besides, it returns eyes with different order in each call (issue 435) # this is an important issue because opencv is the default detector and ssd also uses this # find the largest 2 eye. Thanks to @thelostpeace eyes = sorted(eyes, key=lambda v: abs(v[2] * v[3]), reverse=True) # ---------------------------------------------------------------- if len(eyes) >= 2: # decide left and right eye eye_1 = eyes[0] eye_2 = eyes[1] if eye_1[0] < eye_2[0]: right_eye = eye_1 left_eye = eye_2 else: right_eye = eye_2 left_eye = eye_1 # ----------------------- # find center of eyes left_eye = ( int(left_eye[0] + (left_eye[2] / 2)), int(left_eye[1] + (left_eye[3] / 2)), ) right_eye = ( int(right_eye[0] + (right_eye[2] / 2)), int(right_eye[1] + (right_eye[3] / 2)), ) return left_eye, right_eye def __build_cascade(self, model_name="haarcascade") -> Any: """ Build a opencv face&eye detector models Returns: model (Any) """ opencv_path = self.__get_opencv_path() if model_name == "haarcascade": face_detector_path = opencv_path + "haarcascade_frontalface_default.xml" if os.path.isfile(face_detector_path) != True: raise ValueError( "Confirm that opencv is installed on your environment! Expected path ", face_detector_path, " violated.", ) detector = cv2.CascadeClassifier(face_detector_path) elif model_name == "haarcascade_eye": eye_detector_path = opencv_path + "haarcascade_eye.xml" if os.path.isfile(eye_detector_path) != True: raise ValueError( "Confirm that opencv is installed on your environment! Expected path ", eye_detector_path, " violated.", ) detector = cv2.CascadeClassifier(eye_detector_path) else: raise ValueError(f"unimplemented model_name for build_cascade - {model_name}") return detector def __get_opencv_path(self) -> str: """ Returns where opencv installed Returns: installation_path (str) """ opencv_home = cv2.__file__ folders = opencv_home.split(os.path.sep)[0:-1] path = folders[0] for folder in folders[1:]: path = path + "/" + folder return path + "/data/"