├── .idea ├── Face_AllTPU.iml ├── inspectionProfiles │ └── Project_Default.xml └── modules.xml ├── README.md ├── config.py ├── demo.py ├── embedding_book └── embeddings.h5 ├── embeddings.py ├── facenet.py ├── lfw_pairs.txt ├── object_detection.py ├── pictures ├── KrisWu.jpg └── WangLihong.jpg ├── requirements.txt ├── sample.JPG ├── test.py ├── utils.py └── validate_lfw.py /.idea/Face_AllTPU.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 83 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EdgeTPU-FaceNet 2 | **Implement SSD and FaceNet on Edge TPU Accelerator.** 3 | 4 | **Class can be increased immediately.** 5 | 6 | **You can use 'q' to quit and 'a' to add new class.** 7 | 8 | ## Requirement 9 | tensorflow - 1.15.0 10 | 11 | Ubuntu - 16.04 12 | 13 | Edge TPU compilier - 2.0.267 14 | 15 | USB Camera 16 | 17 | ## Demo 18 | 19 | **about 25 FPS** 20 | 21 | **https://youtu.be/I9F_GT_quFs** 22 | 23 | ![image](https://github.com/Kao1126/EdgeTPU-FaceNet/blob/master/sample.JPG) 24 | 25 | ## Usage 26 | 27 | #### 1. Download SSD and FaceNet weight 28 | 29 | (1). Download [ssd weight](https://drive.google.com/open?id=198woIHpHlhePd0F3ADIXnt5G2bDkEuig) 30 | and put in weight/SSD 31 | 32 | (2). Download [facenet weight](https://drive.google.com/open?id=1LZF3Z2Z6mM_gHueMfTKOtxjiiaeLgexV) 33 | and put in weight/FacaNet 34 | 35 | Both weights have already been compiled and quantized. 36 | 37 | #### 2. Run demo.py 38 | #### 39 | $ git clone https://github.com/Kao1126/EdgeTPU-FaceNet.git 40 | $ cd EdgeTPU-FaceNet 41 | $ python3 demo.py 42 | 43 | ## Valitading on FLW 44 | 1. Create lfw folder 45 | #### 46 | $ mkdir lfw 47 | 2. Download LFW datasets and put in lfw 48 | #### 49 | $ python3 validate_lfw.py 50 | 51 | ## Reference 52 | - coral: 53 | https://coral.withgoogle.com/docs/edgetpu/models-intro/ 54 | 55 | - tensorflow: 56 | https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/quantize 57 | 58 | - face net: 59 | https://github.com/LeslieZhoa/tensorflow-facenet 60 | 61 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | THRED = 0.03 2 | FaceNet_weight = r'weight/FaceNet/FaceNet_128.tflite' 3 | Model_weight = r'weight/SSD/mobilenet_ssd_v2_face_quant_edgetpu.tflite' 4 | Embedding_book = r'embedding_book/embeddings.h5' -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import h5py 3 | import time 4 | import platform 5 | import numpy 6 | import subprocess 7 | from edgetpu.classification.engine import ClassificationEngine 8 | from edgetpu.detection.engine import DetectionEngine 9 | from edgetpu.utils import dataset_utils 10 | from PIL import Image 11 | from PIL import ImageDraw 12 | import tensorflow as tf 13 | from embeddings import Create_embeddings 14 | import numpy as np 15 | from test import Tpu_FaceRecognize 16 | from config import* 17 | 18 | 19 | def crop_image(ans, frame): 20 | Images_cropped = [] 21 | for i in range(0, len(ans)): 22 | img_crop = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 23 | BBC = ans[i].bounding_box # bounding_box_coordinate 24 | 25 | x = int(BBC[0][0]) 26 | y = int(BBC[0][1]) 27 | w = int(BBC[1][0] - BBC[0][0]) 28 | h = int(BBC[1][1] - BBC[0][1]) 29 | 30 | img_crop = img_crop[y:y+h, x:x+w] 31 | 32 | img_crop = cv2.resize(img_crop, (160, 160)) 33 | 34 | Images_cropped.append(img_crop) 35 | 36 | return Images_cropped 37 | 38 | def read_embedding(path=Embedding_book): 39 | 40 | try: 41 | f=h5py.File(path,'r') 42 | except OSError: 43 | face_engine = ClassificationEngine(FaceNet_weight) 44 | Create_embeddings(face_engine) 45 | f=h5py.File(path, 'r') 46 | 47 | class_arr=f['class_name'][:] 48 | class_arr=[k.decode() for k in class_arr] 49 | emb_arr=f['embeddings'][:] 50 | 51 | return class_arr, emb_arr 52 | 53 | 54 | 55 | 56 | def main(): 57 | 58 | load_time = time.time() 59 | 60 | # Initialize engine. 61 | engine = DetectionEngine(Model_weight) 62 | labels = None 63 | 64 | # Face recognize engine 65 | face_engine = ClassificationEngine(FaceNet_weight) 66 | # read embedding 67 | class_arr, emb_arr = read_embedding(Embedding_book) 68 | 69 | l = time.time() - load_time 70 | 71 | with tf.Graph().as_default(): 72 | with tf.compat.v1.Session() as sess: 73 | 74 | cap = cv2.VideoCapture(0) 75 | 76 | while(True): 77 | t1 = cv2.getTickCount() 78 | print('Load_model: {:.2f} sec'.format(l)) 79 | 80 | ret, frame = cap.read() 81 | 82 | img = Image.fromarray(cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)) 83 | draw = ImageDraw.Draw(img) 84 | 85 | 86 | # Run inference. 87 | ans = engine.DetectWithImage( 88 | img, 89 | threshold=0.05, 90 | keep_aspect_ratio=False, 91 | relative_coord=False, 92 | top_k=10) 93 | 94 | img = numpy.asarray(img) 95 | # Display result. 96 | if ans: 97 | crop_img = crop_image(ans, frame) 98 | 99 | if cv2.waitKey(1) == ord('a'): 100 | 101 | for k in range(0, len(crop_img)): 102 | new_class_name = input('Please input your name of class:') 103 | new_save = cv2.cvtColor(crop_img[k], cv2.COLOR_BGR2RGB) 104 | cv2.imwrite('pictures/' + str(new_class_name) + '.jpg', new_save) 105 | 106 | Create_embeddings(face_engine) 107 | class_arr, emb_arr = read_embedding('embedding_book/embeddings.h5') 108 | 109 | embs = Tpu_FaceRecognize(face_engine, crop_img) 110 | 111 | 112 | face_num = len(ans) 113 | face_class = ['Others']*face_num 114 | 115 | for i in range(face_num): 116 | diff = np.mean(np.square(embs[i]-emb_arr), axis=1) 117 | min_diff = min(diff) 118 | 119 | if min_diff < THRED: 120 | 121 | index = np.argmin(diff) 122 | face_class[i] = class_arr[index] 123 | 124 | print('Face_class:', face_class) 125 | print('Classes:', class_arr) 126 | 127 | for count, obj in enumerate(ans): 128 | print('-----------------------------------------') 129 | if labels: 130 | print(labels[obj.label_id]) 131 | print('Score = ', obj.score) 132 | box = obj.bounding_box.flatten().tolist() 133 | 134 | # Draw a rectangle and label 135 | cv2.rectangle(img, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (255, 255, 0), 2) 136 | cv2.putText(img, '{}'.format(face_class[count]), (int(box[0]), int(box[1])-5), cv2.FONT_HERSHEY_PLAIN, 137 | 1, (255, 0, 0), 1, cv2.LINE_AA) 138 | 139 | t2 = cv2.getTickCount() 140 | t = (t2-t1)/cv2.getTickFrequency() 141 | fps = 1.0/t 142 | cv2.putText(img, 'fps: {:.2f}'.format(fps), (5, 20), cv2.FONT_HERSHEY_PLAIN, 1, (255, 0, 0), 1, cv2.LINE_AA) 143 | 144 | cv2.putText(img, 'A: Add new class', (5, 450), cv2.FONT_HERSHEY_PLAIN, 1, (255, 0, 0), 1, cv2.LINE_AA) 145 | cv2.putText(img, 'Q: Quit', (5, 470), cv2.FONT_HERSHEY_PLAIN, 1, (255, 0, 0), 1, cv2.LINE_AA) 146 | img_ = cv2.cvtColor(img,cv2.COLOR_RGB2BGR) 147 | 148 | cv2.imshow('frame', img_) 149 | 150 | if cv2.waitKey(1) == ord('q'): 151 | break 152 | 153 | cap.release() 154 | cv2.destroyAllWindows() 155 | 156 | if __name__ == '__main__': 157 | main() -------------------------------------------------------------------------------- /embedding_book/embeddings.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kao1126/EdgeTPU-FaceNet/d28eb4d3e212b3657d71b4c2ccc8247a47babdee/embedding_book/embeddings.h5 -------------------------------------------------------------------------------- /embeddings.py: -------------------------------------------------------------------------------- 1 | 2 | # coding: utf-8 3 | 4 | # In[1]: 5 | from PIL import Image 6 | from tensorflow.python.platform import gfile 7 | from test import Tpu_FaceRecognize 8 | import tensorflow as tf 9 | import numpy as np 10 | import sys 11 | import os 12 | import copy 13 | import re 14 | from utils import * 15 | import config 16 | import cv2 17 | import h5py 18 | from config import Embedding_book 19 | # In[2]: 20 | 21 | def Create_embeddings(face_engine): 22 | 23 | img_arr, class_arr = align_face() 24 | embs = Tpu_FaceRecognize(face_engine, img_arr) 25 | 26 | f = h5py.File(Embedding_book,'w') 27 | class_arr=[i.encode() for i in class_arr] 28 | f.create_dataset('class_name',data=class_arr) 29 | f.create_dataset('embeddings',data=embs) 30 | f.close() 31 | 32 | 33 | 34 | # In[3]: 35 | 36 | 37 | def align_face(path='pictures/'): 38 | 39 | img_paths=os.listdir(path) 40 | class_names=[a.split('.')[0] for a in img_paths] 41 | img_paths=[os.path.join(path,p) for p in img_paths] 42 | scaled_arr=[] 43 | class_names_arr=[] 44 | 45 | for image_path,class_name in zip(img_paths,class_names): 46 | 47 | img = cv2.imread(image_path) 48 | scaled = cv2.resize(img,(160, 160),interpolation=cv2.INTER_LINEAR) 49 | 50 | scaled = Image.fromarray(cv2.cvtColor(scaled,cv2.COLOR_BGR2RGB)) 51 | scaled = np.asarray(img) 52 | 53 | scaled_arr.append(scaled) 54 | class_names_arr.append(class_name) 55 | 56 | 57 | scaled_arr=np.asarray(scaled_arr) 58 | class_names_arr=np.asarray(class_names_arr) 59 | print("scaled_arr", scaled_arr.shape) 60 | print('class_names_arr', class_names_arr) 61 | return scaled_arr,class_names_arr 62 | 63 | 64 | -------------------------------------------------------------------------------- /facenet.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import Popen, PIPE 3 | import tensorflow as tf 4 | import numpy as np 5 | from scipy import misc 6 | from sklearn.model_selection import KFold 7 | from scipy import interpolate 8 | from tensorflow.python.training import training 9 | import random 10 | import re 11 | from tensorflow.python.platform import gfile 12 | import math 13 | from six import iteritems 14 | import heapq 15 | 16 | 17 | def center_loss(features, label, alfa, nrof_classes): 18 | """Center loss based on the paper "A Discriminative Feature Learning Approach for Deep Face Recognition" 19 | (http://ydwen.github.io/papers/WenECCV16.pdf) 20 | """ 21 | nrof_features = features.get_shape()[1] 22 | centers = tf.get_variable('centers', [nrof_classes, nrof_features], dtype=tf.float32, 23 | initializer=tf.constant_initializer(0), trainable=False) 24 | label = tf.reshape(label, [-1]) 25 | centers_batch = tf.gather(centers, label) 26 | diff = (1 - alfa) * (centers_batch - features) 27 | centers = tf.scatter_sub(centers, label, diff) 28 | with tf.control_dependencies([centers]): 29 | loss = tf.reduce_mean(tf.square(features - centers_batch)) 30 | return loss, centers 31 | 32 | def get_image_paths_and_labels(dataset): 33 | image_paths_flat = [] 34 | labels_flat = [] 35 | for i in range(len(dataset)): 36 | image_paths_flat += dataset[i].image_paths 37 | labels_flat += [i] * len(dataset[i].image_paths) 38 | return image_paths_flat, labels_flat 39 | 40 | def shuffle_examples(image_paths, labels): 41 | shuffle_list = list(zip(image_paths, labels)) 42 | random.shuffle(shuffle_list) 43 | image_paths_shuff, labels_shuff = zip(*shuffle_list) 44 | return image_paths_shuff, labels_shuff 45 | 46 | def random_rotate_image(image): 47 | angle = np.random.uniform(low=-10.0, high=10.0) 48 | return misc.imrotate(image, angle, 'bicubic') 49 | 50 | # 1: Random rotate 2: Random crop 4: Random flip 8: Fixed image standardization 16: Flip 51 | RANDOM_ROTATE = 1 52 | RANDOM_CROP = 2 53 | RANDOM_FLIP = 4 54 | FIXED_STANDARDIZATION = 8 55 | FLIP = 16 56 | def create_input_pipeline(input_queue, image_size, nrof_preprocess_threads, batch_size_placeholder): 57 | images_and_labels_list = [] 58 | for _ in range(nrof_preprocess_threads): 59 | filenames, label, control = input_queue.dequeue() 60 | images = [] 61 | for filename in tf.unstack(filenames): 62 | file_contents = tf.read_file(filename) 63 | image = tf.image.decode_image(file_contents, 3) 64 | print(image) 65 | image = tf.cond(get_control_flag(control[0], RANDOM_ROTATE), 66 | lambda:tf.py_func(random_rotate_image, [image], tf.uint8), 67 | lambda:tf.identity(image)) 68 | image = tf.cond(get_control_flag(control[0], RANDOM_CROP), 69 | lambda:tf.random_crop(image, image_size + (3,)), 70 | lambda:tf.image.resize_image_with_crop_or_pad(image, image_size[0], image_size[1])) 71 | image = tf.cond(get_control_flag(control[0], RANDOM_FLIP), 72 | lambda:tf.image.random_flip_left_right(image), 73 | lambda:tf.identity(image)) 74 | # image = tf.cond(get_control_flag(control[0], FIXED_STANDARDIZATION), 75 | # lambda:(tf.cast(image, tf.float32)-127.5)/128, 76 | # lambda:tf.image.per_image_standardization(image)) 77 | image = (tf.cast(image, tf.float32)-127.5)/128 78 | image = tf.cond(get_control_flag(control[0], FLIP), 79 | lambda:tf.image.flip_left_right(image), 80 | lambda:tf.identity(image)) 81 | #pylint: disable=no-member 82 | image.set_shape(image_size + (3,)) 83 | images.append(image) 84 | images_and_labels_list.append([images, label]) 85 | 86 | image_batch, label_batch = tf.train.batch_join( 87 | images_and_labels_list, batch_size=batch_size_placeholder, 88 | shapes=[image_size + (3,), ()], enqueue_many=True, 89 | capacity=4 * nrof_preprocess_threads * 100, 90 | allow_smaller_final_batch=True) 91 | 92 | return image_batch, label_batch 93 | 94 | def get_control_flag(control, field): 95 | return tf.equal(tf.mod(tf.floor_div(control, field), 2), 1) 96 | 97 | def _add_loss_summaries(total_loss): 98 | """Add summaries for losses. 99 | 100 | Generates moving average for all losses and associated summaries for 101 | visualizing the performance of the network. 102 | 103 | Args: 104 | total_loss: Total loss from loss(). 105 | Returns: 106 | loss_averages_op: op for generating moving averages of losses. 107 | """ 108 | # Compute the moving average of all individual losses and the total loss. 109 | loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg') 110 | losses = tf.get_collection('losses') 111 | loss_averages_op = loss_averages.apply(losses + [total_loss]) 112 | 113 | # Attach a scalar summmary to all individual losses and the total loss; do the 114 | # same for the averaged version of the losses. 115 | for l in losses + [total_loss]: 116 | # Name each loss as '(raw)' and name the moving average version of the loss 117 | # as the original loss name. 118 | tf.summary.scalar(l.op.name +' (raw)', l) 119 | tf.summary.scalar(l.op.name, loss_averages.average(l)) 120 | 121 | return loss_averages_op 122 | 123 | def train(total_loss, global_step, optimizer, learning_rate, moving_average_decay, update_gradient_vars, log_histograms=True): 124 | # Generate moving averages of all losses and associated summaries. 125 | loss_averages_op = _add_loss_summaries(total_loss) 126 | 127 | # Compute gradients. 128 | with tf.control_dependencies([loss_averages_op]): 129 | if optimizer=='ADAGRAD': 130 | opt = tf.train.AdagradOptimizer(learning_rate) 131 | elif optimizer=='ADADELTA': 132 | opt = tf.train.AdadeltaOptimizer(learning_rate, rho=0.9, epsilon=1e-6) 133 | elif optimizer=='ADAM': 134 | opt = tf.train.AdamOptimizer(learning_rate, beta1=0.9, beta2=0.999, epsilon=0.1) 135 | elif optimizer=='RMSPROP': 136 | opt = tf.train.RMSPropOptimizer(learning_rate, decay=0.9, momentum=0.9, epsilon=1.0) 137 | elif optimizer=='MOM': 138 | opt = tf.train.MomentumOptimizer(learning_rate, 0.9, use_nesterov=True) 139 | else: 140 | raise ValueError('Invalid optimization algorithm') 141 | 142 | grads = opt.compute_gradients(total_loss, update_gradient_vars) 143 | 144 | # Apply gradients. 145 | apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) 146 | 147 | # Add histograms for trainable variables. 148 | if log_histograms: 149 | for var in tf.trainable_variables(): 150 | tf.summary.histogram(var.op.name, var) 151 | 152 | # Add histograms for gradients. 153 | if log_histograms: 154 | for grad, var in grads: 155 | if grad is not None: 156 | tf.summary.histogram(var.op.name + '/gradients', grad) 157 | 158 | # Track the moving averages of all trainable variables. 159 | variable_averages = tf.train.ExponentialMovingAverage( 160 | moving_average_decay, global_step) 161 | variables_averages_op = variable_averages.apply(tf.trainable_variables()) 162 | 163 | with tf.control_dependencies([apply_gradient_op, variables_averages_op]): 164 | train_op = tf.no_op(name='train') 165 | 166 | return train_op 167 | 168 | def prewhiten(x): 169 | mean = np.mean(x) 170 | std = np.std(x) 171 | std_adj = np.maximum(std, 1.0/np.sqrt(x.size)) 172 | y = np.multiply(np.subtract(x, mean), 1/std_adj) 173 | return y 174 | 175 | def crop(image, random_crop, image_size): 176 | if image.shape[1]>image_size: 177 | sz1 = int(image.shape[1]//2) 178 | sz2 = int(image_size//2) 179 | if random_crop: 180 | diff = sz1-sz2 181 | (h, v) = (np.random.randint(-diff, diff+1), np.random.randint(-diff, diff+1)) 182 | else: 183 | (h, v) = (0,0) 184 | image = image[(sz1-sz2+v):(sz1+sz2+v),(sz1-sz2+h):(sz1+sz2+h),:] 185 | return image 186 | 187 | def flip(image, random_flip): 188 | if random_flip and np.random.choice([True, False]): 189 | image = np.fliplr(image) 190 | return image 191 | 192 | def to_rgb(img): 193 | w, h = img.shape 194 | ret = np.empty((w, h, 3), dtype=np.uint8) 195 | ret[:, :, 0] = ret[:, :, 1] = ret[:, :, 2] = img 196 | return ret 197 | 198 | def load_data(image_paths, do_random_crop, do_random_flip, image_size, do_prewhiten=True): 199 | nrof_samples = len(image_paths) 200 | images = np.zeros((nrof_samples, image_size, image_size, 3)) 201 | for i in range(nrof_samples): 202 | img = misc.imread(image_paths[i]) 203 | if img.ndim == 2: 204 | img = to_rgb(img) 205 | if do_prewhiten: 206 | img = prewhiten(img) 207 | img = crop(img, do_random_crop, image_size) 208 | img = flip(img, do_random_flip) 209 | images[i,:,:,:] = img 210 | return images 211 | 212 | def get_label_batch(label_data, batch_size, batch_index): 213 | nrof_examples = np.size(label_data, 0) 214 | j = batch_index*batch_size % nrof_examples 215 | if j+batch_size<=nrof_examples: 216 | batch = label_data[j:j+batch_size] 217 | else: 218 | x1 = label_data[j:nrof_examples] 219 | x2 = label_data[0:nrof_examples-j] 220 | batch = np.vstack([x1,x2]) 221 | batch_int = batch.astype(np.int64) 222 | return batch_int 223 | 224 | def get_batch(image_data, batch_size, batch_index): 225 | nrof_examples = np.size(image_data, 0) 226 | j = batch_index*batch_size % nrof_examples 227 | if j+batch_size<=nrof_examples: 228 | batch = image_data[j:j+batch_size,:,:,:] 229 | else: 230 | x1 = image_data[j:nrof_examples,:,:,:] 231 | x2 = image_data[0:nrof_examples-j,:,:,:] 232 | batch = np.vstack([x1,x2]) 233 | batch_float = batch.astype(np.float32) 234 | return batch_float 235 | 236 | def get_triplet_batch(triplets, batch_index, batch_size): 237 | ax, px, nx = triplets 238 | a = get_batch(ax, int(batch_size/3), batch_index) 239 | p = get_batch(px, int(batch_size/3), batch_index) 240 | n = get_batch(nx, int(batch_size/3), batch_index) 241 | batch = np.vstack([a, p, n]) 242 | return batch 243 | 244 | def get_learning_rate_from_file(filename, epoch): 245 | with open(filename, 'r') as f: 246 | for line in f.readlines(): 247 | line = line.split('#', 1)[0] 248 | if line: 249 | par = line.strip().split(':') 250 | e = int(par[0]) 251 | if par[1]=='-': 252 | lr = -1 253 | else: 254 | lr = float(par[1]) 255 | if e <= epoch: 256 | learning_rate = lr 257 | else: 258 | return learning_rate 259 | 260 | class ImageClass(): 261 | "Stores the paths to images for a given class" 262 | def __init__(self, name, image_paths): 263 | self.name = name 264 | self.image_paths = image_paths 265 | 266 | def __str__(self): 267 | return self.name + ', ' + str(len(self.image_paths)) + ' images' 268 | 269 | def __len__(self): 270 | return len(self.image_paths) 271 | 272 | def get_dataset(path, has_class_directories=True): 273 | dataset = [] 274 | path_exp = os.path.expanduser(path) 275 | classes = [path for path in os.listdir(path_exp) \ 276 | if os.path.isdir(os.path.join(path_exp, path))] 277 | classes.sort() 278 | nrof_classes = len(classes) 279 | for i in range(nrof_classes): 280 | class_name = classes[i] 281 | facedir = os.path.join(path_exp, class_name) 282 | image_paths = get_image_paths(facedir) 283 | dataset.append(ImageClass(class_name, image_paths)) 284 | 285 | return dataset 286 | 287 | def get_image_paths(facedir): 288 | image_paths = [] 289 | if os.path.isdir(facedir): 290 | images = os.listdir(facedir) 291 | image_paths = [os.path.join(facedir,img) for img in images] 292 | return image_paths 293 | 294 | def split_dataset(dataset, split_ratio, min_nrof_images_per_class, mode): 295 | if mode=='SPLIT_CLASSES': 296 | nrof_classes = len(dataset) 297 | class_indices = np.arange(nrof_classes) 298 | np.random.shuffle(class_indices) 299 | split = int(round(nrof_classes*(1-split_ratio))) 300 | train_set = [dataset[i] for i in class_indices[0:split]] 301 | test_set = [dataset[i] for i in class_indices[split:-1]] 302 | elif mode=='SPLIT_IMAGES': 303 | train_set = [] 304 | test_set = [] 305 | for cls in dataset: 306 | paths = cls.image_paths 307 | np.random.shuffle(paths) 308 | nrof_images_in_class = len(paths) 309 | split = int(math.floor(nrof_images_in_class*(1-split_ratio))) 310 | if split==nrof_images_in_class: 311 | split = nrof_images_in_class-1 312 | if split>=min_nrof_images_per_class and nrof_images_in_class-split>=1: 313 | train_set.append(ImageClass(cls.name, paths[:split])) 314 | test_set.append(ImageClass(cls.name, paths[split:])) 315 | else: 316 | raise ValueError('Invalid train/test split mode "%s"' % mode) 317 | return train_set, test_set 318 | 319 | def load_model(graph, model, input_map=None): 320 | model_exp = os.path.expanduser(model) 321 | if (os.path.isfile(model_exp)): 322 | print('Model filename: %s' % model_exp) 323 | with gfile.FastGFile(model_exp,'rb') as f: 324 | graph_def = tf.GraphDef() 325 | graph_def.ParseFromString(f.read()) 326 | tf.import_graph_def(graph_def, input_map=input_map, name='') 327 | else: 328 | print('Model directory: %s' % model) 329 | meta_file, ckpt_file = get_model_filenames(model) 330 | 331 | print('Metagraph file: %s' % meta_file) 332 | print('Checkpoint file: %s' % ckpt_file) 333 | 334 | saver = tf.train.import_meta_graph(os.path.join(model_exp, meta_file), input_map=input_map) 335 | saver.restore(tf.get_default_session(), os.path.join(model_exp, ckpt_file)) 336 | 337 | def get_model_filenames(model_dir): 338 | files = os.listdir(model_dir) 339 | meta_files = [s for s in files if s.endswith('.meta')] 340 | if len(meta_files)==0: 341 | raise ValueError('No meta file found in the model directory (%s)' % model_dir) 342 | elif len(meta_files)>1: 343 | raise ValueError('There should not be more than one meta file in the model directory (%s)' % model_dir) 344 | meta_file = meta_files[0] 345 | ckpt = tf.train.get_checkpoint_state(model_dir) 346 | if ckpt and ckpt.model_checkpoint_path: 347 | ckpt_file = os.path.basename(ckpt.model_checkpoint_path) 348 | return meta_file, ckpt_file 349 | 350 | meta_files = [s for s in files if '.ckpt' in s] 351 | max_step = -1 352 | for f in files: 353 | step_str = re.match(r'(^model-[\w\- ]+.ckpt-(\d+))', f) 354 | if step_str is not None and len(step_str.groups())>=2: 355 | step = int(step_str.groups()[1]) 356 | if step > max_step: 357 | max_step = step 358 | ckpt_file = step_str.groups()[0] 359 | return meta_file, ckpt_file 360 | 361 | def distance(embeddings1, embeddings2, distance_metric=0): 362 | if distance_metric==0: 363 | # Euclidian distance 364 | diff = np.subtract(embeddings1, embeddings2) 365 | dist = np.sum(np.square(diff),1) 366 | elif distance_metric==1: 367 | # Distance based on cosine similarity 368 | dot = np.sum(np.multiply(embeddings1, embeddings2), axis=1) 369 | norm = np.linalg.norm(embeddings1, axis=1) * np.linalg.norm(embeddings2, axis=1) 370 | similarity = dot / norm 371 | dist = np.arccos(similarity) / math.pi 372 | else: 373 | raise 'Undefined distance metric %d' % distance_metric 374 | 375 | return dist 376 | 377 | def calculate_roc(thresholds, embeddings1, embeddings2, actual_issame, nrof_folds=10, distance_metric=0, subtract_mean=False): 378 | assert(embeddings1.shape[0] == embeddings2.shape[0]) 379 | assert(embeddings1.shape[1] == embeddings2.shape[1]) 380 | nrof_pairs = min(len(actual_issame), embeddings1.shape[0]) 381 | nrof_thresholds = len(thresholds) 382 | k_fold = KFold(n_splits=nrof_folds, shuffle=False) 383 | accuracy = [[0.]*5]*nrof_folds 384 | 385 | indices = np.arange(nrof_pairs) 386 | 387 | for fold_idx, (train_set, test_set) in enumerate(k_fold.split(indices)): 388 | 389 | if subtract_mean: 390 | mean = np.mean(np.concatenate([embeddings1[train_set], embeddings2[train_set]]), axis=0) 391 | else: 392 | mean = 0.0 393 | dist = distance(embeddings1-mean, embeddings2-mean, distance_metric) 394 | tp_ = [[False]* dist[test_set]]*5 395 | tn_ = [[False]* dist[test_set]]*5 396 | 397 | # Find the best threshold for the fold 398 | acc_train = np.zeros((nrof_thresholds)) 399 | 400 | for threshold_idx, threshold in enumerate(thresholds): 401 | _, _, acc_train[threshold_idx] = calculate_accuracy(threshold, dist[train_set], actual_issame[train_set]) 402 | best5_threshold_index = list(map(list(acc_train).index, heapq.nlargest(5, set(acc_train)))) 403 | 404 | for index, threshold_index in enumerate(best5_threshold_index[:5]): 405 | tp, tn, _ = calculate_accuracy(thresholds[threshold_index], dist[test_set], actual_issame[test_set]) 406 | 407 | tn_[index:], tp_[index:] = np.logical_or(tn_[index:], tn), np.logical_or(tp_[index:], tp) 408 | 409 | accuracy[fold_idx] = list(map(lambda x: x/dist[test_set].size, np.array(np.sum(tn_, axis=1))+np.array(np.sum(tp_, axis=1)))) 410 | 411 | return accuracy 412 | 413 | def calculate_accuracy(threshold, dist, actual_issame): 414 | predict_issame = np.less(dist, threshold) 415 | # 416 | tp = np.logical_and(predict_issame, actual_issame) 417 | tn = np.logical_and(np.logical_not(predict_issame), np.logical_not(actual_issame)) 418 | 419 | acc = float(np.sum(tp)+np.sum(tn))/dist.size 420 | 421 | return tp, tn, acc 422 | 423 | 424 | def calculate_val(thresholds, embeddings1, embeddings2, actual_issame, far_target, nrof_folds=10, distance_metric=0, subtract_mean=False): 425 | assert(embeddings1.shape[0] == embeddings2.shape[0]) 426 | assert(embeddings1.shape[1] == embeddings2.shape[1]) 427 | nrof_pairs = min(len(actual_issame), embeddings1.shape[0]) 428 | nrof_thresholds = len(thresholds) 429 | k_fold = KFold(n_splits=nrof_folds, shuffle=False) 430 | 431 | val = np.zeros(nrof_folds) 432 | far = np.zeros(nrof_folds) 433 | 434 | indices = np.arange(nrof_pairs) 435 | 436 | for fold_idx, (train_set, test_set) in enumerate(k_fold.split(indices)): 437 | if subtract_mean: 438 | mean = np.mean(np.concatenate([embeddings1[train_set], embeddings2[train_set]]), axis=0) 439 | else: 440 | mean = 0.0 441 | dist = distance(embeddings1-mean, embeddings2-mean, distance_metric) 442 | 443 | # Find the threshold that gives FAR = far_target 444 | far_train = np.zeros(nrof_thresholds) 445 | for threshold_idx, threshold in enumerate(thresholds): 446 | _, far_train[threshold_idx] = calculate_val_far(threshold, dist[train_set], actual_issame[train_set]) 447 | if np.max(far_train)>=far_target: 448 | f = interpolate.interp1d(far_train, thresholds, kind='slinear') 449 | threshold = f(far_target) 450 | else: 451 | threshold = 0.0 452 | 453 | val[fold_idx], far[fold_idx] = calculate_val_far(threshold, dist[test_set], actual_issame[test_set]) 454 | 455 | val_mean = np.mean(val) 456 | far_mean = np.mean(far) 457 | val_std = np.std(val) 458 | return val_mean, val_std, far_mean 459 | 460 | 461 | def calculate_val_far(threshold, dist, actual_issame): 462 | predict_issame = np.less(dist, threshold) 463 | true_accept = np.sum(np.logical_and(predict_issame, actual_issame)) 464 | false_accept = np.sum(np.logical_and(predict_issame, np.logical_not(actual_issame))) 465 | n_same = np.sum(actual_issame) 466 | n_diff = np.sum(np.logical_not(actual_issame)) 467 | val = float(true_accept) / float(n_same) 468 | far = float(false_accept) / float(n_diff) 469 | return val, far 470 | 471 | def store_revision_info(src_path, output_dir, arg_string): 472 | try: 473 | # Get git hash 474 | cmd = ['git', 'rev-parse', 'HEAD'] 475 | gitproc = Popen(cmd, stdout = PIPE, cwd=src_path) 476 | (stdout, _) = gitproc.communicate() 477 | git_hash = stdout.strip() 478 | except OSError as e: 479 | git_hash = ' '.join(cmd) + ': ' + e.strerror 480 | 481 | try: 482 | # Get local changes 483 | cmd = ['git', 'diff', 'HEAD'] 484 | gitproc = Popen(cmd, stdout = PIPE, cwd=src_path) 485 | (stdout, _) = gitproc.communicate() 486 | git_diff = stdout.strip() 487 | except OSError as e: 488 | git_diff = ' '.join(cmd) + ': ' + e.strerror 489 | 490 | # Store a text file in the log directory 491 | rev_info_filename = os.path.join(output_dir, 'revision_info.txt') 492 | with open(rev_info_filename, "w") as text_file: 493 | text_file.write('arguments: %s\n--------------------\n' % arg_string) 494 | text_file.write('tensorflow version: %s\n--------------------\n' % tf.__version__) # @UndefinedVariable 495 | text_file.write('git hash: %s\n--------------------\n' % git_hash) 496 | text_file.write('%s' % git_diff) 497 | 498 | def list_variables(filename): 499 | reader = training.NewCheckpointReader(filename) 500 | variable_map = reader.get_variable_to_shape_map() 501 | names = sorted(variable_map.keys()) 502 | return names 503 | 504 | def put_images_on_grid(images, shape=(16,8)): 505 | nrof_images = images.shape[0] 506 | img_size = images.shape[1] 507 | bw = 3 508 | img = np.zeros((shape[1]*(img_size+bw)+bw, shape[0]*(img_size+bw)+bw, 3), np.float32) 509 | for i in range(shape[1]): 510 | x_start = i*(img_size+bw)+bw 511 | for j in range(shape[0]): 512 | img_index = i*shape[0]+j 513 | if img_index>=nrof_images: 514 | break 515 | y_start = j*(img_size+bw)+bw 516 | img[x_start:x_start+img_size, y_start:y_start+img_size, :] = images[img_index, :, :, :] 517 | if img_index>=nrof_images: 518 | break 519 | return img 520 | 521 | def write_arguments_to_file(args, filename): 522 | with open(filename, 'w') as f: 523 | for key, value in iteritems(vars(args)): 524 | f.write('%s: %s\n' % (key, str(value))) 525 | -------------------------------------------------------------------------------- /object_detection.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import argparse 3 | import platform 4 | import numpy 5 | import subprocess 6 | from edgetpu.detection.engine import DetectionEngine 7 | from edgetpu.utils import dataset_utils 8 | from PIL import Image 9 | from PIL import ImageDraw 10 | 11 | 12 | def main(): 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument( 15 | '--model', 16 | help='Path of the detection model, it must be a SSD model with postprocessing operator.', 17 | required=True) 18 | parser.add_argument('--label', help='Path of the labels file.') 19 | parser.add_argument('--output', help='File path of the output image.') 20 | parser.add_argument( 21 | '--keep_aspect_ratio', 22 | dest='keep_aspect_ratio', 23 | action='store_true', 24 | help=( 25 | 'keep the image aspect ratio when down-sampling the image by adding ' 26 | 'black pixel padding (zeros) on bottom or right. ' 27 | 'By default the image is resized and reshaped without cropping. This ' 28 | 'option should be the same as what is applied on input images during ' 29 | 'model training. Otherwise the accuracy may be affected and the ' 30 | 'bounding box of detection result may be stretched.')) 31 | parser.set_defaults(keep_aspect_ratio=False) 32 | args = parser.parse_args() 33 | 34 | if not args.output: 35 | output_name = 'object_detection_result.jpg' 36 | else: 37 | output_name = args.output 38 | 39 | # Initialize engine. 40 | engine = DetectionEngine(args.model) 41 | labels = dataset_utils.ReadLabelFile(args.label) if args.label else None 42 | 43 | # Open image. 44 | # img = Image.open(args.input) 45 | #draw = ImageDraw.Draw(img) 46 | 47 | 48 | 49 | cap = cv2.VideoCapture(0) 50 | 51 | while(True): 52 | # 從攝影機擷取一張影像 53 | ret, frame = cap.read() 54 | 55 | # img = Image.open(im) 56 | img = Image.fromarray(cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)) 57 | draw = ImageDraw.Draw(img) 58 | 59 | 60 | # Run inference. 61 | ans = engine.DetectWithImage( 62 | img, 63 | threshold=0.05, 64 | keep_aspect_ratio=args.keep_aspect_ratio, 65 | relative_coord=False, 66 | top_k=10) 67 | 68 | # Display result. 69 | if ans: 70 | for obj in ans: 71 | print('-----------------------------------------') 72 | if labels: 73 | print(labels[obj.label_id]) 74 | print('score = ', obj.score) 75 | box = obj.bounding_box.flatten().tolist() 76 | print('box = ', box) 77 | # Draw a rectangle. 78 | draw.rectangle(box, outline='red') 79 | 80 | img_ = cv2.cvtColor(numpy.asarray(img),cv2.COLOR_RGB2BGR) 81 | 82 | cv2.imshow('frame', img_) 83 | 84 | # 若按下 q 鍵則離開迴圈 85 | if cv2.waitKey(1) & 0xFF == ord('q'): 86 | break 87 | 88 | # 釋放攝影機 89 | cap.release() 90 | 91 | # 關閉所有 OpenCV 視窗 92 | cv2.destroyAllWindows() 93 | 94 | 95 | if __name__ == '__main__': 96 | main() 97 | -------------------------------------------------------------------------------- /pictures/KrisWu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kao1126/EdgeTPU-FaceNet/d28eb4d3e212b3657d71b4c2ccc8247a47babdee/pictures/KrisWu.jpg -------------------------------------------------------------------------------- /pictures/WangLihong.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kao1126/EdgeTPU-FaceNet/d28eb4d3e212b3657d71b4c2ccc8247a47babdee/pictures/WangLihong.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==0.7.1 2 | alabaster==0.7.8 3 | anaconda-client==1.4.0 4 | anaconda-navigator==1.8.4 5 | anaconda-project==0.8.3 6 | argcomplete==1.0.0 7 | asn1crypto==0.24.0 8 | astor==0.8.0 9 | astroid==2.0.4 10 | astropy==1.2.1 11 | Babel==2.3.3 12 | backports.shutil-get-terminal-size==1.0.0 13 | beautifulsoup4==4.4.1 14 | bitarray==0.8.1 15 | bokeh==0.11.1 16 | boto==2.40.0 17 | Bottleneck==1.0.0 18 | certifi==2018.8.24 19 | cffi==1.12.3 20 | chardet==3.0.4 21 | chest==0.2.3 22 | click==6.6 23 | cloudpickle==0.2.1 24 | clyent==1.2.2 25 | colorama==0.3.7 26 | comtypes==1.1.2 27 | conda==4.5.11 28 | conda-build==1.21.2 29 | configobj==5.0.6 30 | contextlib2==0.5.3 31 | cryptography==2.7 32 | cycler==0.10.0 33 | Cython==0.28.5 34 | cytoolz==0.9.0.1 35 | dask==0.10.0 36 | datashape==0.5.2 37 | decorator==4.0.10 38 | dill==0.2.5 39 | docutils==0.12 40 | dynd===c328ab7 41 | et-xmlfile==1.0.1 42 | fastcache==1.0.2 43 | flask>=1.0.0 44 | Flask-Cors==2.1.2 45 | gast==0.2.2 46 | gevent==1.1.1 47 | google-pasta==0.1.7 48 | greenlet==0.4.10 49 | grpcio==1.22.0 50 | h5py==2.9.0 51 | HeapDict==1.0.0 52 | idna==2.1 53 | imagesize==0.7.1 54 | ipykernel==4.3.1 55 | ipython==5.0.0 56 | ipython-genutils==0.1.0 57 | ipywidgets==4.1.1 58 | isort==4.3.4 59 | itsdangerous==0.24 60 | jdcal==1.2 61 | jedi==0.9.0 62 | Jinja2>=2.10.1 63 | jsonschema==2.5.1 64 | jupyter==1.0.0 65 | jupyter-client==4.3.0 66 | jupyter-console==4.1.1 67 | jupyter-core==4.1.0 68 | Keras-Applications==1.0.8 69 | Keras-Preprocessing==1.1.0 70 | keyring==13.2.1 71 | lazy-object-proxy==1.3.1 72 | llvmlite==0.11.0 73 | locket==0.2.0 74 | lxml==3.6.0 75 | Markdown==3.1.1 76 | MarkupSafe==0.23 77 | matplotlib==1.5.1 78 | mccabe==0.6.1 79 | menuinst==1.4.1 80 | mistune>=0.8.1 81 | mpmath==0.19 82 | multipledispatch==0.4.8 83 | nb-anacondacloud==1.1.0 84 | nb-conda==1.1.0 85 | nb-conda-kernels==1.0.3 86 | nbconvert==4.2.0 87 | nbformat==4.0.1 88 | nbpresent==3.0.2 89 | networkx==1.11 90 | nltk>=3.4.5 91 | nose==1.3.7 92 | notebook>=5.7.8 93 | numba==0.26.0 94 | numexpr==2.6.0 95 | numpy==1.17.0 96 | numpydoc==0.9.1 97 | odo==0.5.0 98 | olefile==0.46 99 | openpyxl==2.3.2 100 | opt-einsum==2.3.2 101 | pandas==0.18.1 102 | partd==0.3.4 103 | path.py==0.0.0 104 | pathlib2==2.1.0 105 | patsy==0.4.1 106 | pep8==1.7.0 107 | pickleshare==0.7.2 108 | pillow>=6.2.0 109 | ply==3.8 110 | prompt-toolkit==1.0.15 111 | protobuf==3.9.1 112 | psutil==4.3.0 113 | py==1.4.31 114 | pyasn1==0.1.9 115 | pycocotools==2.0 116 | pycodestyle==2.4.0 117 | pycosat==0.6.3 118 | pycparser==2.14 119 | pycurl==7.43.0.2 120 | pyflakes==1.2.3 121 | Pygments==2.1.3 122 | pylint==2.1.1 123 | pyopenssl>=17.5.0 124 | pyparsing==2.1.4 125 | pyreadline==2.1 126 | pytest==2.9.2 127 | python-dateutil==2.5.3 128 | pytz==2016.4 129 | pywin32==220 130 | pyyaml>=4.2b1 131 | pyzmq==15.2.0 132 | QtAwesome==0.5.7 133 | qtconsole==4.5.4 134 | QtPy==1.9.0 135 | requests>=2.20.0 136 | rope-py3k==0.9.4.post1 137 | scikit-image==0.12.3 138 | scikit-learn==0.17.1 139 | scipy==0.17.1 140 | simplegeneric==0.8.1 141 | singledispatch==3.4.0.3 142 | six==1.12.0 143 | snowballstemmer==1.2.1 144 | sockjs-tornado==1.0.3 145 | Sphinx==1.3.1 146 | sphinx-rtd-theme==0.1.9 147 | spyder==3.3.1 148 | spyder-kernels==0.2.4 149 | SQLAlchemy>=1.3.0 150 | statsmodels==0.6.1 151 | sympy==1.0 152 | tables==3.2.2 153 | tb-nightly==1.15.0a20190802 154 | tensorboard==1.14.0 155 | tensorflow==1.14.0 156 | tensorflow-estimator==1.14.0 157 | tensorflow-gpu==1.14.0 158 | termcolor==1.1.0 159 | tf-estimator-nightly==1.14.0.dev2019080401 160 | tf-nightly==1.15.0.dev20190804 161 | toolz==0.8.0 162 | tornado==4.3 163 | traitlets==4.2.1 164 | typed-ast==1.1.0 165 | unicodecsv==0.14.1 166 | wcwidth==0.1.7 167 | Werkzeug==0.15.5 168 | win-unicode-console==0.5 169 | wincertstore==0.2 170 | wrapt==1.11.2 171 | xlrd==1.0.0 172 | XlsxWriter==0.9.2 173 | xlwings==0.7.2 174 | xlwt==1.1.2 175 | -------------------------------------------------------------------------------- /sample.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kao1126/EdgeTPU-FaceNet/d28eb4d3e212b3657d71b4c2ccc8247a47babdee/sample.JPG -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from edgetpu.classification.engine import ClassificationEngine 3 | from edgetpu.utils import dataset_utils 4 | from PIL import Image 5 | import numpy as np 6 | 7 | 8 | from edgetpu.basic.basic_engine import BasicEngine 9 | from PIL import Image 10 | 11 | 12 | def takeSecond(elem): 13 | return elem[0] 14 | 15 | 16 | def Tpu_FaceRecognize(engine, face_img): 17 | 18 | faces = [] 19 | for face in face_img: 20 | img = np.asarray(face).flatten() 21 | result = engine.ClassifyWithInputTensor(img, top_k=200, threshold=-0.5) 22 | result.sort(key=takeSecond) 23 | 24 | np_result = [] 25 | for i in range(0, len(result)): 26 | np_result.append(result[i][1]) 27 | 28 | faces.append(np_result) 29 | np_face = np.array(faces) 30 | 31 | return np_face 32 | 33 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | 2 | # coding: utf-8 3 | 4 | # In[1]: 5 | 6 | 7 | import numpy as np 8 | import os 9 | from tqdm import tqdm 10 | import math 11 | import tensorflow as tf 12 | from scipy import misc 13 | 14 | # In[2]: 15 | 16 | 17 | def IOU(box,boxes): 18 | '''裁剪的box和图片所有人脸box的iou值 19 | 参数: 20 | box:裁剪的box,当box维度为4时表示box左上右下坐标,维度为5时,最后一维为box的置信度 21 | boxes:图片所有人脸box,[n,4] 22 | 返回值: 23 | iou值,[n,] 24 | ''' 25 | #box面积 26 | box_area=(box[2]-box[0]+1)*(box[3]-box[1]+1) 27 | #boxes面积,[n,] 28 | area=(boxes[:,2]-boxes[:,0]+1)*(boxes[:,3]-boxes[:,1]+1) 29 | #重叠部分左上右下坐标 30 | xx1=np.maximum(box[0],boxes[:,0]) 31 | yy1=np.maximum(box[1],boxes[:,1]) 32 | xx2=np.minimum(box[2],boxes[:,2]) 33 | yy2=np.minimum(box[3],boxes[:,3]) 34 | 35 | #重叠部分长宽 36 | w=np.maximum(0,xx2-xx1+1) 37 | h=np.maximum(0,yy2-yy1+1) 38 | #重叠部分面积 39 | inter=w*h 40 | return inter/(box_area+area-inter+1e-10) 41 | 42 | 43 | # In[3]: 44 | 45 | def read_annotation(base_dir, label_path): 46 | '''读取文件的image,box''' 47 | data = dict() 48 | images = [] 49 | bboxes = [] 50 | labelfile = open(label_path, 'r') 51 | while True: 52 | # 图像地址 53 | imagepath = labelfile.readline().strip('\n') 54 | if not imagepath: 55 | break 56 | imagepath = base_dir + '/images/' + imagepath 57 | images.append(imagepath) 58 | # 人脸数目 59 | nums = labelfile.readline().strip('\n') 60 | 61 | one_image_bboxes = [] 62 | for i in range(int(nums)): 63 | 64 | bb_info = labelfile.readline().strip('\n').split(' ') 65 | #人脸框 66 | face_box = [float(bb_info[i]) for i in range(4)] 67 | 68 | xmin = face_box[0] 69 | ymin = face_box[1] 70 | xmax = xmin + face_box[2] 71 | ymax = ymin + face_box[3] 72 | 73 | one_image_bboxes.append([xmin, ymin, xmax, ymax]) 74 | 75 | bboxes.append(one_image_bboxes) 76 | 77 | 78 | data['images'] = images 79 | data['bboxes'] = bboxes 80 | return data 81 | def convert_to_square(box): 82 | '''将box转换成更大的正方形 83 | 参数: 84 | box:预测的box,[n,5] 85 | 返回值: 86 | 调整后的正方形box,[n,5] 87 | ''' 88 | square_box=box.copy() 89 | h=box[:,3]-box[:,1]+1 90 | w=box[:,2]-box[:,0]+1 91 | #找寻正方形最大边长 92 | max_side=np.maximum(w,h) 93 | 94 | square_box[:,0]=box[:,0]+w*0.5-max_side*0.5 95 | square_box[:,1]=box[:,1]+h*0.5-max_side*0.5 96 | square_box[:,2]=square_box[:,0]+max_side-1 97 | square_box[:,3]=square_box[:,1]+max_side-1 98 | return square_box 99 | class ImageClass(): 100 | '''获取图片类别和路径''' 101 | def __init__(self, name, image_paths): 102 | self.name = name 103 | self.image_paths = image_paths 104 | 105 | def __str__(self): 106 | return self.name + ', ' + str(len(self.image_paths)) + ' images' 107 | 108 | def __len__(self): 109 | return len(self.image_paths) 110 | 111 | def get_dataset(paths): 112 | dataset = [] 113 | classes = [path for path in os.listdir(paths) if os.path.isdir(os.path.join(paths, path))] 114 | classes.sort() 115 | nrof_classes = len(classes) 116 | for i in tqdm(range(nrof_classes)): 117 | class_name = classes[i] 118 | facedir = os.path.join(paths, class_name) 119 | image_paths = get_image_paths(facedir) 120 | dataset.append(ImageClass(class_name, image_paths)) 121 | return dataset 122 | 123 | def get_image_paths(facedir): 124 | image_paths = [] 125 | if os.path.isdir(facedir): 126 | images = os.listdir(facedir) 127 | image_paths = [os.path.join(facedir,img) for img in images] 128 | return image_paths 129 | 130 | 131 | def split_dataset(dataset,split_ratio,min_nrof_images_per_class): 132 | '''拆分训练和验证集 133 | 参数: 134 | dataset:有get_dataset生成的数据集 135 | split_ratio:留取验证集的比例 136 | min_nrof_images_per_class:一个类别中最少含有的图片数量,过少舍弃 137 | 返回值: 138 | train_set,test_set:还有图片类别和路径的训练验证集 139 | ''' 140 | train_set=[] 141 | test_set=[] 142 | for cls in dataset: 143 | paths=cls.image_paths 144 | np.random.shuffle(paths) 145 | #某一种类图片个数 146 | nrof_images_in_class=len(paths) 147 | #留取训练的比例 148 | split=int(math.floor(nrof_images_in_class*(1-split_ratio))) 149 | if split==nrof_images_in_class: 150 | split=nrof_images_in_class-1 151 | if split>=min_nrof_images_per_class and nrof_images_in_class-split>=1: 152 | train_set.append(ImageClass(cls.name,paths[:split])) 153 | test_set.append(ImageClass(cls.name,paths[split:])) 154 | return train_set,test_set 155 | 156 | def get_image_paths_and_labels(dataset): 157 | '''获取所有图像地址和类别''' 158 | image_paths_flat=[] 159 | labels_flat=[] 160 | for i in range(len(dataset)): 161 | image_paths_flat+=dataset[i].image_paths 162 | labels_flat+=[i]*len(dataset[i].image_paths) 163 | return image_paths_flat,labels_flat 164 | 165 | def create_input_pipeline(input_queue,image_size,nrof_preprocess_threads,bath_size_placeholder): 166 | '''由输入队列返回图片和label的batch组合 167 | 参数: 168 | input_queue:输入队列 169 | image_size:图片尺寸 170 | nrof_preprocess_threads:线程数 171 | batch_size_placeholder:batch_size的placeholder 172 | 返回值: 173 | image_batch,label_batch:图片和label的batch组合 174 | ''' 175 | image_and_labels_list=[] 176 | for _ in range(nrof_preprocess_threads): 177 | filenames,label=input_queue.dequeue() 178 | images=[] 179 | for filename in tf.unstack(filenames): 180 | file_contents=tf.read_file(filename) 181 | image=tf.image.decode_image(file_contents,3) 182 | #随机翻转图像 183 | image=tf.cond(tf.constant(np.random.uniform()>0.8), 184 | lambda:tf.py_func(random_rotate_image,[image],tf.uint8), 185 | lambda:tf.identity(image)) 186 | #随机裁剪图像 187 | image=tf.cond(tf.constant(np.random.uniform()>0.5), 188 | lambda:tf.random_crop(image,image_size+(3,)), 189 | lambda:tf.image.resize_image_with_crop_or_pad(image,image_size[0],image_size[1])) 190 | #随机左右翻转图像 191 | image=tf.cond(tf.constant(np.random.uniform()>0.7), 192 | lambda:tf.image.random_flip_left_right(image), 193 | lambda:tf.identity(image)) 194 | #图像归一到[-1,1]内 195 | image=tf.cast(image,tf.float32)-127.5/128.0 196 | image.set_shape(image_size+(3,)) 197 | images.append(image) 198 | image_and_labels_list.append([images,label]) 199 | image_batch,label_batch=tf.train.batch_join(image_and_labels_list, 200 | batch_size=bath_size_placeholder, 201 | shapes=[image_size+(3,),()], 202 | enqueue_many=True, 203 | capacity=4*nrof_preprocess_threads*100, 204 | allow_smaller_final_batch=True) 205 | return image_batch,label_batch 206 | 207 | def random_rotate_image(image): 208 | '''随机翻转图片''' 209 | angle = np.random.uniform(low=-10.0, high=10.0) 210 | return misc.imrotate(image, angle, 'bicubic') 211 | -------------------------------------------------------------------------------- /validate_lfw.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from PIL import Image 4 | from edgetpu.classification.engine import ClassificationEngine 5 | import time 6 | from config import* 7 | 8 | 9 | def main(txt_path, image_path): 10 | pair = read_pairs(txt_path) 11 | path_list, issame_list = get_paths(image_path, pair) 12 | 13 | min_THREAD = 0.5 14 | max_THREAD = 2 15 | step = 0.5 16 | accuracy_list = [] 17 | THREAD_list = [] 18 | 19 | print("-------------------------------------------------") 20 | 21 | for THREAD in np.arange(min_THREAD, max_THREAD, step): 22 | THREAD_list.append(THREAD) 23 | tick1 = time.time() 24 | accuracy = classify(path_list, issame_list, THREAD) 25 | accuracy_list.append(accuracy) 26 | tick2 = time.time() 27 | print("THREAD:{:.2f} finish!".format(THREAD)) 28 | print("Accuracy:{:.2f}".format(accuracy)) 29 | print("Time: {:.2f}".format(tick2 - tick1)) 30 | print("-------------------------------------------------") 31 | save_txt(accuracy, THREAD) 32 | 33 | 34 | print("max_accuracy:", max(accuracy_list)) 35 | 36 | def save_txt(accuracy_list, THREAD_list): 37 | 38 | with open('lfw_score.txt', 'a+') as f: 39 | 40 | f.write(' Thread:') 41 | f.write('{:2f}'.format(THREAD_list)) 42 | f.write(' Accuracy:') 43 | f.write('{:2f}\n'.format(accuracy_list)) 44 | 45 | def classify(path_list_, same_list_, THREAD): 46 | engine = ClassificationEngine(FaceNet_weight) 47 | pred = bool() 48 | correct = 0 49 | for same_index, pair in enumerate(path_list_): 50 | picture1_embs = [] 51 | picture2_embs = [] 52 | 53 | for k, img in enumerate(pair): 54 | img = Image.open(img) 55 | img = np.asarray(img).flatten() 56 | result = engine.ClassifyWithInputTensor(img, top_k=200, threshold=-0.5) 57 | result.sort(key=takeSecond) 58 | 59 | if k == 1: 60 | for i in range(0, len(result)): 61 | picture1_embs.append(result[i][1]) 62 | else: 63 | for i in range(0, len(result)): 64 | picture2_embs.append(result[i][1]) 65 | 66 | picture1_embs = np.array(picture1_embs) 67 | picture2_embs = np.array(picture2_embs) 68 | 69 | diff = np.mean(np.square(picture1_embs-picture2_embs)) 70 | 71 | if diff < THREAD: 72 | pred = True 73 | else: 74 | pred = False 75 | 76 | if pred == same_list_[same_index]: 77 | correct += 1 78 | 79 | 80 | accuracy = correct/len(path_list_) 81 | 82 | return accuracy 83 | 84 | def get_paths(lfw_dir, pairs): 85 | nrof_skipped_pairs = 0 86 | path_list = [] 87 | issame_list = [] 88 | path0 = '' 89 | for pair in pairs: 90 | if len(pair) == 3: 91 | path0 = os.path.join(lfw_dir, pair[0], pair[0] + '_' + '%04d' % int(pair[1]))+'.jpg' 92 | path1 = os.path.join(lfw_dir, pair[0], pair[0] + '_' + '%04d' % int(pair[2]))+'.jpg' 93 | issame = True 94 | elif len(pair) == 4: 95 | path0 = os.path.join(lfw_dir, pair[0], pair[0] + '_' + '%04d' % int(pair[1]))+'.jpg' 96 | path1 = os.path.join(lfw_dir, pair[2], pair[2] + '_' + '%04d' % int(pair[3]))+'.jpg' 97 | issame = False 98 | if os.path.exists(path0) and os.path.exists(path1): # Only add the pair if both paths exist 99 | path_list.append([path0, path1]) 100 | issame_list.append(issame) 101 | else: 102 | nrof_skipped_pairs += 1 103 | if nrof_skipped_pairs>0: 104 | print('Skipped %d image pairs' % nrof_skipped_pairs) 105 | 106 | return path_list, issame_list 107 | 108 | 109 | def read_pairs(pairs_filename): 110 | pairs = [] 111 | with open(pairs_filename, 'r') as f: 112 | for line in f.readlines()[1:]: 113 | pair = line.strip().split() 114 | pairs.append(pair) 115 | return np.array(pairs) 116 | 117 | def takeSecond(elem): 118 | return elem[0] 119 | 120 | 121 | if __name__ == "__main__": 122 | main('lfw_pairs.txt', 'lfw') --------------------------------------------------------------------------------