├── .gitignore ├── LICENSE ├── README.md ├── aws_face_recognizer.py ├── build_ui.sh ├── data ├── face_recognition_demo_1.png ├── face_recognition_demo_2.png └── face_recognition_demo_3.png ├── edge_face_recognizer.py ├── face_recognition_app.py ├── face_recognizer.py ├── face_registration_view.py ├── face_registry_view.py ├── gui ├── create_registry.ui ├── create_registry_ui.py ├── delete_registry.ui ├── delete_registry_ui.py ├── face_recognition.ui ├── face_recognition_ui.py ├── face_registration_view.ui ├── face_registration_view_ui.py ├── face_registry_view.ui └── face_registry_view_ui.py ├── loggers.py ├── main.py ├── requirements.txt └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gopalakrishna Hegde, AIMLedge Pte Ltd, Singapore. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Face recognition application using AWS Rekognition and PyQt5 2 | This is a simple desktop application for face recognition. It uses AWS Rekognition APIs 3 | for the face recognition task. We plan to add different recognition 4 | strategies that will run entirely on the local PC as opposed to sending 5 | video frames to the cloud and getting the results back. Trust me, the method of using cloud APIs is 6 | not only slow but also expensive for a real-time 24x7 kind of application! 7 | 8 | The application is built on top of PyQt5 framework to provide simple 9 | UI to register faces and create face albums 10 | 11 | The application is tested working on Python 3.5.2 running on Ubuntu 16.04LTS 12 | 13 | You can find more details on the implementation and working of the application 14 | in [this blog series]() - **upcoming** 15 | 16 | # Setup 17 | 1. Install python packages. You can also create a virtualenv in order not to 18 | mess up with your other development setup. 19 | ```bash 20 | pip3 install -r requirements.txt 21 | ``` 22 | 2. Create AWS account and create a role and include the credentials in ~/.aws/credentials file. 23 | See the example credentials file below. 24 | ```text 25 | [default] 26 | aws_access_key_id = 27 | aws_secret_access_key = 28 | 29 | ``` 30 | 3. Also, configure your AWS region in ~/.aws/config file as below. 31 | ```text 32 | [default] 33 | region = 34 | 35 | ``` 36 | 37 | 4. [Optional] The Qt Designer UI files can be built using build_ui.sh. This is 38 | needed only if you modify the UI files. Run the following command to generate 39 | python files from UI files. 40 | ```bash 41 | ./build_ui.sh 42 | ``` 43 | 44 | That's it we are good to run the application! 45 | 46 | # Running 47 | Simply run `main.py` 48 | ```bash 49 | python main.py 50 | ``` 51 | 52 | # Demo 53 | | | | | 54 | |:-------------------------:|:-------------------------:|:-------------------------:| 55 | |![](./data/face_recognition_demo_1.png)|![](./data/face_recognition_demo_2.png)|![](./data/face_recognition_demo_3.png)| 56 | 57 | 58 | # Using the application 59 | You need to register target faces that you want to recognize. The registered faces are stored in 60 | albums. You need to create an album to start registering the faces. You can create more than one 61 | albums such as `Family`, `Office` etc. 62 | 63 | ## Creating face album 64 | **File -> Add album** 65 | Give a unique name and save. 66 | 67 | ## Registering face/s into an album 68 | After creating album, you can register one or more face with the created album. 69 | 70 | **File -> Register faces** 71 | 72 | While registering faces, make sure that the picture contains only one face. If there are more than 73 | one faces, the largest face will be registered with the name provided. 74 | 75 | ## Running the face recognition on Webcam video stream 76 | From the main window, you can choose the face album on which you want to run the recognition. 77 | If there is only one album, it will be chosen by default. 78 | 79 | Use **Start** and **Stop** button to control the application. 80 | 81 | ## Deleting a face album 82 | You can delete the face album with 83 | 84 | **File -> Delete album** 85 | 86 | Note that all the faces stored in the album along with other metadata will be lost after deleting. 87 | -------------------------------------------------------------------------------- /aws_face_recognizer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2018, AIMLedge Pte, Ltd. 3 | All rights reserved. 4 | 5 | """ 6 | from face_recognizer import FaceRecognizer, logger 7 | import boto3 8 | from botocore.exceptions import * 9 | from PIL import Image 10 | import io 11 | import cv2 12 | 13 | 14 | def opencv_image_to_binary_image(image, format='JPEG'): 15 | """ 16 | 17 | :param image: np.ndarray containing BGR image. 18 | :param format: Target encoding format. 19 | :return: 20 | """ 21 | pil_image = Image.fromarray(image) 22 | stream = io.BytesIO() 23 | pil_image.save(stream, format=format) 24 | binary_image = stream.getvalue() 25 | return binary_image 26 | 27 | 28 | class AwsFaceRecognizer(FaceRecognizer): 29 | 30 | def __init__(self): 31 | FaceRecognizer.__init__(self) 32 | logger.info('Creating AWS Rekognition client.') 33 | self._aws_client = boto3.client('rekognition') 34 | self._face_registries = self._get_aws_collections() 35 | self._active_face_registry = None 36 | 37 | # Holds current registry details 38 | self._registry_faces = [] 39 | self._registry_face_names = [] 40 | self._registry_face_ids = [] 41 | 42 | self._detection_attributes = ['DEFAULT'] 43 | self._detection_threshold = 80.0 44 | self._matching_threshold = 70.0 45 | # Enabling this will get more facial attributes such as age, gender. 46 | # self._detection_attributes = ['DEFAULT', 'ALL'] 47 | logger.info('Created face recognizer.') 48 | logger.info('Existing face registries {}'.format(self._face_registries)) 49 | 50 | def create_face_registry(self, registry_name): 51 | if registry_name in self._face_registries: 52 | logger.info('Face registry already present. Not creating again') 53 | return registry_name 54 | try: 55 | resp = self._aws_client.create_collection(CollectionId=registry_name) 56 | logger.debug('Collection ARN: ' + resp['CollectionArn']) 57 | logger.debug('Status code: ' + str(resp['StatusCode'])) 58 | logger.info('Created face registry {}'.format(registry_name)) 59 | self._face_registries.append(registry_name) 60 | return registry_name 61 | except Exception as e: 62 | logger.fatal(e) 63 | return None 64 | 65 | def delete_face_registry(self, registry_name): 66 | try: 67 | if registry_name not in self._face_registries: 68 | logger.warning('Looks like there is no such registry to delete.' 69 | 'Still trying with AWS'.format(registry_name)) 70 | resp = self._aws_client.delete_collection(CollectionId=registry_name) 71 | if registry_name in self._face_registries: 72 | self._face_registries.remove(registry_name) 73 | status_code = resp['StatusCode'] 74 | except ClientError as e: 75 | if e.response['Error']['Code'] == 'ResourceNotFoundException': 76 | logger.warning('Registry {} not found'.format(registry_name)) 77 | else: 78 | logger.error('Error occured: {}'.format(e.response['Error']['Message'])) 79 | status_code = e.response['ResponseMetadata']['HTTPStatusCode'] 80 | logger.info('Delete face registry status: {}'.format(status_code)) 81 | 82 | def get_active_face_registry(self): 83 | return self._active_face_registry 84 | 85 | def set_active_face_registry(self, registry_name): 86 | if registry_name not in self._face_registries: 87 | raise ValueError('Face registry not found {}'.format(registry_name)) 88 | # Nothing to do 89 | logger.info('Setting active face registry to {}'.format(registry_name)) 90 | if self._active_face_registry == registry_name: 91 | return 92 | # TODO: Load the face registry 93 | self._active_face_registry = registry_name 94 | return self._active_face_registry 95 | 96 | def list_face_registries(self): 97 | return self._face_registries 98 | 99 | def face_registry_details(self, registry_name): 100 | if registry_name != self._active_face_registry: 101 | raise NotImplementedError('Only able to give active face registry') 102 | num_faces = len(self._registry_face_ids) 103 | for idx in range(num_faces): 104 | yield self._registry_face_ids[idx], self._registry_face_names[idx], \ 105 | self._registry_faces[idx] 106 | 107 | def register_face(self, registry_name, image, name): 108 | if registry_name not in self._face_registries: 109 | raise ValueError('No such face registry {}'.format(registry_name)) 110 | if isinstance(image, str): 111 | image = cv2.imread(image) 112 | binary_image = opencv_image_to_binary_image(image) 113 | resp = self._aws_client.index_faces(CollectionId=registry_name, 114 | Image={'Bytes': binary_image}, 115 | ExternalImageId=name, 116 | MaxFaces=1, 117 | QualityFilter="AUTO", 118 | DetectionAttributes=['ALL']) 119 | logger.info('Faces registered:') 120 | for face_record in resp['FaceRecords']: 121 | logger.info('Face ID: ' + face_record['Face']['FaceId']) 122 | logger.info('Location: {}'.format(face_record['Face']['BoundingBox'])) 123 | logger.info('Face name: ' + face_record['Face']['ExternalImageId']) 124 | if registry_name == self._active_face_registry: 125 | box = AwsFaceRecognizer._decode_aws_bounding_box( 126 | face_record['Face']['BoundingBox'], image.shape[1], image.shape[0]) 127 | crop = image[box[1]:box[3], box[0]:box[2], :] 128 | self._registry_face_ids.append(face_record['Face']['FaceId']) 129 | self._registry_face_names.append(face_record['Face']['ExternalImageId']) 130 | self._registry_faces.append(crop) 131 | 132 | logger.info('Faces not registered:') 133 | for unindexed_face in resp['UnindexedFaces']: 134 | logger.info('Location: {}'.format( 135 | unindexed_face['FaceDetail']['BoundingBox'])) 136 | logger.info('Reasons:') 137 | for reason in unindexed_face['Reasons']: 138 | logger.info(' ' + reason) 139 | 140 | def recognize_faces(self, image): 141 | # First, detect faces in the image 142 | face_boxes = self._detect_faces(image) 143 | # Crop and search for each face in the active registry 144 | rec_result = [] 145 | for face_box in face_boxes: 146 | box = face_box['box'] 147 | face_crop = image[box[1]:box[3], box[0]:box[2], :] 148 | bin_face_crop = opencv_image_to_binary_image(face_crop) 149 | try: 150 | resp = self._aws_client.search_faces_by_image( 151 | CollectionId=self._active_face_registry, 152 | Image={'Bytes': bin_face_crop}, 153 | MaxFaces=1, 154 | FaceMatchThreshold=self._matching_threshold 155 | ) 156 | if len(resp['FaceMatches']) >= 1: 157 | match = resp['FaceMatches'][0] 158 | rec_result.append({ 159 | 'face_id': match['Face']['FaceId'], 160 | 'face_name': match['Face']['ExternalImageId'], 161 | 'recognition_score': match['Similarity'], 162 | 'box': box, 163 | 'detection_score': face_box['detection_score'] 164 | }) 165 | else: 166 | rec_result.append({ 167 | 'face_id': None, 168 | 'face_name': 'Unknown', 169 | 'recognition_score': None, 170 | 'box': box, 171 | 'detection_score': face_box['detection_score'] 172 | }) 173 | except Exception as e: 174 | logger.warning('Some error occured when searching for the face. ' 175 | 'Skipping this face. {}'.format(e)) 176 | rec_result.append({ 177 | 'face_id': None, 178 | 'face_name': 'Unknown', 179 | 'recognition_score': None, 180 | 'box': box, 181 | 'detection_score': face_box['detection_score'] 182 | }) 183 | return rec_result 184 | 185 | def deregister_face(self, registry_name, face_id): 186 | try: 187 | resp = self._aws_client.delete_faces(CollectionId=registry_name, 188 | FaceIds=[face_id] 189 | ) 190 | assert len(resp['DeletedFaces']) == 1 191 | deleted_face_id = resp['DeletedFaces'][0] 192 | deleted_face_name = None 193 | logger.warning('Deleted face: {}'.format(deleted_face_id)) 194 | if registry_name == self._active_face_registry: 195 | idx = self._registry_face_ids.index(face_id) 196 | deleted_face_name = self._registry_face_names[idx] 197 | del self._registry_face_ids[idx] 198 | del self._registry_face_names[idx] 199 | del self._registry_faces[idx] 200 | return deleted_face_id, deleted_face_name 201 | except Exception as e: 202 | logger.warning('Error occured. Could not de-register face.') 203 | logger.warning(e) 204 | return None, None 205 | 206 | def get_face_name(self, registry_name, face_id): 207 | if registry_name != self._active_face_registry: 208 | raise ValueError('Cannot get face name from inactive registry') 209 | if face_id in self._registry_face_ids: 210 | return self._registry_face_names[self._registry_face_ids.index(face_id)] 211 | else: 212 | return None 213 | 214 | def _get_aws_collections(self): 215 | all_collections = [] 216 | resp = self._aws_client.list_collections(MaxResults=2) 217 | while True: 218 | collections = resp['CollectionIds'] 219 | 220 | for collection in collections: 221 | all_collections.append(collection) 222 | if 'NextToken' in resp: 223 | resp = self._aws_client.list_collections(NextToken=resp['NextToken'], 224 | MaxResults=2) 225 | else: 226 | break 227 | return all_collections 228 | 229 | def _detect_faces(self, image): 230 | binary_image = opencv_image_to_binary_image(image) 231 | face_boxes = [] 232 | try: 233 | resp = self._aws_client.detect_faces( 234 | Image={'Bytes': binary_image}, 235 | Attributes=self._detection_attributes 236 | ) 237 | for face_detail in resp['FaceDetails']: 238 | if face_detail['Confidence'] >= self._detection_threshold: 239 | face_box = AwsFaceRecognizer._decode_aws_bounding_box( 240 | face_detail['BoundingBox'], image.shape[1], image.shape[0]) 241 | score = face_detail['Confidence'] 242 | face_boxes.append({'detection_score': score, 'box': face_box}) 243 | except Exception as e: 244 | logger.warning('Error occured when detecting faces. {}'.format(e)) 245 | return face_boxes 246 | 247 | @staticmethod 248 | def _decode_aws_bounding_box(aws_bouding_box, image_width, 249 | image_height): 250 | """ 251 | 252 | :param aws_bouding_box: 253 | :param image_width: 254 | :param image_height: 255 | :return: 256 | """ 257 | xmin = max(0, int(image_width * aws_bouding_box['Left'])) 258 | ymin = max(0, int((image_height * aws_bouding_box['Top']))) 259 | xmax = min(image_width-1, xmin + int(image_width * aws_bouding_box['Width'])) 260 | ymax = min(image_height-1, ymin + int(image_height * aws_bouding_box['Height'])) 261 | return [xmin, ymin, xmax, ymax] 262 | -------------------------------------------------------------------------------- /build_ui.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | script_dir=$(dirname $0) 3 | ui_files=$(ls ${script_dir}/gui/*.ui) 4 | for ui_file in ${ui_files[@]} 5 | do 6 | out_file="${ui_file%.*}_ui.py" 7 | echo "Generating $out_file" 8 | pyuic5 -o $out_file -x $ui_file 9 | done 10 | -------------------------------------------------------------------------------- /data/face_recognition_demo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aimledge/pyqt5-face-recognition/485f7207e7b03015901569db25db46326b1f1194/data/face_recognition_demo_1.png -------------------------------------------------------------------------------- /data/face_recognition_demo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aimledge/pyqt5-face-recognition/485f7207e7b03015901569db25db46326b1f1194/data/face_recognition_demo_2.png -------------------------------------------------------------------------------- /data/face_recognition_demo_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aimledge/pyqt5-face-recognition/485f7207e7b03015901569db25db46326b1f1194/data/face_recognition_demo_3.png -------------------------------------------------------------------------------- /edge_face_recognizer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2018, AIMLedge Pte, Ltd. 3 | All rights reserved. 4 | 5 | """ 6 | 7 | import pickle 8 | import os 9 | import face_recognition 10 | import cv2 11 | import numpy as np 12 | from face_recognizer import FaceRecognizer, logger 13 | from scipy.spatial import distance 14 | 15 | FACE_REGISTRY_PATH = os.path.join(os.path.expanduser('~'), 16 | '.config/face-recognition') 17 | 18 | 19 | class EdgeFaceRecognizer(FaceRecognizer): 20 | def __init__(self): 21 | logger.info('Creating edge face recognizer.') 22 | self._registry_faces = [] 23 | self._registry_face_names = [] 24 | self._registry_face_ids = [] 25 | self._registry_face_encodings = [] 26 | self._image_scale = 1.0 27 | self._num_upsamples = 2 28 | self._face_detector_type = 'cnn' # hog or 'cnn' 29 | self._matching_thr = 0.1 30 | if not os.path.exists(FACE_REGISTRY_PATH): 31 | logger.info('Creating face registry at {}'.format(FACE_REGISTRY_PATH)) 32 | os.makedirs(FACE_REGISTRY_PATH) 33 | self._face_registries = self.list_face_registries() 34 | self._active_face_registry = None 35 | 36 | def create_face_registry(self, registry_name): 37 | registry_path = self._get_face_registry_path(registry_name) 38 | if os.path.exists(registry_path): 39 | logger.info('Face registry already present. Not creating again') 40 | else: 41 | self._face_registries.append(registry_name) 42 | open(registry_path, 'w').close() 43 | return registry_name 44 | 45 | def delete_face_registry(self, registry_name): 46 | if registry_name not in self._face_registries: 47 | logger.warning('Looks like there is no such registry to delete.'.format( 48 | registry_name)) 49 | raise ValueError('No such face registry {}'.format(registry_name)) 50 | else: 51 | registry_path = self._get_face_registry_path(registry_name) 52 | os.remove(registry_path) 53 | if registry_name == self._active_face_registry: 54 | self._registry_face_names = [] 55 | self._registry_faces = [] 56 | self._registry_face_ids = [] 57 | self._registry_face_encodings = [] 58 | self._active_face_registry = None 59 | logger.info('Removed face registry {}'.format(registry_name)) 60 | return registry_name 61 | 62 | def get_active_face_registry(self): 63 | return self._active_face_registry 64 | 65 | def set_active_face_registry(self, registry_name): 66 | if registry_name not in self._face_registries: 67 | raise ValueError('Face registry not found {}'.format(registry_name)) 68 | # Nothing to do 69 | logger.info('Setting active face registry to {}'.format(registry_name)) 70 | if self._active_face_registry == registry_name: 71 | return registry_name 72 | self._load_face_registry(registry_name) 73 | self._active_face_registry = registry_name 74 | return self._active_face_registry 75 | 76 | def list_face_registries(self): 77 | registry_names = [] 78 | for reg_path in os.listdir(FACE_REGISTRY_PATH): 79 | file_ext = os.path.basename(reg_path).split('.')[-1] 80 | if file_ext == 'pkl': 81 | registry_names.append(os.path.basename(reg_path).split('.')[0]) 82 | return registry_names 83 | 84 | def face_registry_details(self, registry_name): 85 | if registry_name != self._active_face_registry: 86 | raise NotImplementedError('Only able to give active face registry') 87 | num_faces = len(self._registry_face_ids) 88 | for idx in range(num_faces): 89 | yield self._registry_face_ids[idx], self._registry_face_names[idx], \ 90 | self._registry_faces[idx] 91 | 92 | def register_face(self, registry_name, image, name): 93 | if registry_name not in self._face_registries: 94 | raise ValueError('No such face registry {}'.format(registry_name)) 95 | if isinstance(image, str): 96 | image = face_recognition.load_image_file(image) 97 | 98 | face_boxes = face_recognition.face_locations( 99 | image, number_of_times_to_upsample=self._num_upsamples, model='cnn') 100 | if len(face_boxes) == 0: 101 | logger.warning('No faces found in the image') 102 | return None 103 | elif len(face_boxes) == 1: 104 | target_face_box = face_boxes[0] 105 | logger.info('Found one face in the image {}'.format(target_face_box)) 106 | else: 107 | target_face_box = EdgeFaceRecognizer._get_largest_face(face_boxes) 108 | logger.info('Found multiple faces in the image. Taking the largest one {}' 109 | ''.format(target_face_box)) 110 | 111 | face_crop = image[target_face_box[0]:target_face_box[2], 112 | target_face_box[3]:target_face_box[1], :] 113 | encoding = face_recognition.face_encodings(image, 114 | known_face_locations=[target_face_box]) 115 | new_face_id = self._get_new_face_id() 116 | 117 | if registry_name != self._active_face_registry: 118 | active_reg = self._active_face_registry 119 | self._load_face_registry(registry_name) 120 | assert registry_name == self._active_face_registry 121 | self._registry_faces.append(face_crop) 122 | self._registry_face_names.append(name) 123 | assert len(encoding) == 1 124 | self._registry_face_encodings.append(encoding[0]) 125 | self._registry_face_ids.append(new_face_id) 126 | self._save_active_face_registry() 127 | 128 | # Restore active registry 129 | if registry_name != self._active_face_registry: 130 | self._load_face_registry(active_reg) 131 | 132 | return new_face_id 133 | 134 | def recognize_faces(self, image): 135 | resized_image = cv2.resize(image, (0, 0), fx=self._image_scale, 136 | fy=self._image_scale) 137 | resized_image = resized_image[:, :, ::-1] 138 | # Returned face locations are [top(y1), right(x2), bottom(y2), left(x1)] 139 | face_locations = face_recognition.face_locations( 140 | resized_image, number_of_times_to_upsample=self._num_upsamples, 141 | model=self._face_detector_type) 142 | if len(face_locations) == 0: 143 | return [] 144 | face_encodings = face_recognition.face_encodings(resized_image, 145 | face_locations) 146 | face_encodings = np.array(face_encodings) 147 | # rescale face boxes and re-arrange the points in the (x1, x2, y1, 148 | # y2) order. 149 | detected_face_ids, detected_face_names, recognition_scores = self._match( 150 | face_encodings) 151 | face_locations = (np.array(face_locations) / self._image_scale).astype( 152 | np.int32) 153 | if face_locations.shape[0] > 0: 154 | face_locations[:, [0, 1, 2, 3]] = face_locations[:, [3, 0, 1, 2]] 155 | 156 | face_locations = list(map(tuple, face_locations)) 157 | output = [] 158 | for i in range(len(detected_face_names)): 159 | output.append({'face_id': detected_face_ids[i], 160 | 'face_name': detected_face_names[i], 161 | 'box': face_locations[i], 162 | 'detection_score': 1.0, 163 | 'recognition_score': recognition_scores[i] 164 | } 165 | ) 166 | return output 167 | 168 | def deregister_face(self, registry_name, face_id): 169 | raise NotImplementedError('Feature not implemented.') 170 | 171 | def get_face_name(self, registry_name, face_id): 172 | if registry_name != self._active_face_registry: 173 | raise ValueError('Registry must be active in order to get name') 174 | if face_id in self._registry_face_ids: 175 | return self._registry_face_names[self._registry_face_ids.index(face_id)] 176 | else: 177 | raise ValueError('No such face ID') 178 | 179 | def _find_best_match(self, face_encoding): 180 | found = False 181 | norm_dist = face_recognition.face_distance(self._registry_face_encodings, 182 | face_encoding) 183 | 184 | closest_match_idx = np.argmin(norm_dist) 185 | closest_match_conf = norm_dist[closest_match_idx] 186 | if closest_match_conf <= self._matching_thr: 187 | found = True 188 | return found, closest_match_idx, closest_match_conf 189 | 190 | def _match(self, face_encodings): 191 | assert len(self._registry_face_encodings) > 0 192 | gallary = np.array(self._registry_face_encodings) 193 | dist_mat = distance.cdist(gallary, face_encodings, metric='cosine') 194 | rows = dist_mat.min(axis=1).argsort() 195 | cols = dist_mat.argmin(axis=1)[rows] 196 | 197 | used_rows = set() 198 | used_cols = set() 199 | all_face_ids = [-1 for i in range(len(face_encodings))] 200 | all_face_names = ['Unknown' for i in range(len(face_encodings))] 201 | all_scores = [0 for i in range(len(face_encodings))] 202 | for (row, col) in zip(rows, cols): 203 | if row in used_rows or col in used_cols: 204 | continue 205 | if dist_mat[row, col] > self._matching_thr: 206 | continue 207 | all_face_ids[col] = self._registry_face_ids[row] 208 | all_face_names[col] = self._registry_face_names[row] 209 | all_scores[col] = (1 - dist_mat[row, col]) * 100 210 | used_rows.add(row) 211 | used_cols.add(col) 212 | return all_face_ids, all_face_names, all_scores 213 | 214 | def _get_face_registry_path(self, registry_name): 215 | """ 216 | 217 | :param registry_name: 218 | :return: 219 | """ 220 | return os.path.join(FACE_REGISTRY_PATH, registry_name + '.pkl') 221 | 222 | def _load_face_registry(self, registry_name): 223 | reg_path = self._get_face_registry_path(registry_name) 224 | if os.path.exists(reg_path): 225 | with open(reg_path, 'rb') as f: 226 | try: 227 | data = pickle.load(f) 228 | self._registry_face_encodings = data['face_encodings'] 229 | self._registry_faces = data['face_images'] 230 | self._registry_face_names = data['face_names'] 231 | self._registry_face_ids = data['face_ids'] 232 | self._active_face_registry = registry_name 233 | logger.info('Loaded face registry {}. Set it as active face ' 234 | 'registry'.format(registry_name)) 235 | except Exception as e: 236 | logger.warning('Falied to load the face registry {}'.format(e)) 237 | 238 | def _save_active_face_registry(self): 239 | registry_path = self._get_face_registry_path(self._active_face_registry) 240 | with open(registry_path, 'wb') as f: 241 | pickle.dump({'face_ids': self._registry_face_ids, 242 | 'face_names': self._registry_face_names, 243 | 'face_images': self._registry_faces, 244 | 'face_encodings': self._registry_face_encodings 245 | }, f) 246 | logger.info('Saved active face registry') 247 | 248 | def _get_new_face_id(self): 249 | return len(self._registry_face_ids) 250 | 251 | @staticmethod 252 | def _get_largest_face(face_boxes): 253 | """ 254 | 255 | :param face_boxes: List of (top, right, bottom , left) 256 | :return: 257 | """ 258 | face_areas = [] 259 | for face_box in face_boxes: 260 | area = (face_box[1] - face_box[3]) * (face_box[2] - face_box[0]) 261 | face_areas.append(area) 262 | face_areas = np.array(face_areas) 263 | largest_idx = np.argmax(face_areas) 264 | return face_boxes[largest_idx] 265 | -------------------------------------------------------------------------------- /face_recognition_app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2018, AIMLedge Pte, Ltd. 3 | All rights reserved. 4 | 5 | """ 6 | import sys 7 | from PyQt5.QtWidgets import QAction, QActionGroup, QMainWindow 8 | from PyQt5.QtCore import QByteArray, QTimer 9 | from PyQt5.QtGui import QPixmap, QImage 10 | from PyQt5.QtMultimedia import QCamera 11 | from gui.face_recognition_ui import Ui_FaceRecApp 12 | from face_registration_view import \ 13 | FaceRegistrationView, CreateRegistryView, DeleteRegistryView 14 | from face_registry_view import FaceRegistryView 15 | from aws_face_recognizer import AwsFaceRecognizer 16 | from edge_face_recognizer import EdgeFaceRecognizer 17 | from loggers import face_recognition_app_logger as \ 18 | logger 19 | import cv2 20 | 21 | # Only OpenCV camera type is functional 22 | CAMERA_TYPE = 'OpenCV' # or Qt 23 | 24 | 25 | class FaceRecognitionAppUI(QMainWindow): 26 | def __init__(self, parent=None): 27 | QMainWindow.__init__(self, parent=parent) 28 | self.ui = Ui_FaceRecApp() 29 | 30 | self.ui.setupUi(self) 31 | self.camera = None 32 | self.running = False 33 | # self.face_recognizer = AwsFaceRecognizer() 34 | self.face_recognizer = EdgeFaceRecognizer() 35 | self.populate_face_registry_list() 36 | 37 | if CAMERA_TYPE == 'OpenCV': 38 | self.setup_opencv_camera() 39 | else: 40 | self.setup_camera() 41 | 42 | self.timer = QTimer(self, interval=5) 43 | self.timer.timeout.connect(self.process) 44 | 45 | def populate_face_registry_list(self): 46 | self.ui.faceRegistrySelect.blockSignals(True) 47 | for item in range(self.ui.faceRegistrySelect.count()): 48 | self.ui.faceRegistrySelect.removeItem(item) 49 | for registry in self.face_recognizer.list_face_registries(): 50 | self.ui.faceRegistrySelect.addItem(registry) 51 | self.ui.faceRegistrySelect.blockSignals(False) 52 | 53 | if self.ui.faceRegistrySelect.currentText(): 54 | self.set_current_registry(self.ui.faceRegistrySelect.currentText()) 55 | 56 | def start(self): 57 | self.start_camera() 58 | self.timer.start() 59 | self.running = True 60 | 61 | def pause(self): 62 | pass 63 | 64 | def stop(self): 65 | self.timer.stop() 66 | logger.info('Stopping the app') 67 | self.running = False 68 | self.stop_camera() 69 | 70 | def register_faces(self): 71 | self.stop_camera() 72 | registration_dialog = FaceRegistrationView(parent=self, 73 | face_recognizer=self.face_recognizer) 74 | registration_dialog.exec_() 75 | logger.info('Done registering faces') 76 | self.start_camera() 77 | 78 | def create_registry(self): 79 | dialog = CreateRegistryView(parent=self, face_recognizer=self.face_recognizer) 80 | dialog.exec_() 81 | self.populate_face_registry_list() 82 | 83 | def delete_registry(self): 84 | dialog = DeleteRegistryView(parent=self, face_recognizer=self.face_recognizer) 85 | dialog.exec_() 86 | self.populate_face_registry_list() 87 | 88 | def set_current_registry(self, registry): 89 | assert registry is not None and registry != '' 90 | self.face_recognizer.set_active_face_registry(registry) 91 | 92 | def view_registry(self): 93 | self.stop_camera() 94 | registry_dialog = FaceRegistryView(parent=self, 95 | face_recognizer=self.face_recognizer) 96 | registry_dialog.exec_() 97 | self.start_camera() 98 | 99 | def setup_camera(self): 100 | camera_device = QByteArray() 101 | 102 | video_devices_group = QActionGroup(self) 103 | video_devices_group.setExclusive(True) 104 | 105 | for device in QCamera.availableDevices(): 106 | description = QCamera.deviceDescription(device) 107 | video_device_action = QAction(description, video_devices_group) 108 | video_device_action.setCheckable(True) 109 | video_device_action.setData(device) 110 | 111 | if camera_device.isEmpty(): 112 | camera_device = device 113 | video_device_action.setChecked(True) 114 | 115 | self.ui.menuDevices.addAction(video_device_action) 116 | if camera_device.isEmpty(): 117 | self.camera = QCamera() 118 | else: 119 | self.camera = QCamera(camera_device) 120 | self.camera.setViewfinder(self.ui.cameraViewFinder) 121 | 122 | def setup_opencv_camera(self): 123 | if len(QCamera.availableDevices()) > 0: 124 | camera_addr = str(QCamera.availableDevices()[0]) 125 | self.camera = cv2.VideoCapture(0) 126 | if self.camera.isOpened(): 127 | self.camera.set(cv2.CAP_PROP_BUFFERSIZE, 1) 128 | self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, 640) 129 | self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) 130 | logger.info('Opened the camera {}'.format(camera_addr)) 131 | else: 132 | logger.error('Failed to open the camera {}'.format(camera_addr)) 133 | 134 | def stop_camera(self): 135 | if self.camera is None: 136 | return 137 | if CAMERA_TYPE == 'OpenCV': 138 | self.camera.release() 139 | else: 140 | self.camera.stop() 141 | 142 | def start_camera(self): 143 | if self.camera is None: 144 | return 145 | if CAMERA_TYPE == 'OpenCV': 146 | self.camera.open(0) 147 | else: 148 | self.camera.start() 149 | 150 | def update_frame(self): 151 | read, frame = self.camera.read() 152 | self.display_frame(frame) 153 | 154 | def display_frame(self, frame): 155 | if frame is None: 156 | return 157 | qformat = QImage.Format_Indexed8 158 | if len(frame.shape) == 3: 159 | if frame.shape[2] == 4: 160 | qformat = QImage.Format_RGBA8888 161 | else: 162 | qformat = QImage.Format_RGB888 163 | qt_image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], 164 | qformat) 165 | qt_image = qt_image.rgbSwapped() 166 | self.ui.cameraSurface.setPixmap(QPixmap.fromImage(qt_image)) 167 | self.update() 168 | 169 | def process(self): 170 | assert self.camera is not None 171 | read, frame = self.camera.read() 172 | if not read: 173 | return 174 | recognition_results = self.face_recognizer.recognize_faces(frame) 175 | AwsFaceRecognizer.draw_face_recognitions(frame, recognition_results) 176 | self.display_frame(frame) 177 | 178 | def run_recognizer_with_opencv(self): 179 | self.camera.stop() 180 | cur_registry = self.ui.faceRegistrySelect.currentText() 181 | self.face_recognizer.set_active_face_registry(cur_registry) 182 | cap = cv2.VideoCapture(0) 183 | cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) 184 | if not cap.isOpened(): 185 | logger.warning('Unable to open the webcam') 186 | return 187 | 188 | cv2.namedWindow('Face recognizer', cv2.WINDOW_NORMAL) 189 | count = 0 190 | while True: 191 | read, frame = cap.read() 192 | if not read: 193 | logger.warning('Unable to read video frame') 194 | break 195 | recognition_results = self.face_recognizer.recognize_faces(frame) 196 | count += 1 197 | print('{}'.format(count), end='\r') 198 | sys.stdout.flush() 199 | 200 | AwsFaceRecognizer.draw_face_recognitions(frame, recognition_results) 201 | cv2.imshow('Face recognizer', frame) 202 | key = cv2.waitKey(5) & 255 203 | if key == 27: 204 | break 205 | cv2.destroyAllWindows() 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /face_recognizer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2018, AIMLedge Pte, Ltd. 3 | All rights reserved. 4 | 5 | """ 6 | import abc 7 | import logging 8 | import cv2 9 | 10 | logger = logging.getLogger('Face_Rec') 11 | 12 | 13 | # Face recognizer interface. Different algorithm implementations will 14 | # implement this interface. We are using Strategy design pattern here. 15 | class FaceRecognizer(metaclass=abc.ABCMeta): 16 | def __init__(self): 17 | pass 18 | 19 | @abc.abstractmethod 20 | def create_face_registry(self, registry_name): 21 | """ 22 | 23 | :param registry_name: Face registry name 24 | :return: Created face registry name 25 | """ 26 | 27 | @abc.abstractmethod 28 | def delete_face_registry(self, registry_name): 29 | """ 30 | 31 | :param registry_name: Face registry name to delete 32 | :return: Deleted registry name if there was such registry else raises 33 | ValueError 34 | """ 35 | 36 | @abc.abstractmethod 37 | def get_active_face_registry(self): 38 | """ 39 | 40 | :return: Returns the face registry name if any that is being used for 41 | face recognition. Returns None if no registry is added to the recognizer. 42 | """ 43 | pass 44 | 45 | @abc.abstractmethod 46 | def set_active_face_registry(self, registry_name): 47 | """ 48 | 49 | :param registry_name: Registry name to use for face recognition from now on 50 | :return: Active face registry if the operation is successful else raises 51 | Exception 52 | """ 53 | pass 54 | 55 | @abc.abstractmethod 56 | def list_face_registries(self): 57 | """ 58 | 59 | :return: List of face registries present 60 | """ 61 | pass 62 | 63 | @abc.abstractmethod 64 | def face_registry_details(self, registry_name): 65 | """ 66 | 67 | :param registry_name: Registry name 68 | :return: A generator function that returns (face_id, face_name, 69 | face_image) that are registered in this registry. 70 | """ 71 | pass 72 | 73 | @abc.abstractmethod 74 | def register_face(self, registry_name, image, name): 75 | """ 76 | 77 | :param registry_name: Face registry name to registry face. 78 | :param image: A of np.ndarray representing BGR image or image file. 79 | :param name: Name of the person present in the image. If the image 80 | contains more than one face, then the name must be of the largest face 81 | present. 82 | :return: Image ID for the registered face. 83 | """ 84 | pass 85 | 86 | @abc.abstractmethod 87 | def recognize_faces(self, image): 88 | """ 89 | 90 | :param image: A single np.ndarray representing the target image in the 91 | BGR format OR an image file. 92 | :return: A list of dict of the form {'face_id': str, 'face_name': str, 93 | 'box': [xmin, ymin, xmax, ymax], 'detection_score': float, 94 | 'recognition_score': float } 95 | """ 96 | pass 97 | 98 | @abc.abstractmethod 99 | def deregister_face(self, registry_name, face_id): 100 | """ 101 | 102 | :param registry_name: Registry name from which to deregister face 103 | :param face_id: Face ID to deregister 104 | :return: de-registered (face_id, face_name) if the operation is successful 105 | else (None, None) 106 | """ 107 | pass 108 | 109 | @abc.abstractmethod 110 | def get_face_name(self, registry_name, face_id): 111 | """ 112 | 113 | :param registry_name: Face registry name 114 | :param face_id: Face ID for which face name to be given 115 | :return: Face name if the face_id is registered else None 116 | """ 117 | pass 118 | 119 | @staticmethod 120 | def draw_face_recognitions(image, recognition_data): 121 | """ 122 | 123 | :param image: np.ndarray containing BGR image 124 | :param recognition_data: Data returned by self.recognize_faces 125 | :return: 126 | """ 127 | color = (255, 0, 0) 128 | for face_data in recognition_data: 129 | box = face_data['box'] 130 | if face_data['face_id'] is None: 131 | label = '{:s}'.format(face_data['face_name']) 132 | else: 133 | label = '{:s} : {:d}%'.format(face_data['face_name'], 134 | int(face_data['recognition_score'])) 135 | c1 = (int(box[0]), int(box[1])) 136 | c2 = (int(box[2]), int(box[3])) 137 | cv2.rectangle(image, c1, c2, color, thickness=2) 138 | text_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_PLAIN, 2, 1)[0] 139 | 140 | c2 = c1[0] + text_size[0] + 10, c1[1] + text_size[1] + 5 141 | cv2.rectangle(image, c1, c2, color, -1) 142 | cv2.putText(image, label, (c1[0], c1[1] + text_size[1] + 10), 143 | cv2.FONT_HERSHEY_PLAIN, 2, [225, 255, 255], 1) 144 | -------------------------------------------------------------------------------- /face_registration_view.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2018, AIMLedge Pte, Ltd. 3 | All rights reserved. 4 | 5 | """ 6 | import os 7 | from PyQt5.QtWidgets import QDialog, QApplication, QMessageBox 8 | from PyQt5.QtGui import QPixmap 9 | from PyQt5.QtCore import QByteArray, Qt 10 | from PyQt5.QtMultimedia import QCamera, QCameraImageCapture 11 | from gui.face_registration_view_ui import Ui_FaceRegistrationUI 12 | from gui.create_registry_ui import Ui_CreateRegistry 13 | from gui.delete_registry_ui import Ui_DeleteRegistry 14 | from loggers import face_recognition_app_logger as logger 15 | 16 | 17 | class CreateRegistryView(QDialog): 18 | def __init__(self, parent=None, face_recognizer=None): 19 | QDialog.__init__(self, parent=parent) 20 | self.msg_box = QMessageBox() 21 | self.ui = Ui_CreateRegistry() 22 | self.ui.setupUi(self) 23 | self.registry_name = None 24 | self.face_recognizer = face_recognizer 25 | 26 | def create_registry(self): 27 | name = self.ui.registryNameInput.text() 28 | if name == '': 29 | self.msg_box.setIcon(QMessageBox.Critical) 30 | self.msg_box.warning(self, 'Error', 'Album name cannot be empty!') 31 | return 32 | 33 | name = name.replace(' ', '_') 34 | confirm_msg = 'Are you sure that you want to create album {}?'.format(name) 35 | reply = QMessageBox.question(self, 'Create album', confirm_msg, 36 | QMessageBox.Yes, QMessageBox.No) 37 | if reply == QMessageBox.Yes: 38 | print('Creating registry {}'.format(name)) 39 | registry = self.face_recognizer.create_face_registry(name) 40 | if registry is not None: 41 | logger.info('Successfully created face registry {}'.format(registry)) 42 | self.close() 43 | 44 | def set_registry_name(self, name): 45 | self.registry_name = name 46 | 47 | 48 | class DeleteRegistryView(QDialog): 49 | def __init__(self, parent=None, face_recognizer=None): 50 | QDialog.__init__(self, parent=parent) 51 | self.face_recognizer = face_recognizer 52 | 53 | self.ui = Ui_DeleteRegistry() 54 | self.ui.setupUi(self) 55 | for registry in self.face_recognizer.list_face_registries(): 56 | self.ui.faceRegistryBox.addItem(registry) 57 | if len(self.face_recognizer.list_face_registries()) == 0: 58 | self.ui.deleteButton.setEnabled(False) 59 | 60 | def delete_registry(self): 61 | current_registry = self.ui.faceRegistryBox.currentText() 62 | confirm_msg = 'Are you sure that you want to delete {}'.format( 63 | current_registry) 64 | reply = QMessageBox.question(self, 'Delete album', confirm_msg, 65 | QMessageBox.Yes, QMessageBox.No) 66 | if reply == QMessageBox.Yes: 67 | logger.warning('Deleting {}'.format(current_registry)) 68 | self.face_recognizer.delete_face_registry(current_registry) 69 | self.close() 70 | 71 | 72 | class FaceRegistrationView(QDialog): 73 | def __init__(self, parent=None, face_recognizer=None): 74 | QDialog.__init__(self, parent=parent) 75 | self.face_recognizer = face_recognizer 76 | self.ui = Ui_FaceRegistrationUI() 77 | 78 | self.ui.setupUi(self) 79 | 80 | # Add existing face albums to the combobox 81 | # FIXME: need to get this list from the face recognizer 82 | for registry in self.face_recognizer.list_face_registries(): 83 | self.ui.registryNameSelect.addItem(registry) 84 | 85 | self.ui.retakeButton.setEnabled(False) 86 | self.cur_face_name = None 87 | self.cur_face_registry = self.ui.registryNameSelect.currentText() 88 | self.camera = None 89 | self.image_capture = None 90 | self.cur_saved_image_path = None 91 | self.setup_camera() 92 | 93 | def setup_camera(self): 94 | camera_device = QByteArray() 95 | 96 | for device in QCamera.availableDevices(): 97 | if camera_device.isEmpty(): 98 | camera_device = device 99 | if camera_device.isEmpty(): 100 | self.camera = QCamera() 101 | else: 102 | self.camera = QCamera(camera_device) 103 | 104 | self.image_capture = QCameraImageCapture(self.camera) 105 | self.image_capture.readyForCaptureChanged.connect(self.ready_for_capture) 106 | self.image_capture.imageCaptured.connect(self.process_captured_image) 107 | self.image_capture.imageSaved.connect(self.image_saved) 108 | 109 | self.camera.setViewfinder(self.ui.viewFinder) 110 | self.camera.start() 111 | 112 | def ready_for_capture(self, ready): 113 | self.ui.captureButton.setEnabled(ready) 114 | 115 | def process_captured_image(self, request_id, image): 116 | scaled_image = image.scaled(self.ui.viewFinder.size(), 117 | Qt.KeepAspectRatio, Qt.SmoothTransformation) 118 | self.ui.picturePreview.setPixmap(QPixmap.fromImage(scaled_image)) 119 | self.show_captured_image() 120 | 121 | def image_saved(self, id, file_path): 122 | self.cur_saved_image_path = file_path 123 | logger.info('Image saved at {}'.format(file_path)) 124 | 125 | def handle_face_name(self, name): 126 | self.cur_face_name = name 127 | 128 | def register_face(self): 129 | name = self.ui.nameInput.text() 130 | if name == '': 131 | msg_box = QMessageBox() 132 | msg_box.setIcon(QMessageBox.Critical) 133 | msg_box.warning(self, 'Error', 'Person name cannot be empty!') 134 | return 135 | 136 | confirm_msg = 'Register face of {:s} into {:s}?'.format(name, 137 | self.cur_face_registry) 138 | reply = QMessageBox.question(self, 'Register face', confirm_msg, 139 | QMessageBox.No, QMessageBox.Yes) 140 | if reply == QMessageBox.Yes: 141 | logger.info('Registering {:s}'.format(name)) 142 | try: 143 | self.face_recognizer.register_face(self.cur_face_registry, 144 | self.cur_saved_image_path, name) 145 | self.parent().ui.statusbar.showMessage( 146 | 'Successfully registered the face', 2000) 147 | 148 | except Exception as e: 149 | msg_box = QMessageBox() 150 | msg_box.setIcon(QMessageBox.Critical) 151 | msg_box.warning(self, 'Error', str(e)) 152 | # Clean up the captured image and show the video stream again 153 | self.ui.nameInput.clear() 154 | # self.ui.nameInput.setText('') 155 | self.retake_picture() 156 | else: 157 | pass 158 | 159 | def retake_picture(self): 160 | 161 | self.delete_current_picture() 162 | self.ui.captureButton.setEnabled(True) 163 | self.ui.retakeButton.setEnabled(False) 164 | self.show_video_stream() 165 | 166 | def capture_picture(self): 167 | self.image_capture.capture() 168 | self.ui.captureButton.setEnabled(False) 169 | self.ui.retakeButton.setEnabled(True) 170 | 171 | def show_captured_image(self): 172 | self.ui.stackedWidget.setCurrentIndex(1) 173 | 174 | def show_video_stream(self): 175 | self.ui.stackedWidget.setCurrentIndex(0) 176 | 177 | def set_current_face_registry(self, registry_name): 178 | self.cur_face_registry = registry_name 179 | 180 | def closeEvent(self, event): 181 | self.camera.stop() 182 | self.delete_current_picture() 183 | 184 | def accept(self): 185 | self.camera.stop() 186 | self.delete_current_picture() 187 | QDialog.reject(self) 188 | 189 | def reject(self): 190 | self.camera.stop() 191 | logger.info('Cleaning up captured images...') 192 | self.delete_current_picture() 193 | QDialog.reject(self) 194 | 195 | def delete_current_picture(self): 196 | if self.cur_saved_image_path is not None: 197 | if os.path.exists(self.cur_saved_image_path): 198 | os.remove(self.cur_saved_image_path) 199 | logger.info('Deleted {}'.format(self.cur_saved_image_path)) 200 | self.cur_saved_image_path = None 201 | 202 | 203 | if __name__ == "__main__": 204 | import sys 205 | app = QApplication(sys.argv) 206 | dialog = FaceRegistrationView() 207 | dialog.show() 208 | sys.exit(app.exec_()) 209 | -------------------------------------------------------------------------------- /face_registry_view.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2018, AIMLedge Pte, Ltd. 3 | All rights reserved. 4 | 5 | """ 6 | 7 | from PyQt5.QtWidgets import QDialog, QApplication 8 | from gui.face_registry_view_ui import Ui_FaceRegistryUI 9 | 10 | SAMPLE_FACE_REGISTRIES = ['Demo_face_1', 'Demo_face_2'] 11 | 12 | 13 | class FaceRegistryView(QDialog): 14 | def __init__(self, parent=None, face_recognizer=None): 15 | QDialog.__init__(self, parent=parent) 16 | 17 | self.ui = Ui_FaceRegistryUI() 18 | self.ui.setupUi(self) 19 | self.cur_registry = None 20 | self.face_recognizer = face_recognizer 21 | 22 | for registry in SAMPLE_FACE_REGISTRIES: 23 | self.ui.registryNameSelect.addItem(registry) 24 | 25 | def load_face_registry(self): 26 | print('Loading face registry {} ...'.format(self.cur_registry)) 27 | 28 | def show_next_face(self): 29 | print('Next face') 30 | 31 | def show_prev_face(self): 32 | print('Prev face') 33 | 34 | def set_current_face_registry(self, registry): 35 | self.cur_registry = registry 36 | print(registry) 37 | 38 | 39 | 40 | if __name__ == "__main__": 41 | import sys 42 | app = QApplication(sys.argv) 43 | registry_view = FaceRegistryView() 44 | registry_view.show() 45 | sys.exit(app.exec_()) 46 | -------------------------------------------------------------------------------- /gui/create_registry.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | CreateRegistry 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 147 11 | 12 | 13 | 14 | New Album 15 | 16 | 17 | 18 | .. 19 | 20 | 21 | 22 | 23 | 40 24 | 100 25 | 341 26 | 32 27 | 28 | 29 | 30 | Qt::Horizontal 31 | 32 | 33 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 34 | 35 | 36 | 37 | 38 | 39 | 40 40 | 30 41 | 341 42 | 29 43 | 44 | 45 | 46 | 47 | QLayout::SetNoConstraint 48 | 49 | 50 | 51 | 52 | Album Name 53 | 54 | 55 | registryNameInput 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | layoutWidget 65 | createButton 66 | 67 | 68 | 69 | 70 | createButton 71 | accepted() 72 | CreateRegistry 73 | create_registry() 74 | 75 | 76 | 349 77 | 146 78 | 79 | 80 | 367 81 | 98 82 | 83 | 84 | 85 | 86 | createButton 87 | rejected() 88 | CreateRegistry 89 | reject() 90 | 91 | 92 | 283 93 | 151 94 | 95 | 96 | 281 97 | 99 98 | 99 | 100 | 101 | 102 | registryNameInput 103 | textChanged(QString) 104 | CreateRegistry 105 | set_registry_name(QString) 106 | 107 | 108 | 312 109 | 44 110 | 111 | 112 | 317 113 | 74 114 | 115 | 116 | 117 | 118 | 119 | create_registry() 120 | set_registry_name(QString) 121 | 122 | 123 | -------------------------------------------------------------------------------- /gui/create_registry_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file './gui/create_registry.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_CreateRegistry(object): 13 | def setupUi(self, CreateRegistry): 14 | CreateRegistry.setObjectName("CreateRegistry") 15 | CreateRegistry.resize(400, 147) 16 | icon = QtGui.QIcon.fromTheme("address-book-new") 17 | CreateRegistry.setWindowIcon(icon) 18 | self.createButton = QtWidgets.QDialogButtonBox(CreateRegistry) 19 | self.createButton.setGeometry(QtCore.QRect(40, 100, 341, 32)) 20 | self.createButton.setOrientation(QtCore.Qt.Horizontal) 21 | self.createButton.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) 22 | self.createButton.setObjectName("createButton") 23 | self.layoutWidget = QtWidgets.QWidget(CreateRegistry) 24 | self.layoutWidget.setGeometry(QtCore.QRect(40, 30, 341, 29)) 25 | self.layoutWidget.setObjectName("layoutWidget") 26 | self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) 27 | self.gridLayout.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) 28 | self.gridLayout.setContentsMargins(0, 0, 0, 0) 29 | self.gridLayout.setObjectName("gridLayout") 30 | self.label = QtWidgets.QLabel(self.layoutWidget) 31 | self.label.setObjectName("label") 32 | self.gridLayout.addWidget(self.label, 0, 0, 1, 1) 33 | self.registryNameInput = QtWidgets.QLineEdit(self.layoutWidget) 34 | self.registryNameInput.setObjectName("registryNameInput") 35 | self.gridLayout.addWidget(self.registryNameInput, 0, 1, 1, 1) 36 | self.layoutWidget.raise_() 37 | self.createButton.raise_() 38 | self.label.setBuddy(self.registryNameInput) 39 | 40 | self.retranslateUi(CreateRegistry) 41 | self.createButton.accepted.connect(CreateRegistry.create_registry) 42 | self.createButton.rejected.connect(CreateRegistry.reject) 43 | self.registryNameInput.textChanged['QString'].connect(CreateRegistry.set_registry_name) 44 | QtCore.QMetaObject.connectSlotsByName(CreateRegistry) 45 | 46 | def retranslateUi(self, CreateRegistry): 47 | _translate = QtCore.QCoreApplication.translate 48 | CreateRegistry.setWindowTitle(_translate("CreateRegistry", "New Album")) 49 | self.label.setText(_translate("CreateRegistry", "Album Name")) 50 | 51 | 52 | 53 | 54 | if __name__ == "__main__": 55 | import sys 56 | app = QtWidgets.QApplication(sys.argv) 57 | CreateRegistry = QtWidgets.QDialog() 58 | ui = Ui_CreateRegistry() 59 | ui.setupUi(CreateRegistry) 60 | CreateRegistry.show() 61 | sys.exit(app.exec_()) 62 | -------------------------------------------------------------------------------- /gui/delete_registry.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DeleteRegistry 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 147 11 | 12 | 13 | 14 | Delete Album 15 | 16 | 17 | 18 | .. 19 | 20 | 21 | 22 | 23 | 280 24 | 100 25 | 85 26 | 27 27 | 28 | 29 | 30 | Delete 31 | 32 | 33 | 34 | 35 | 36 | 60 37 | 40 38 | 74 39 | 17 40 | 41 | 42 | 43 | Album Name 44 | 45 | 46 | 47 | 48 | 49 | 170 50 | 40 51 | 201 52 | 27 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | deleteButton 61 | clicked() 62 | DeleteRegistry 63 | delete_registry() 64 | 65 | 66 | 347 67 | 112 68 | 69 | 70 | 236 71 | 92 72 | 73 | 74 | 75 | 76 | 77 | delete_registry() 78 | set_registry_name(QString) 79 | 80 | 81 | -------------------------------------------------------------------------------- /gui/delete_registry_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file './gui/delete_registry.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_DeleteRegistry(object): 13 | def setupUi(self, DeleteRegistry): 14 | DeleteRegistry.setObjectName("DeleteRegistry") 15 | DeleteRegistry.resize(400, 147) 16 | icon = QtGui.QIcon.fromTheme("edit-delete") 17 | DeleteRegistry.setWindowIcon(icon) 18 | self.deleteButton = QtWidgets.QPushButton(DeleteRegistry) 19 | self.deleteButton.setGeometry(QtCore.QRect(280, 100, 85, 27)) 20 | self.deleteButton.setObjectName("deleteButton") 21 | self.label = QtWidgets.QLabel(DeleteRegistry) 22 | self.label.setGeometry(QtCore.QRect(60, 40, 74, 17)) 23 | self.label.setObjectName("label") 24 | self.faceRegistryBox = QtWidgets.QComboBox(DeleteRegistry) 25 | self.faceRegistryBox.setGeometry(QtCore.QRect(170, 40, 201, 27)) 26 | self.faceRegistryBox.setObjectName("faceRegistryBox") 27 | 28 | self.retranslateUi(DeleteRegistry) 29 | self.deleteButton.clicked.connect(DeleteRegistry.delete_registry) 30 | QtCore.QMetaObject.connectSlotsByName(DeleteRegistry) 31 | 32 | def retranslateUi(self, DeleteRegistry): 33 | _translate = QtCore.QCoreApplication.translate 34 | DeleteRegistry.setWindowTitle(_translate("DeleteRegistry", "Delete Album")) 35 | self.deleteButton.setText(_translate("DeleteRegistry", "Delete")) 36 | self.label.setText(_translate("DeleteRegistry", "Album Name")) 37 | 38 | 39 | 40 | 41 | if __name__ == "__main__": 42 | import sys 43 | app = QtWidgets.QApplication(sys.argv) 44 | DeleteRegistry = QtWidgets.QDialog() 45 | ui = Ui_DeleteRegistry() 46 | ui.setupUi(DeleteRegistry) 47 | DeleteRegistry.show() 48 | sys.exit(app.exec_()) 49 | -------------------------------------------------------------------------------- /gui/face_recognition.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FaceRecApp 4 | 5 | 6 | 7 | 0 8 | 0 9 | 886 10 | 653 11 | 12 | 13 | 14 | Face Recognition 15 | 16 | 17 | 18 | .. 19 | 20 | 21 | 22 | 23 | 24 | 20 25 | 60 26 | 661 27 | 501 28 | 29 | 30 | 31 | false 32 | 33 | 34 | 35 | 36 | 37 | 38 | 640 39 | 480 40 | 41 | 42 | 43 | 44 | 640 45 | 480 46 | 47 | 48 | 49 | QFrame::Box 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 720 62 | 160 63 | 121 64 | 281 65 | 66 | 67 | 68 | Controls 69 | 70 | 71 | 72 | 73 | 74 | Start 75 | 76 | 77 | 78 | 79 | 80 | 81 | Pause 82 | 83 | 84 | 85 | 86 | 87 | 88 | Qt::Vertical 89 | 90 | 91 | 92 | 20 93 | 40 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | Stop 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 690 111 | 80 112 | 181 113 | 27 114 | 115 | 116 | 117 | Qt::Horizontal 118 | 119 | 120 | 121 | Album 122 | 123 | 124 | faceRegistrySelect 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 0 134 | 0 135 | 886 136 | 27 137 | 138 | 139 | 140 | 141 | File 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | Devices 152 | 153 | 154 | 155 | 156 | View 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | Exit 168 | 169 | 170 | 171 | 172 | Start Camera 173 | 174 | 175 | 176 | 177 | Stop Camera 178 | 179 | 180 | 181 | 182 | Register faces 183 | 184 | 185 | 186 | 187 | Face registry 188 | 189 | 190 | 191 | 192 | Add album 193 | 194 | 195 | 196 | 197 | Delete album 198 | 199 | 200 | 201 | 202 | 203 | 204 | startButton 205 | clicked() 206 | FaceRecApp 207 | start() 208 | 209 | 210 | 790 211 | 218 212 | 213 | 214 | 781 215 | 64 216 | 217 | 218 | 219 | 220 | pauseButton 221 | clicked() 222 | FaceRecApp 223 | pause() 224 | 225 | 226 | 820 227 | 261 228 | 229 | 230 | 846 231 | 121 232 | 233 | 234 | 235 | 236 | stopButton 237 | clicked() 238 | FaceRecApp 239 | stop() 240 | 241 | 242 | 803 243 | 442 244 | 245 | 246 | 717 247 | 117 248 | 249 | 250 | 251 | 252 | actionRegisterFaces 253 | triggered() 254 | FaceRecApp 255 | register_faces() 256 | 257 | 258 | -1 259 | -1 260 | 261 | 262 | 442 263 | 326 264 | 265 | 266 | 267 | 268 | actionAddAlbum 269 | triggered() 270 | FaceRecApp 271 | create_registry() 272 | 273 | 274 | -1 275 | -1 276 | 277 | 278 | 442 279 | 326 280 | 281 | 282 | 283 | 284 | actionViewFaceRegistry 285 | triggered() 286 | FaceRecApp 287 | view_registry() 288 | 289 | 290 | -1 291 | -1 292 | 293 | 294 | 442 295 | 326 296 | 297 | 298 | 299 | 300 | actionExit 301 | triggered() 302 | FaceRecApp 303 | close() 304 | 305 | 306 | -1 307 | -1 308 | 309 | 310 | 442 311 | 326 312 | 313 | 314 | 315 | 316 | actionDeleteAlbum 317 | triggered() 318 | FaceRecApp 319 | delete_registry() 320 | 321 | 322 | -1 323 | -1 324 | 325 | 326 | 442 327 | 326 328 | 329 | 330 | 331 | 332 | faceRegistrySelect 333 | currentIndexChanged(QString) 334 | FaceRecApp 335 | set_current_registry(QString) 336 | 337 | 338 | 824 339 | 115 340 | 341 | 342 | 844 343 | 46 344 | 345 | 346 | 347 | 348 | 349 | pause() 350 | stop() 351 | start() 352 | register_faces() 353 | create_registry() 354 | view_registry() 355 | delete_registry() 356 | set_current_registry(QString) 357 | 358 | 359 | -------------------------------------------------------------------------------- /gui/face_recognition_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file './gui/face_recognition.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_FaceRecApp(object): 13 | def setupUi(self, FaceRecApp): 14 | FaceRecApp.setObjectName("FaceRecApp") 15 | FaceRecApp.resize(886, 653) 16 | icon = QtGui.QIcon.fromTheme("camera-video") 17 | FaceRecApp.setWindowIcon(icon) 18 | self.centralwidget = QtWidgets.QWidget(FaceRecApp) 19 | self.centralwidget.setObjectName("centralwidget") 20 | self.viewFinderPage = QtWidgets.QWidget(self.centralwidget) 21 | self.viewFinderPage.setGeometry(QtCore.QRect(20, 60, 661, 501)) 22 | self.viewFinderPage.setAutoFillBackground(False) 23 | self.viewFinderPage.setObjectName("viewFinderPage") 24 | self.gridLayout = QtWidgets.QGridLayout(self.viewFinderPage) 25 | self.gridLayout.setContentsMargins(0, 0, 0, 0) 26 | self.gridLayout.setObjectName("gridLayout") 27 | self.cameraSurface = QtWidgets.QLabel(self.viewFinderPage) 28 | self.cameraSurface.setMinimumSize(QtCore.QSize(640, 480)) 29 | self.cameraSurface.setMaximumSize(QtCore.QSize(640, 480)) 30 | self.cameraSurface.setFrameShape(QtWidgets.QFrame.Box) 31 | self.cameraSurface.setText("") 32 | self.cameraSurface.setObjectName("cameraSurface") 33 | self.gridLayout.addWidget(self.cameraSurface, 0, 1, 1, 1) 34 | self.groupBox = QtWidgets.QGroupBox(self.centralwidget) 35 | self.groupBox.setGeometry(QtCore.QRect(720, 160, 121, 281)) 36 | self.groupBox.setObjectName("groupBox") 37 | self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox) 38 | self.verticalLayout.setObjectName("verticalLayout") 39 | self.startButton = QtWidgets.QPushButton(self.groupBox) 40 | self.startButton.setObjectName("startButton") 41 | self.verticalLayout.addWidget(self.startButton) 42 | self.pauseButton = QtWidgets.QPushButton(self.groupBox) 43 | self.pauseButton.setObjectName("pauseButton") 44 | self.verticalLayout.addWidget(self.pauseButton) 45 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 46 | self.verticalLayout.addItem(spacerItem) 47 | self.stopButton = QtWidgets.QPushButton(self.groupBox) 48 | self.stopButton.setObjectName("stopButton") 49 | self.verticalLayout.addWidget(self.stopButton) 50 | self.splitter = QtWidgets.QSplitter(self.centralwidget) 51 | self.splitter.setGeometry(QtCore.QRect(690, 80, 181, 27)) 52 | self.splitter.setOrientation(QtCore.Qt.Horizontal) 53 | self.splitter.setObjectName("splitter") 54 | self.label = QtWidgets.QLabel(self.splitter) 55 | self.label.setObjectName("label") 56 | self.faceRegistrySelect = QtWidgets.QComboBox(self.splitter) 57 | self.faceRegistrySelect.setObjectName("faceRegistrySelect") 58 | FaceRecApp.setCentralWidget(self.centralwidget) 59 | self.menubar = QtWidgets.QMenuBar(FaceRecApp) 60 | self.menubar.setGeometry(QtCore.QRect(0, 0, 886, 27)) 61 | self.menubar.setObjectName("menubar") 62 | self.menuFile = QtWidgets.QMenu(self.menubar) 63 | self.menuFile.setObjectName("menuFile") 64 | self.menuDevices = QtWidgets.QMenu(self.menubar) 65 | self.menuDevices.setObjectName("menuDevices") 66 | self.menuView = QtWidgets.QMenu(self.menubar) 67 | self.menuView.setObjectName("menuView") 68 | FaceRecApp.setMenuBar(self.menubar) 69 | self.statusbar = QtWidgets.QStatusBar(FaceRecApp) 70 | self.statusbar.setObjectName("statusbar") 71 | FaceRecApp.setStatusBar(self.statusbar) 72 | self.actionExit = QtWidgets.QAction(FaceRecApp) 73 | self.actionExit.setObjectName("actionExit") 74 | self.actionStartCamera = QtWidgets.QAction(FaceRecApp) 75 | self.actionStartCamera.setObjectName("actionStartCamera") 76 | self.actionStopCamera = QtWidgets.QAction(FaceRecApp) 77 | self.actionStopCamera.setObjectName("actionStopCamera") 78 | self.actionRegisterFaces = QtWidgets.QAction(FaceRecApp) 79 | self.actionRegisterFaces.setObjectName("actionRegisterFaces") 80 | self.actionViewFaceRegistry = QtWidgets.QAction(FaceRecApp) 81 | self.actionViewFaceRegistry.setObjectName("actionViewFaceRegistry") 82 | self.actionAddAlbum = QtWidgets.QAction(FaceRecApp) 83 | self.actionAddAlbum.setObjectName("actionAddAlbum") 84 | self.actionDeleteAlbum = QtWidgets.QAction(FaceRecApp) 85 | self.actionDeleteAlbum.setObjectName("actionDeleteAlbum") 86 | self.menuFile.addAction(self.actionRegisterFaces) 87 | self.menuFile.addAction(self.actionAddAlbum) 88 | self.menuFile.addAction(self.actionDeleteAlbum) 89 | self.menuFile.addSeparator() 90 | self.menuFile.addAction(self.actionExit) 91 | self.menuView.addAction(self.actionViewFaceRegistry) 92 | self.menubar.addAction(self.menuFile.menuAction()) 93 | self.menubar.addAction(self.menuView.menuAction()) 94 | self.menubar.addAction(self.menuDevices.menuAction()) 95 | self.label.setBuddy(self.faceRegistrySelect) 96 | 97 | self.retranslateUi(FaceRecApp) 98 | self.startButton.clicked.connect(FaceRecApp.start) 99 | self.pauseButton.clicked.connect(FaceRecApp.pause) 100 | self.stopButton.clicked.connect(FaceRecApp.stop) 101 | self.actionRegisterFaces.triggered.connect(FaceRecApp.register_faces) 102 | self.actionAddAlbum.triggered.connect(FaceRecApp.create_registry) 103 | self.actionViewFaceRegistry.triggered.connect(FaceRecApp.view_registry) 104 | self.actionExit.triggered.connect(FaceRecApp.close) 105 | self.actionDeleteAlbum.triggered.connect(FaceRecApp.delete_registry) 106 | self.faceRegistrySelect.currentIndexChanged['QString'].connect(FaceRecApp.set_current_registry) 107 | QtCore.QMetaObject.connectSlotsByName(FaceRecApp) 108 | 109 | def retranslateUi(self, FaceRecApp): 110 | _translate = QtCore.QCoreApplication.translate 111 | FaceRecApp.setWindowTitle(_translate("FaceRecApp", "Face Recognition")) 112 | self.groupBox.setTitle(_translate("FaceRecApp", "Controls")) 113 | self.startButton.setText(_translate("FaceRecApp", "Start")) 114 | self.pauseButton.setText(_translate("FaceRecApp", "Pause")) 115 | self.stopButton.setText(_translate("FaceRecApp", "Stop")) 116 | self.label.setText(_translate("FaceRecApp", "Album")) 117 | self.menuFile.setTitle(_translate("FaceRecApp", "File")) 118 | self.menuDevices.setTitle(_translate("FaceRecApp", "Devices")) 119 | self.menuView.setTitle(_translate("FaceRecApp", "View")) 120 | self.actionExit.setText(_translate("FaceRecApp", "Exit")) 121 | self.actionStartCamera.setText(_translate("FaceRecApp", "Start Camera")) 122 | self.actionStopCamera.setText(_translate("FaceRecApp", "Stop Camera")) 123 | self.actionRegisterFaces.setText(_translate("FaceRecApp", "Register faces")) 124 | self.actionViewFaceRegistry.setText(_translate("FaceRecApp", "Face registry")) 125 | self.actionAddAlbum.setText(_translate("FaceRecApp", "Add album")) 126 | self.actionDeleteAlbum.setText(_translate("FaceRecApp", "Delete album")) 127 | 128 | 129 | 130 | 131 | if __name__ == "__main__": 132 | import sys 133 | app = QtWidgets.QApplication(sys.argv) 134 | FaceRecApp = QtWidgets.QMainWindow() 135 | ui = Ui_FaceRecApp() 136 | ui.setupUi(FaceRecApp) 137 | FaceRecApp.show() 138 | sys.exit(app.exec_()) 139 | -------------------------------------------------------------------------------- /gui/face_registration_view.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FaceRegistrationUI 4 | 5 | 6 | 7 | 0 8 | 0 9 | 774 10 | 485 11 | 12 | 13 | 14 | Face Registration 15 | 16 | 17 | 18 | .. 19 | 20 | 21 | 22 | 23 | 530 24 | 40 25 | 241 26 | 271 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 35 | 36 | 37 | 38 | 39 | Album 40 | 41 | 42 | registryNameSelect 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Capture 53 | 54 | 55 | 56 | 57 | 58 | 59 | Retake 60 | 61 | 62 | 63 | 64 | 65 | 66 | Name 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | Register 77 | 78 | 79 | 80 | 81 | 82 | 83 | Qt::Vertical 84 | 85 | 86 | 87 | 20 88 | 40 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 400 99 | 430 100 | 341 101 | 32 102 | 103 | 104 | 105 | Qt::Horizontal 106 | 107 | 108 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 109 | 110 | 111 | 112 | 113 | 114 | 30 115 | 10 116 | 481 117 | 361 118 | 119 | 120 | 121 | QFrame::Box 122 | 123 | 124 | QFrame::Sunken 125 | 126 | 127 | 128 | 129 | 130 | 0 131 | 0 132 | 481 133 | 361 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 0 143 | 0 144 | 481 145 | 361 146 | 147 | 148 | 149 | QFrame::Box 150 | 151 | 152 | TextLabel 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | QCameraViewfinder 161 | QWidget 162 |
PyQt5.QtMultimediaWidgets
163 | 1 164 |
165 |
166 | 167 | 168 | 169 | buttonBox 170 | accepted() 171 | FaceRegistrationUI 172 | accept() 173 | 174 | 175 | 248 176 | 254 177 | 178 | 179 | 157 180 | 274 181 | 182 | 183 | 184 | 185 | buttonBox 186 | rejected() 187 | FaceRegistrationUI 188 | reject() 189 | 190 | 191 | 316 192 | 260 193 | 194 | 195 | 286 196 | 274 197 | 198 | 199 | 200 | 201 | nameInput 202 | textChanged(QString) 203 | FaceRegistrationUI 204 | handle_face_name(QString) 205 | 206 | 207 | 645 208 | 224 209 | 210 | 211 | 492 212 | 30 213 | 214 | 215 | 216 | 217 | registerButton 218 | clicked() 219 | FaceRegistrationUI 220 | register_face() 221 | 222 | 223 | 677 224 | 262 225 | 226 | 227 | 516 228 | 53 229 | 230 | 231 | 232 | 233 | retakeButton 234 | clicked() 235 | FaceRegistrationUI 236 | retake_picture() 237 | 238 | 239 | 625 240 | 143 241 | 242 | 243 | 694 244 | 27 245 | 246 | 247 | 248 | 249 | captureButton 250 | clicked() 251 | FaceRegistrationUI 252 | capture_picture() 253 | 254 | 255 | 688 256 | 110 257 | 258 | 259 | 590 260 | 15 261 | 262 | 263 | 264 | 265 | registryNameSelect 266 | currentIndexChanged(QString) 267 | FaceRegistrationUI 268 | set_current_face_registry(QString) 269 | 270 | 271 | 610 272 | 78 273 | 274 | 275 | 500 276 | 121 277 | 278 | 279 | 280 | 281 | 282 | handle_face_name(QString) 283 | register_face() 284 | capture_picture() 285 | retake_picture() 286 | set_current_face_registry(QString) 287 | 288 |
289 | -------------------------------------------------------------------------------- /gui/face_registration_view_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file './gui/face_registration_view.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_FaceRegistrationUI(object): 13 | def setupUi(self, FaceRegistrationUI): 14 | FaceRegistrationUI.setObjectName("FaceRegistrationUI") 15 | FaceRegistrationUI.resize(774, 485) 16 | icon = QtGui.QIcon.fromTheme("face-cool") 17 | FaceRegistrationUI.setWindowIcon(icon) 18 | self.groupBox = QtWidgets.QGroupBox(FaceRegistrationUI) 19 | self.groupBox.setGeometry(QtCore.QRect(530, 40, 241, 271)) 20 | self.groupBox.setTitle("") 21 | self.groupBox.setObjectName("groupBox") 22 | self.formLayout = QtWidgets.QFormLayout(self.groupBox) 23 | self.formLayout.setLabelAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) 24 | self.formLayout.setObjectName("formLayout") 25 | self.label = QtWidgets.QLabel(self.groupBox) 26 | self.label.setObjectName("label") 27 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label) 28 | self.registryNameSelect = QtWidgets.QComboBox(self.groupBox) 29 | self.registryNameSelect.setObjectName("registryNameSelect") 30 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.registryNameSelect) 31 | self.captureButton = QtWidgets.QPushButton(self.groupBox) 32 | self.captureButton.setObjectName("captureButton") 33 | self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.captureButton) 34 | self.retakeButton = QtWidgets.QPushButton(self.groupBox) 35 | self.retakeButton.setObjectName("retakeButton") 36 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.retakeButton) 37 | self.label_2 = QtWidgets.QLabel(self.groupBox) 38 | self.label_2.setObjectName("label_2") 39 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_2) 40 | self.nameInput = QtWidgets.QLineEdit(self.groupBox) 41 | self.nameInput.setObjectName("nameInput") 42 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.nameInput) 43 | self.registerButton = QtWidgets.QPushButton(self.groupBox) 44 | self.registerButton.setObjectName("registerButton") 45 | self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.registerButton) 46 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 47 | self.formLayout.setItem(3, QtWidgets.QFormLayout.FieldRole, spacerItem) 48 | self.buttonBox = QtWidgets.QDialogButtonBox(FaceRegistrationUI) 49 | self.buttonBox.setGeometry(QtCore.QRect(400, 430, 341, 32)) 50 | self.buttonBox.setOrientation(QtCore.Qt.Horizontal) 51 | self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) 52 | self.buttonBox.setObjectName("buttonBox") 53 | self.stackedWidget = QtWidgets.QStackedWidget(FaceRegistrationUI) 54 | self.stackedWidget.setGeometry(QtCore.QRect(30, 10, 481, 361)) 55 | self.stackedWidget.setFrameShape(QtWidgets.QFrame.Box) 56 | self.stackedWidget.setFrameShadow(QtWidgets.QFrame.Sunken) 57 | self.stackedWidget.setObjectName("stackedWidget") 58 | self.viewFinderPage = QtWidgets.QWidget() 59 | self.viewFinderPage.setObjectName("viewFinderPage") 60 | self.viewFinder = QCameraViewfinder(self.viewFinderPage) 61 | self.viewFinder.setGeometry(QtCore.QRect(0, 0, 481, 361)) 62 | self.viewFinder.setObjectName("viewFinder") 63 | self.stackedWidget.addWidget(self.viewFinderPage) 64 | self.previewPage = QtWidgets.QWidget() 65 | self.previewPage.setObjectName("previewPage") 66 | self.picturePreview = QtWidgets.QLabel(self.previewPage) 67 | self.picturePreview.setGeometry(QtCore.QRect(0, 0, 481, 361)) 68 | self.picturePreview.setFrameShape(QtWidgets.QFrame.Box) 69 | self.picturePreview.setObjectName("picturePreview") 70 | self.stackedWidget.addWidget(self.previewPage) 71 | self.label.setBuddy(self.registryNameSelect) 72 | 73 | self.retranslateUi(FaceRegistrationUI) 74 | self.buttonBox.accepted.connect(FaceRegistrationUI.accept) 75 | self.buttonBox.rejected.connect(FaceRegistrationUI.reject) 76 | self.nameInput.textChanged['QString'].connect(FaceRegistrationUI.handle_face_name) 77 | self.registerButton.clicked.connect(FaceRegistrationUI.register_face) 78 | self.retakeButton.clicked.connect(FaceRegistrationUI.retake_picture) 79 | self.captureButton.clicked.connect(FaceRegistrationUI.capture_picture) 80 | self.registryNameSelect.currentIndexChanged['QString'].connect(FaceRegistrationUI.set_current_face_registry) 81 | QtCore.QMetaObject.connectSlotsByName(FaceRegistrationUI) 82 | 83 | def retranslateUi(self, FaceRegistrationUI): 84 | _translate = QtCore.QCoreApplication.translate 85 | FaceRegistrationUI.setWindowTitle(_translate("FaceRegistrationUI", "Face Registration")) 86 | self.label.setText(_translate("FaceRegistrationUI", "Album")) 87 | self.captureButton.setText(_translate("FaceRegistrationUI", "Capture")) 88 | self.retakeButton.setText(_translate("FaceRegistrationUI", "Retake")) 89 | self.label_2.setText(_translate("FaceRegistrationUI", "Name")) 90 | self.registerButton.setText(_translate("FaceRegistrationUI", "Register")) 91 | self.picturePreview.setText(_translate("FaceRegistrationUI", "TextLabel")) 92 | 93 | 94 | from PyQt5.QtMultimediaWidgets import QCameraViewfinder 95 | 96 | 97 | if __name__ == "__main__": 98 | import sys 99 | app = QtWidgets.QApplication(sys.argv) 100 | FaceRegistrationUI = QtWidgets.QDialog() 101 | ui = Ui_FaceRegistrationUI() 102 | ui.setupUi(FaceRegistrationUI) 103 | FaceRegistrationUI.show() 104 | sys.exit(app.exec_()) 105 | -------------------------------------------------------------------------------- /gui/face_registry_view.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FaceRegistryUI 4 | 5 | 6 | 7 | 0 8 | 0 9 | 763 10 | 499 11 | 12 | 13 | 14 | Face Album 15 | 16 | 17 | 18 | .. 19 | 20 | 21 | 22 | 23 | 490 24 | 70 25 | 247 26 | 291 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Load 37 | 38 | 39 | 40 | 41 | 42 | 43 | Album 44 | 45 | 46 | registryNameSelect 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Next >> 57 | 58 | 59 | 60 | 61 | 62 | 63 | << Prev 64 | 65 | 66 | 67 | 68 | 69 | 70 | true 71 | 72 | 73 | 74 | 75 | 76 | 77 | Name 78 | 79 | 80 | faceName 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 80 90 | 40 91 | 321 92 | 301 93 | 94 | 95 | 96 | 97 | 98 | 99 | 640 100 | 440 101 | 85 102 | 27 103 | 104 | 105 | 106 | Close 107 | 108 | 109 | 110 | 111 | 112 | 113 | loadButton 114 | clicked() 115 | FaceRegistryUI 116 | load_face_registry() 117 | 118 | 119 | 627 120 | 190 121 | 122 | 123 | 643 124 | 31 125 | 126 | 127 | 128 | 129 | nextFaceButton 130 | clicked() 131 | FaceRegistryUI 132 | show_next_face() 133 | 134 | 135 | 670 136 | 303 137 | 138 | 139 | 576 140 | 42 141 | 142 | 143 | 144 | 145 | prevFaceButton 146 | clicked() 147 | FaceRegistryUI 148 | show_prev_face() 149 | 150 | 151 | 565 152 | 302 153 | 154 | 155 | 520 156 | 435 157 | 158 | 159 | 160 | 161 | closeViewButton 162 | clicked() 163 | FaceRegistryUI 164 | close() 165 | 166 | 167 | 706 168 | 447 169 | 170 | 171 | 735 172 | 409 173 | 174 | 175 | 176 | 177 | registryNameSelect 178 | currentIndexChanged(QString) 179 | FaceRegistryUI 180 | set_current_face_registry(QString) 181 | 182 | 183 | 608 184 | 131 185 | 186 | 187 | 457 188 | 69 189 | 190 | 191 | 192 | 193 | 194 | load_face_registry() 195 | show_next_face() 196 | show_prev_face() 197 | set_current_face_registry(QString) 198 | 199 | 200 | -------------------------------------------------------------------------------- /gui/face_registry_view_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file './gui/face_registry_view.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_FaceRegistryUI(object): 13 | def setupUi(self, FaceRegistryUI): 14 | FaceRegistryUI.setObjectName("FaceRegistryUI") 15 | FaceRegistryUI.resize(763, 499) 16 | icon = QtGui.QIcon.fromTheme("face-cool") 17 | FaceRegistryUI.setWindowIcon(icon) 18 | self.groupBox = QtWidgets.QGroupBox(FaceRegistryUI) 19 | self.groupBox.setGeometry(QtCore.QRect(490, 70, 247, 291)) 20 | self.groupBox.setTitle("") 21 | self.groupBox.setObjectName("groupBox") 22 | self.gridLayout = QtWidgets.QGridLayout(self.groupBox) 23 | self.gridLayout.setObjectName("gridLayout") 24 | self.loadButton = QtWidgets.QPushButton(self.groupBox) 25 | self.loadButton.setObjectName("loadButton") 26 | self.gridLayout.addWidget(self.loadButton, 1, 1, 1, 3) 27 | self.label = QtWidgets.QLabel(self.groupBox) 28 | self.label.setObjectName("label") 29 | self.gridLayout.addWidget(self.label, 0, 0, 1, 1) 30 | self.registryNameSelect = QtWidgets.QComboBox(self.groupBox) 31 | self.registryNameSelect.setObjectName("registryNameSelect") 32 | self.gridLayout.addWidget(self.registryNameSelect, 0, 1, 1, 3) 33 | self.nextFaceButton = QtWidgets.QPushButton(self.groupBox) 34 | self.nextFaceButton.setObjectName("nextFaceButton") 35 | self.gridLayout.addWidget(self.nextFaceButton, 3, 3, 1, 1) 36 | self.prevFaceButton = QtWidgets.QPushButton(self.groupBox) 37 | self.prevFaceButton.setObjectName("prevFaceButton") 38 | self.gridLayout.addWidget(self.prevFaceButton, 3, 0, 1, 2) 39 | self.faceName = QtWidgets.QLineEdit(self.groupBox) 40 | self.faceName.setReadOnly(True) 41 | self.faceName.setObjectName("faceName") 42 | self.gridLayout.addWidget(self.faceName, 2, 2, 1, 2) 43 | self.label_2 = QtWidgets.QLabel(self.groupBox) 44 | self.label_2.setObjectName("label_2") 45 | self.gridLayout.addWidget(self.label_2, 2, 0, 1, 2) 46 | self.faceImageArea = QtWidgets.QWidget(FaceRegistryUI) 47 | self.faceImageArea.setGeometry(QtCore.QRect(80, 40, 321, 301)) 48 | self.faceImageArea.setObjectName("faceImageArea") 49 | self.closeViewButton = QtWidgets.QPushButton(FaceRegistryUI) 50 | self.closeViewButton.setGeometry(QtCore.QRect(640, 440, 85, 27)) 51 | self.closeViewButton.setObjectName("closeViewButton") 52 | self.label.setBuddy(self.registryNameSelect) 53 | self.label_2.setBuddy(self.faceName) 54 | 55 | self.retranslateUi(FaceRegistryUI) 56 | self.loadButton.clicked.connect(FaceRegistryUI.load_face_registry) 57 | self.nextFaceButton.clicked.connect(FaceRegistryUI.show_next_face) 58 | self.prevFaceButton.clicked.connect(FaceRegistryUI.show_prev_face) 59 | self.closeViewButton.clicked.connect(FaceRegistryUI.close) 60 | self.registryNameSelect.currentIndexChanged['QString'].connect(FaceRegistryUI.set_current_face_registry) 61 | QtCore.QMetaObject.connectSlotsByName(FaceRegistryUI) 62 | 63 | def retranslateUi(self, FaceRegistryUI): 64 | _translate = QtCore.QCoreApplication.translate 65 | FaceRegistryUI.setWindowTitle(_translate("FaceRegistryUI", "Face Album")) 66 | self.loadButton.setText(_translate("FaceRegistryUI", "Load")) 67 | self.label.setText(_translate("FaceRegistryUI", "Album")) 68 | self.nextFaceButton.setText(_translate("FaceRegistryUI", "Next >>")) 69 | self.prevFaceButton.setText(_translate("FaceRegistryUI", "<< Prev")) 70 | self.label_2.setText(_translate("FaceRegistryUI", "Name")) 71 | self.closeViewButton.setText(_translate("FaceRegistryUI", "Close")) 72 | 73 | 74 | 75 | 76 | if __name__ == "__main__": 77 | import sys 78 | app = QtWidgets.QApplication(sys.argv) 79 | FaceRegistryUI = QtWidgets.QDialog() 80 | ui = Ui_FaceRegistryUI() 81 | ui.setupUi(FaceRegistryUI) 82 | FaceRegistryUI.show() 83 | sys.exit(app.exec_()) 84 | -------------------------------------------------------------------------------- /loggers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2018, AIMLedge Pte, Ltd. 3 | All rights reserved. 4 | 5 | """ 6 | import logging 7 | 8 | face_recognition_app_logger = logging.getLogger('Face_Rec_App') -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from PyQt5.QtWidgets import QApplication 3 | import face_recognition_app 4 | 5 | if __name__ == "__main__": 6 | import sys 7 | logging.basicConfig(level=logging.INFO) 8 | app = QApplication(sys.argv) 9 | face_rec_app = face_recognition_app.FaceRecognitionAppUI() 10 | face_rec_app.show() 11 | sys.exit(app.exec_()) 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | opencv_python==3.4.2.17 2 | botocore==1.12.140 3 | face_recognition==1.2.3 4 | boto3==1.9.140 5 | scipy==1.1.0 6 | numpy==1.14.3 7 | Pillow==6.0.0 8 | PyQt5==5.12.1 9 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export LD_LIBRARY_PATH=/home/ghegde/Env/sandwich/lib/python3.5/site-packages/PyQt5/Qt/lib:$LD_LIBRARY_PATH 3 | export QT_PLUGIN_PATH=/usr/lib/x86_64-linux-gnu/qt5/plugins/ 4 | python $(dirname $0)/main.py --------------------------------------------------------------------------------