authenticate_user.py 7.79 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
"""
Kyle Anderson's avatar
Kyle Anderson committed
4

5
import time
6

7
import cv2
8 9
import face_recognition
import imutils
10

11
import common
Kyle Anderson's avatar
Kyle Anderson committed
12
import data_handler
13

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."""
Kyle Anderson's avatar
Kyle Anderson committed
23
    return data_handler.load_database(file_location)
Kyle Anderson's avatar
Kyle Anderson committed
24 25


26
def determine_identity(face_encoding, known_faces) -> str:
27 28 29 30 31 32 33 34 35
    """
    "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)
36 37 38 39
        count = matches.count(True)
        if count > 0:
            # Count the number of occurrences of true.
            recognized_users[user_id] = count
40

41 42 43 44
    matched_user = ""
    if len(recognized_users) > 0:
        matched_user: str = max(recognized_users,
                                key=recognized_users.get)
45 46
    return matched_user

Kyle Anderson's avatar
Kyle Anderson committed
47

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

Kyle Anderson's avatar
Kyle Anderson committed
57

Kyle Anderson's avatar
Kyle Anderson committed
58
def draw_rectangles_and_user_ids(image_frame, conversion: float, box_user_id_map: dict):
Kyle Anderson's avatar
Kyle Anderson committed
59
    """Draws the rectangles and user_ids onto the video stream so anyone viewing the stream could see them."""
60 61
    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():
62 63 64 65 66 67 68 69 70 71
            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)
72
    common.display_frame(image_frame)
Kyle Anderson's avatar
Kyle Anderson committed
73 74


75 76
def run_face_recognition(frame, known_faces: dict, encoding_model: str = "hog", draw_rectangles: bool = False) -> list:
    recognized_user_ids: list = []
77
    original_frame = frame
78 79

    # Convert input from BGR to RGB
80
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
    # Resize image to width of 750 PX to speed up processing.
    rgb_image = imutils.resize(frame, width=750)
    r = frame.shape[1] / float(rgb_image.shape[1])

    # Detect the location of each face and determine the boxes in which they lie
    boxes = face_recognition.face_locations(
        rgb_image, model=encoding_model)
    # Compute the facial embeddings (the encoding) at
    # each of the locations found in the previous line.
    encodings = face_recognition.face_encodings(rgb_image, boxes)

    box_user_id_mapping = {}
    for (i, encoding) in enumerate(encodings):
        user_id: str = determine_identity(encoding, known_faces)
        if user_id:
            box_user_id_mapping[boxes[i]] = user_id
            recognized_user_ids.append(user_id)

    if draw_rectangles:
100
        draw_rectangles_and_user_ids(original_frame, r, box_user_id_mapping)
101 102 103 104

    return recognized_user_ids


105
def recognize_user(known_faces: dict, encoding_model: str = "hog", image_flip: int = None,
106
                   draw_rectangles: bool = False):
Kyle Anderson's avatar
Kyle Anderson committed
107
    """Attempts to recognize a user.
Kyle Anderson's avatar
Kyle Anderson committed
108
    Returns the ID of the user if identified, or None if no users are identified."""
109 110
    recognized_users_count = {}
    recognized_user = None
111
    video_stream = common.start_video_stream(0)
112 113 114

    # Determine the time at which we will time out. Equal to current time + timeout.
    timeout_time: float = time.time() + TIMEOUT
115
    while time.time() < timeout_time:
Kyle Anderson's avatar
Kyle Anderson committed
116 117 118 119 120
        # Read a image_frame from the video stream.
        ret, image_frame = video_stream.read()
        if image_flip is not None:
            image_frame = cv2.flip(image_frame, image_flip)

121 122 123 124 125 126
        recognized_user_ids = run_face_recognition(image_frame, known_faces, encoding_model=encoding_model,
                                                   draw_rectangles=draw_rectangles)
        for user_id in recognized_user_ids:
            if user_id not in recognized_users_count:
                recognized_users_count[user_id] = 0
            recognized_users_count[user_id] += 1
Kyle Anderson's avatar
Kyle Anderson committed
127

128 129 130 131
        # 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
132
        cv2.waitKey(20)  # Required or else video stream doesn't really render.
Kyle Anderson's avatar
Kyle Anderson committed
133

134 135 136 137 138
    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
139 140 141
    return recognized_user


Kyle Anderson's avatar
Kyle Anderson committed
142
def recognize_user_from_database(database_loc: str = common.DATABASE_LOC, encoding_model: str = "hog",
Kyle Anderson's avatar
Kyle Anderson committed
143
                                 image_flip: int = None, draw_rectangles: bool = False):
Kyle Anderson's avatar
Kyle Anderson committed
144 145 146 147 148 149 150 151
    """
    Recognizes a user
    :param database_loc: The database containing the face encodings for users.
    :param encoding_model: The encoding model to be used for recognition.
    :param image_flip: The type of image flip to be applied to the image, if it will be upside-down or horizontally inverted.
    :param draw_rectangles: True to draw the rectangles to the screen, false otherwise.
    :return: The recognized user's id, or None if no user was recognized.
    """
Kyle Anderson's avatar
Kyle Anderson committed
152 153 154
    return recognize_user(data_handler.load_database(database_loc), encoding_model=encoding_model,
                          image_flip=image_flip,
                          draw_rectangles=draw_rectangles)
Kyle Anderson's avatar
Kyle Anderson committed
155 156


157 158
# If this program is the main program, authenticate the user.
if __name__ == "__main__":
Kyle Anderson's avatar
Kyle Anderson committed
159
    import argparse
160 161 162

    parser = argparse.ArgumentParser(description="Facial Identification Options")
    parser.add_argument("--encodings", "-e", type=str, help="File location of facial encodings.", required=False,
Kyle Anderson's avatar
Kyle Anderson committed
163
                        default=None)
164 165
    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,
Kyle Anderson's avatar
Kyle Anderson committed
166
                        default=None, choices=["cnn", "hog"])
167
    parser.add_argument("--flip", "-f", type=int,
Kyle Anderson's avatar
Kyle Anderson committed
168 169
                        help="Whether or not to flip the image vertically or horizontally. 0 to flip horizontally, "
                             "1 to flip vertically.",
170 171 172 173
                        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
174 175 176

    args_dict = {}
    if args.encodings is not None:
177
        args_dict["database_loc"] = args.encodings
Kyle Anderson's avatar
Kyle Anderson committed
178 179 180 181 182
    if args.model is not None:
        args_dict["encoding_model"] = args.model

    user = recognize_user_from_database(**args_dict, image_flip=args.flip,
                                        draw_rectangles=args.show)
183 184
    if user:
        print(f"Recognized user {user}.")