-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathface_recognition_knn_trainer.py
165 lines (138 loc) · 7.31 KB
/
face_recognition_knn_trainer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#-*- coding: utf-8 -*-
"""
This is an example of using the k-nearest-neighbors(knn) algorithm for face recognition.
When should I use this example?
This example is useful when you whish to recognize a large set of known people,
and make a prediction for an unkown person in a feasible computation time.
Algorithm Description:
The knn classifier is first trained on a set of labeled(known) faces, and can then predict the person
in an unkown image by finding the k most similar faces(images with closet face-features under eucledian distance) in its training set,
and performing a majority vote(possibly weighted) on their label.
For example, if k=3, and the three closest face images to the given image in the training set are one image of Biden and two images of Obama,
The result would be 'Obama'.
*This implemententation uses a weighted vote, such that the votes of closer-neighbors are weighted more heavily.
Usage:
-First, prepare a set of images of the known people you want to recognize.
Organize the images in a single directory with a sub-directory for each known person.
-Then, call the 'train' function with the appropriate parameters.
make sure to pass in the 'model_save_path' if you want to re-use the model without having to re-train it.
-After training the model, you can call 'predict' to recognize the person in an unknown image.
NOTE: This example requires scikit-learn to be installed! You can install it with pip:
$ pip3 install scikit-learn
"""
import os
from math import sqrt
from sklearn import neighbors
from os import listdir
from os.path import isdir, join, isfile, splitext
import pickle
from PIL import Image, ImageFont, ImageDraw, ImageEnhance
import face_recognition
from face_recognition import face_locations
from glob import glob
# from face_recognition.cli import image_files_in_folder
import cv2
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}
def train(train_dir, model_save_path = "", n_neighbors = None, knn_algo = 'ball_tree', verbose=False):
"""
Trains a k-nearest neighbors classifier for face recognition.
:param train_dir: directory that contains a sub-directory for each known person, with its name.
(View in source code to see train_dir example tree structure)
Structure:
<train_dir>/
├── <person1>/
│ ├── <somename1>.jpeg
│ ├── <somename2>.jpeg
│ ├── ...
├── <person2>/
│ ├── <somename1>.jpeg
│ └── <somename2>.jpeg
└── ...
:param model_save_path: (optional) path to save model of disk
:param n_neighbors: (optional) number of neighbors to weigh in classification. Chosen automatically if not specified.
:param knn_algo: (optional) underlying data structure to support knn.default is ball_tree
:param verbose: verbosity of training
:return: returns knn classifier that was trained on the given data.
"""
X = []
y = []
for class_dir in listdir(train_dir):
if not isdir(join(train_dir, class_dir)):
continue
for img_path in sorted(glob(join(train_dir, class_dir, '*'))):
image = face_recognition.load_image_file(img_path)
faces_bboxes = face_locations(image)
if len(faces_bboxes) != 1:
if verbose:
print("image {} not fit for training: {}".format(img_path, "didn't find a face" if len(faces_bboxes) < 1 else "found more than one face"))
continue
X.append(face_recognition.face_encodings(image, known_face_locations=faces_bboxes)[0])
y.append(class_dir)
if n_neighbors is None:
n_neighbors = int(round(sqrt(len(X))))
if verbose:
print("Chose n_neighbors automatically as:", n_neighbors)
knn_clf = neighbors.KNeighborsClassifier(n_neighbors=n_neighbors, algorithm=knn_algo, weights='distance')
knn_clf.fit(X, y)
if model_save_path != "":
with open(model_save_path, 'wb') as f:
pickle.dump(knn_clf, f)
return knn_clf
def predict(X_img_path, knn_clf = None, model_save_path ="", DIST_THRESH = .4):
"""
recognizes faces in given image, based on a trained knn classifier
:param X_img_path: path to image to be recognized
:param knn_clf: (optional) a knn classifier object. if not specified, model_save_path must be specified.
:param model_save_path: (optional) path to a pickled knn classifier. if not specified, model_save_path must be knn_clf.
:param DIST_THRESH: (optional) distance threshold in knn classification. the larger it is, the more chance of misclassifying an unknown person to a known one.
:return: a list of names and face locations for the recognized faces in the image: [(name, bounding box), ...].
For faces of unrecognized persons, the name 'N/A' will be passed.
"""
if not isfile(X_img_path) or splitext(X_img_path)[1][1:] not in ALLOWED_EXTENSIONS:
raise Exception("invalid image path: {}".format(X_img_path))
if knn_clf is None and model_save_path == "":
raise Exception("must supply knn classifier either thourgh knn_clf or model_save_path")
if knn_clf is None:
with open(model_save_path, 'rb') as f:
knn_clf = pickle.load(f)
X_img = face_recognition.load_image_file(X_img_path) # rgb
print (X_img.shape)
# show = cv2.cvtColor(X_img, cv2.COLOR_RGB2BGR)
# cv2.imshow('show', show)
# cv2.waitKey()
X_faces_loc = face_locations(X_img) # (top, right, bottom, left)
if len(X_faces_loc) == 0:
return []
print (X_faces_loc[0])
start = cv2.getTickCount()
faces_encodings = face_recognition.face_encodings(X_img, known_face_locations=X_faces_loc)
closest_distances = knn_clf.kneighbors(faces_encodings, n_neighbors=1)
time = (cv2.getTickCount() - start) / cv2.getTickFrequency() * 1000
print('prediction time: %.2fms'%time)
is_recognized = [closest_distances[0][i][0] <= DIST_THRESH for i in range(len(X_faces_loc))]
# predict classes and cull classifications that are not with high confidence
return [(pred, loc) if rec else ("unknown", loc) for pred, loc, rec in zip(knn_clf.predict(faces_encodings), X_faces_loc, is_recognized)]
def draw_preds(img_path, preds):
"""
shows the face recognition results visually.
:param img_path: path to image to be recognized
:param preds: results of the predict function
:return:
"""
source_img = Image.open(img_path).convert("RGBA")
draw = ImageDraw.Draw(source_img)
for pred in preds:
loc = pred[1]
name = pred[0]
# (top, right, bottom, left) => (left,top,right,bottom)
draw.rectangle(((loc[3], loc[0]), (loc[1],loc[2])), outline="red")
# draw.text((loc[3], loc[0] - 30), name, font=ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 30))
draw.text((loc[3], loc[0] - 21), name, font=ImageFont.truetype('./BMDOHYEON_TTF.TTF', 20))
source_img.show()
if __name__ == "__main__":
knn_clf = train("./data/train", model_save_path='./models/fr_knn.pkl')
for img_path in listdir("./data/test"):
# preds = predict(join("./data/test", img_path), knn_clf=knn_clf)
preds = predict(join("./data/test", img_path), model_save_path='./models/fr_knn.pkl', DIST_THRESH=0.4)
print(os.path.basename(img_path), preds)
# draw_preds(join("./data/test", img_path), preds)