예제 #1
0
def test_invalid_image():
    model_loc = "./models"
    ob = FaceDetectorDlib(model_loc=model_loc, model_type="hog")
    img = np.zeros((100,100,5), dtype='float32')

    with pytest.raises(InvalidImage):
        ob.detect_faces(img)
예제 #2
0
def test_incorrect_model_path():
    """
    Test object init with the incorrect model path
    """
    ob = None
    incorrect_model_loc = "./wrong_models"
    with pytest.raises(ModelFileMissing):
        ob = FaceDetectorDlib(model_loc=incorrect_model_loc, model_type="mmod")
예제 #3
0
    def __init__(
        self,
        model_loc: str = "./models",
        persistent_data_loc="data/facial_data.json",
        face_detection_threshold: int = 0.99,
        face_detector: str = "dlib",
    ) -> None:
        """Constructor

        Args:
            model_loc (str, optional): Path where model files are saved. Defaults to "./models".
            persistent_data_loc (str, optional): Path to save the persistence storage file.
                Defaults to 'data/facial_data.json'.
            face_detection_threshold (int, optional): Threshold facial model confidence to consider a detection.
                Defaults to 0.99.
            face_detector (str, optional): Type of face detector to use. Options:
                Dlib-HOG and MMOD, MTCNN, OpenCV CNN. Defaults to 'dlib'.

        Raises:
            ModelFileMissing: Raised when model file is not found
        """
        keypoints_model_path = os.path.join(
            model_loc, FaceRecognition.keypoints_model_path
        )
        face_recog_model_path = os.path.join(
            model_loc, FaceRecognition.face_recog_model_path
        )
        if not (
            path_exists(keypoints_model_path) or path_exists(face_recog_model_path)
        ):
            raise ModelFileMissing
        if face_detector == "opencv":
            self.face_detector = FaceDetectorOpenCV(
                model_loc=model_loc, crop_forehead=True, shrink_ratio=0.2
            )
        elif face_detector == "mtcnn":
            self.face_detector = FaceDetectorMTCNN(crop_forehead=True, shrink_ratio=0.2)
        else:
            self.face_detector = FaceDetectorDlib()
        self.face_detection_threshold = face_detection_threshold

        self.keypoints_detector = dlib.shape_predictor(keypoints_model_path)
        self.face_recognizor = dlib.face_recognition_model_v1(face_recog_model_path)
        self.datastore = FaceDataStore(persistent_data_loc=persistent_data_loc)
예제 #4
0
def test_correct_model_path():
    """
    Test object init with the correct model path
    """
    ob = None
    model_loc = "./models"
    try:
        ob = FaceDetectorDlib(model_loc=model_loc, model_type="mmod")
    except Exception:
        pass
    finally:
        assert isinstance(ob, FaceDetectorDlib)
예제 #5
0
    def __init__(
        self,
        face_detector: str = "dlib",
        model_loc: str = "models",
        persistent_db_path: str = "data/facial_data.json",
        face_detection_threshold: float = 0.8,
    ) -> None:

        self.face_recognizer = FaceRecognition(
            model_loc=model_loc,
            persistent_data_loc=persistent_db_path,
            face_detection_threshold=face_detection_threshold,
            face_detector=face_detector,
        )
        if face_detector == "opencv":
            self.face_detector = FaceDetectorOpenCV(
                model_loc=model_loc, crop_forehead=True, shrink_ratio=0.2
            )
        elif face_detector == "mtcnn":
            self.face_detector = FaceDetectorMTCNN(crop_forehead=True, shrink_ratio=0.2)
        elif face_detector == "dlib":
            self.face_detector = FaceDetectorDlib()
예제 #6
0
class FaceRecognition:
    """Class for Face Recognition related methods.
    Main operations: Register and Recognize face.

    Raises:
        ModelFileMissing: [description]
        NoNameProvided: [description]
        NoFaceDetected: [description]
        FaceMissing: [description]
    """

    keypoints_model_path = "shape_predictor_5_face_landmarks.dat"
    face_recog_model_path = "dlib_face_recognition_resnet_model_v1.dat"

    def __init__(
        self,
        model_loc: str = "./models",
        persistent_data_loc="data/facial_data.json",
        face_detection_threshold: int = 0.99,
        face_detector: str = "dlib",
    ) -> None:
        """Constructor

        Args:
            model_loc (str, optional): Path where model files are saved. Defaults to "./models".
            persistent_data_loc (str, optional): Path to save the persistence storage file.
                Defaults to 'data/facial_data.json'.
            face_detection_threshold (int, optional): Threshold facial model confidence to consider a detection.
                Defaults to 0.99.
            face_detector (str, optional): Type of face detector to use. Options:
                Dlib-HOG and MMOD, MTCNN, OpenCV CNN. Defaults to 'dlib'.

        Raises:
            ModelFileMissing: Raised when model file is not found
        """
        keypoints_model_path = os.path.join(
            model_loc, FaceRecognition.keypoints_model_path)
        face_recog_model_path = os.path.join(
            model_loc, FaceRecognition.face_recog_model_path)
        if not (path_exists(keypoints_model_path)
                or path_exists(face_recog_model_path)):
            raise ModelFileMissing
        if face_detector == "opencv":
            self.face_detector = FaceDetectorOpenCV(model_loc=model_loc,
                                                    crop_forehead=True,
                                                    shrink_ratio=0.2)
        elif face_detector == "mtcnn":
            self.face_detector = FaceDetectorMTCNN(crop_forehead=True,
                                                   shrink_ratio=0.2)
        else:
            self.face_detector = FaceDetectorDlib()
        self.face_detection_threshold = face_detection_threshold

        self.keypoints_detector = dlib.shape_predictor(keypoints_model_path)
        self.face_recognizor = dlib.face_recognition_model_v1(
            face_recog_model_path)
        self.datastore = FaceDataStore(persistent_data_loc=persistent_data_loc)

    def register_face(self,
                      image=None,
                      name: str = None,
                      bbox: List[int] = None):
        """Method to register a face via the facial encoding.
        Siamese neural network is used to generate 128 numbers
        for a given facial region. These encodings can be used to identify a
        facial ROI for identification later.

        Args:
            image (numpy array, optional): Defaults to None.
            name (str, optional): Name to associate with the face. Defaults to None.
            bbox (List[int], optional): Facial ROI bounding box. Defaults to None.

        Raises:
            NoNameProvided:
            NoFaceDetected:

        Returns:
            Dict: Facial encodings along with an unique identifier and name
        """

        if not is_valid_img(image) or name is None:
            raise NoNameProvided if name is None else InvalidImage

        image = image.copy()
        face_encoding = None

        try:
            if bbox is None:
                bboxes = self.face_detector.detect_faces(image=image)
                if len(bboxes) == 0:
                    raise NoFaceDetected
                bbox = bboxes[0]
            face_encoding = self.get_facial_fingerprint(image, bbox)

            # Convert the numpy array to normal python float list
            # to make json serialization simpler
            facial_data = {
                "id": str(uuid.uuid4()),
                "encoding": tuple(face_encoding.tolist()),
                "name": name,
            }
            # save the encoding with the name
            self.save_facial_data(facial_data)
            logger.info("Face registered with name: {}".format(name))
        except Exception as exc:
            raise exc
        return facial_data

    def save_facial_data(self, facial_data: Dict = None) -> bool:
        """Saves facial data to cache and persistent storage

        Args:
            facial_data (Dict, optional): [description]. Defaults to None.

        Returns:
            bool: status of saving
        """
        if facial_data is not None:
            self.datastore.add_facial_data(facial_data=facial_data)
            return True
        return False

    def get_registered_faces(self) -> List[Dict]:
        """Returns the list of all facial data of all registered users

        Returns:
            List[Dict]: List of facial data
        """
        return self.datastore.get_all_facial_data()

    def recognize_faces(self,
                        image,
                        threshold: float = 0.6,
                        bboxes: List[List[int]] = None):
        """Finds matching registered users for the
        face(s) in the input image. The input image should be cropped to contain
        only one face and then sent to this method.

        Args:
            image (numpy array): [description]
            threshold (float, optional): Max threshold euclidean distance to
            consider two people to be a match. Defaults to 0.6.
            bboxes (List[List[int]], optional): List of facial ROI bounding box.
                If this is None, then face detection is performed on the image
                and facial recognition is run for all the detected faces, otherwise
                if a bounding box is sent, then facial recognition is only
                done for that bounding box. Defaults to None.

        Raises:
            NoFaceDetected: [description]

        Returns:
            List[Tuple]: List of information of matching
        """
        if image is None:
            return InvalidImage
        image = image.copy()

        if bboxes is None:
            bboxes = self.face_detector.detect_faces(image=image)
            if len(bboxes) == 0:
                raise NoFaceDetected
        # Load the data of existing registered faces
        # compare using the metric the closest match
        all_facial_data = self.datastore.get_all_facial_data()
        matches = []
        for bbox in bboxes:
            face_encoding = self.get_facial_fingerprint(image, bbox)
            match, min_dist = None, 10000000

            for face_data in all_facial_data:
                dist = self.euclidean_distance(face_encoding,
                                               face_data["encoding"])
                if dist <= threshold and dist < min_dist:
                    match = face_data
                    min_dist = dist
            # bound box, matched face details, dist from closest match
            matches.append((bbox, match, min_dist))
        return matches

    def get_facial_fingerprint(self,
                               image,
                               bbox: List[int] = None) -> List[float]:
        """Driver method for generating the facial encoding for an input image.
            Input image bbox -> facial keypoints detection -> keypoints used for
            face alignment -> Siamese NN -> Encoding
        Args:
            image (numpy array): [description]
            bbox (List[int], optional): List of facial ROI bounding box. Defaults to None.

        Raises:
            FaceMissing: [description]

        Returns:
            List[float]: Facial Encoding
        """
        if bbox is None:
            raise FaceMissing
        # Convert to dlib format rectangle
        bbox = convert_to_dlib_rectangle(bbox)
        # Get the facial landmark coordinates
        face_keypoints = self.keypoints_detector(image, bbox)

        # Compute the 128D vector that describes the face in an img identified by
        # shape. In general, if two face descriptor vectors have a Euclidean
        # distance between them less than 0.6 then they are from the same
        # person, otherwise they are from different people.
        face_encoding = self.get_face_encoding(image, face_keypoints)
        return face_encoding

    def get_face_encoding(self, image, face_keypoints: List):
        """Method for generating the facial encoding for
            a face in an input image.

        Args:
            image (numpy array): [description]
            face_keypoints (List): [description]

        Returns:
            [type]: [description]
        """
        encoding = self.face_recognizor.compute_face_descriptor(
            image, face_keypoints, 1)
        return np.array(encoding)

    def euclidean_distance(self, vector1: Tuple, vector2: Tuple):
        """Computes Euclidean distance between two vectors

        Args:
            vector1 (Tuple): [description]
            vector2 (Tuple): [description]

        Returns:
            [type]: [description]
        """
        return np.linalg.norm(np.array(vector1) - np.array(vector2))
예제 #7
0
def test_detect_face_mmod(img2_data, img2_facebox_dlib_mmod):
    model_loc = "./models"
    ob = FaceDetectorDlib(model_loc=model_loc, model_type="mmod")
    assert img2_facebox_dlib_mmod == ob.detect_faces(img2_data)
예제 #8
0
def test_detect_face_hog(img2_data, img2_facebox_dlib_hog):
    model_loc = "./models"
    ob = FaceDetectorDlib(model_loc=model_loc, model_type="hog")
    assert img2_facebox_dlib_hog == ob.detect_faces(img2_data)
예제 #9
0
class FaceRecognitionVideo:
    """Class with methods to do facial recognition on video or webcam feed."""

    def __init__(
        self,
        face_detector: str = "dlib",
        model_loc: str = "models",
        persistent_db_path: str = "data/facial_data.json",
        face_detection_threshold: float = 0.8,
    ) -> None:

        self.face_recognizer = FaceRecognition(
            model_loc=model_loc,
            persistent_data_loc=persistent_db_path,
            face_detection_threshold=face_detection_threshold,
            face_detector=face_detector,
        )
        if face_detector == "opencv":
            self.face_detector = FaceDetectorOpenCV(
                model_loc=model_loc, crop_forehead=True, shrink_ratio=0.2
            )
        elif face_detector == "mtcnn":
            self.face_detector = FaceDetectorMTCNN(crop_forehead=True, shrink_ratio=0.2)
        elif face_detector == "dlib":
            self.face_detector = FaceDetectorDlib()

    def recognize_face_video(
        self,
        video_path: str = None,
        detection_interval: int = 15,
        save_output: bool = False,
        preview: bool = False,
        output_path: str = "data/output.mp4",
        resize_scale: float = 0.5,
        verbose: bool = True,
    ) -> None:

        if video_path is None:
            # If no video source is given, try
            # switching to webcam
            video_path = 0
        elif not path_exists(video_path):
            raise FileNotFoundError

        cap, video_writer = None, None

        try:
            cap = cv2.VideoCapture(video_path)
            # To save the video file, get the opencv video writer
            video_writer = get_video_writer(cap, output_path)
            frame_num = 1
            matches, name, match_dist = [], None, None

            t1 = time.time()
            logger.info("Enter q to exit...")

            while True:
                status, frame = cap.read()
                if not status:
                    break
                try:
                    # Flip webcam feed so that it looks mirrored
                    if video_path == 0:
                        frame = cv2.flip(frame, 2)

                    if frame_num % detection_interval == 0:
                        # Scale down the image to increase model
                        # inference time.
                        smaller_frame = convert_to_rgb(
                            cv2.resize(frame, (0, 0), fx=resize_scale, fy=resize_scale)
                        )
                        # Detect faces
                        matches = self.face_recognizer.recognize_faces(
                            image=smaller_frame, threshold=0.6, bboxes=None
                        )
                    if verbose:
                        self.annotate_facial_data(matches, frame, resize_scale)
                    if save_output:
                        video_writer.write(frame)
                    if preview:
                        cv2.imshow("Preview", cv2.resize(frame, (680, 480)))

                    key = cv2.waitKey(1) & 0xFF
                    if key == ord("q"):
                        break
                except Exception:
                    pass
                frame_num += 1

            t2 = time.time()
            logger.info("Time:{}".format((t2 - t1) / 60))
            logger.info("Total frames: {}".format(frame_num))
            logger.info("Time per frame: {}".format((t2 - t1) / frame_num))

        except Exception as exc:
            raise exc
        finally:
            cv2.destroyAllWindows()
            cap.release()
            video_writer.release()

    def register_face_webcam(
        self, name: str = None, detection_interval: int = 5
    ) -> bool:
        if name is None:
            raise NoNameProvided

        cap = None
        try:
            cap = cv2.VideoCapture(0)
            frame_num = 0

            while True:
                status, frame = cap.read()
                if not status:
                    break

                if frame_num % detection_interval == 0:
                    # detect faces
                    bboxes = self.face_detector.detect_faces(image=frame)
                    try:
                        if len(bboxes) == 1:
                            facial_data = self.face_recognizer.register_face(
                                image=frame, name=name, bbox=bboxes[0]
                            )
                            if facial_data:
                                draw_bounding_box(frame, bboxes[0])
                                cv2.imshow("Registered Face", frame)
                                cv2.waitKey(0)
                                logger.info("Press any key to continue......")
                                break
                    except Exception as exc:
                        traceback.print_exc(file=sys.stdout)
                frame_num += 1
        except Exception as exc:
            raise exc
        finally:
            cv2.destroyAllWindows()
            cap.release()

    def register_face_path(self, img_path: str, name: str) -> None:
        if not path_exists(img_path):
            raise PathNotFound
        try:
            img = cv2.imread(img_path)
            facial_data = self.face_recognizer.register_face(
                image=convert_to_rgb(img), name=name
            )
            if facial_data:
                logger.info("Face registered...")
                return True
            return False
        except Exception as exc:
            raise exc

    def annotate_facial_data(
        self, matches: List[Dict], image, resize_scale: float
    ) -> None:
        for face_bbox, match, dist in matches:
            name = match["name"] if match is not None else "Unknown"
            # match_dist = '{:.2f}'.format(dist) if dist < 1000 else 'INF'
            # name = name + ', Dist: {}'.format(match_dist)
            # draw face labels
            draw_annotation(image, name, int(1 / resize_scale) * np.array(face_bbox))