├── .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 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
81 |
82 |
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 | 
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')
--------------------------------------------------------------------------------