authenticate_user.py 6.36 KB
Newer Older
Kyle Anderson's avatar
Kyle Anderson committed
1
"""
2
Methods for authenticating a user.
Kyle Anderson's avatar
Kyle Anderson committed
3
"""
4 5

import time
6

7
import cv2
8 9
import face_recognition
import imutils
10

11 12 13
import common
from data_handler import load_database

14 15 16 17
# How long to wait before timing out and saying failed authentication.
TIMEOUT: float = 30.0
# Minimum number of frames in which a user must be recognized in order to be authenticated.
MIN_USER_RECOGNITION_COUNT = 10
18
USER_IDS_KEY: str = "user_ids"
19 20 21


def load_encodings(file_location: str):
Kyle Anderson's avatar
Kyle Anderson committed
22
    """Loads the encodings for faces from the given file location."""
23
    return load_database(file_location)
24

Kyle Anderson's avatar
Kyle Anderson committed
25

26
def determine_identity(face_encoding, known_faces):
27 28 29 30 31 32 33 34 35 36 37 38 39 40
    """
    "Determines the most likely identity of a single face. Returns the user id.
    :param face_encoding: The encoding which needs identification.
    :param known_faces: The database of known faces to use for searching.
    :return: The string user_id of the recognized user.
    """
    recognized_users = {}
    for (user_id, user_encodings) in known_faces.items():
        matches = face_recognition.compare_faces(user_encodings, face_encoding)
        # Count the number of occurrences of true.
        recognized_users[user_id] = matches.count(True)

    matched_user: str = max(recognized_users,
                            key=recognized_users.get)
41 42
    return matched_user

Kyle Anderson's avatar
Kyle Anderson committed
43

44
def check_recognized_users(recognized_user_counts):
Kyle Anderson's avatar
Kyle Anderson committed
45 46
    """Determines if there are recognized users in the dictionary,
    and if so returns the list of their IDs"""
47
    recognized_users = []
Kyle Anderson's avatar
Kyle Anderson committed
48
    for user_id, count in recognized_user_counts.items():
49 50 51
        if count >= MIN_USER_RECOGNITION_COUNT:
            recognized_users.append(user_id)
    return recognized_users
Kyle Anderson's avatar
Kyle Anderson committed
52

Kyle Anderson's avatar
Kyle Anderson committed
53

54
def draw_rectanges_and_user_ids(image_frame, conversion: float, box_user_id_map: dict):
Kyle Anderson's avatar
Kyle Anderson committed
55
    """Draws the rectangles and user_ids onto the video stream so anyone viewing the stream could see them."""
56 57
    if box_user_id_map and len(box_user_id_map) > 0:
        for ((top, right, bottom, left), user_id) in box_user_id_map.items():
58 59 60 61 62 63 64 65 66 67
            top = round(top * conversion)
            right = round(right * conversion)
            bottom = round(bottom * conversion)
            left = round(left * conversion)

            # Draw the rectangle onto the face we've identified
            cv2.rectangle(image_frame, (left, top), (right, bottom), (0, 255, 0), 2)
            # Find the top so we can put the text there.
            y = top - 15 if top - 15 > 15 else top + 15
            cv2.putText(image_frame, user_id, (left, y), cv2.FONT_HERSHEY_PLAIN, 0.75, (0, 255, 0), 2)
68
    common.display_frame(image_frame)
Kyle Anderson's avatar
Kyle Anderson committed
69 70


71
def recognize_user(known_faces: dict, encoding_model: str = "hog", image_flip: int = None,
72
                   draw_rectangles=False):
Kyle Anderson's avatar
Kyle Anderson committed
73 74 75 76
    """Attempts to recognize a user.
    Returns the ID of the user if identified, or None if no users are identified.
    Dictionary of the form { "user_id": #frames_recognized } to keep
    track of how many times each user was recognized."""
77 78
    recognized_users_count = {}
    recognized_user = None
79
    video_stream = common.start_video_stream(0)
80 81 82

    # Determine the time at which we will time out. Equal to current time + timeout.
    timeout_time: float = time.time() + TIMEOUT
83
    while time.time() < timeout_time:
Kyle Anderson's avatar
Kyle Anderson committed
84
        # Read a image_frame from the video stream.
Kyle Anderson's avatar
Kyle Anderson committed
85
        ret, image_frame = video_stream.read()
86 87
        if image_flip is not None:
            image_frame = cv2.flip(image_frame, image_flip)
88 89

        # Convert input from BGR to RGB
90
        cv2.cvtColor(image_frame, cv2.COLOR_BGR2RGB)
91
        # Resize image to width of 750 PX to speed up processing.
Kyle Anderson's avatar
Kyle Anderson committed
92 93
        rgb_image = imutils.resize(image_frame, width=750)
        r = image_frame.shape[1] / float(rgb_image.shape[1])
94

95
        # Detect the location of each face and determine the boxes in which they lie
96
        boxes = face_recognition.face_locations(
97
            rgb_image, model=encoding_model)
98
        # Compute the facial embeddings (the encoding) at
Kyle Anderson's avatar
Kyle Anderson committed
99
        # each of the locations found in the previous line.
100 101
        encodings = face_recognition.face_encodings(rgb_image, boxes)

102 103
        box_user_id_mapping = {}
        for (i, encoding) in enumerate(encodings):
Kyle Anderson's avatar
Kyle Anderson committed
104 105
            user_id: str = determine_identity(encoding, known_faces)
            if user_id:
106 107
                if user_id not in recognized_users_count:
                    recognized_users_count[user_id] = 0
Kyle Anderson's avatar
Kyle Anderson committed
108
                recognized_users_count[user_id] += 1
109
                box_user_id_mapping[boxes[i]] = user_id
Kyle Anderson's avatar
Kyle Anderson committed
110 111

        if draw_rectangles:
112
            draw_rectanges_and_user_ids(image_frame, r, box_user_id_mapping)
Kyle Anderson's avatar
Kyle Anderson committed
113

114 115 116 117
        # Now check if we have already positively identified a user enough times
        recognized_users = check_recognized_users(recognized_users_count)
        if len(recognized_users) > 0:
            break
118
        cv2.waitKey(20)  # Required or else video stream doesn't really render.
Kyle Anderson's avatar
Kyle Anderson committed
119

120 121 122 123 124
    if recognized_users_count:
        recognized_user = max(recognized_users_count,
                              key=recognized_users_count.get)
        if recognized_users_count[recognized_user] < MIN_USER_RECOGNITION_COUNT:
            recognized_user = None
125 126 127 128 129
    return recognized_user


# If this program is the main program, authenticate the user.
if __name__ == "__main__":
130 131 132 133 134 135 136 137 138 139 140 141 142 143
    import argparse;

    parser = argparse.ArgumentParser(description="Facial Identification Options")
    parser.add_argument("--encodings", "-e", type=str, help="File location of facial encodings.", required=False,
                        default="./encodings.pickle")
    parser.add_argument("--model", "-m", type=str, help="Type of encoding method, either \"hog\" or \"cnn\". HOG is "
                                                        "faster, CNN is more accurate.", required=False,
                        default="hog", choices=["cnn", "hog"])
    parser.add_argument("--flip", "-f", type=int,
                        help="Whether or not to flip the image vertically or horizontally. 0 to flip horizontally, 1 to flip vertically.",
                        required=False, default=None, choices=[0, 1])
    parser.add_argument("--show", "-s", action="store_true",
                        help="Include this argument to have the image shown to you.", default=False)
    args = parser.parse_args()
Kyle Anderson's avatar
Kyle Anderson committed
144
    user = recognize_user(known_faces=load_encodings(args.encodings), encoding_model=args.model, image_flip=args.flip,
145 146 147
                          draw_rectangles=args.show)
    if user:
        print(f"Recognized user {user}.")