Skip to content

Commit d4292a8

Browse files
itamar8910ageitgey
authored andcommitted
Face recognition with knn (ageitgey#250)
* face recognition with knn * improved knn, added doc and tests * updated for flake8 * fixed requirements * fixed paths in test * fixed additional paths in test * updated tests * updated readme * Add recognition with k-nearest-neighbors classifier * Correcting for PR comments * remove scikit-learn from global requirements * remove scikit-learn from setup requirements * Add intro to knn example
1 parent df83b44 commit d4292a8

File tree

16 files changed

+154
-1
lines changed

16 files changed

+154
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ All the examples are available [here](https://github.com/ageitgey/face_recogniti
303303
* [Recognize faces in a video file and write out new video file (Requires OpenCV to be installed)](https://github.com/ageitgey/face_recognition/blob/master/examples/facerec_from_video_file.py)
304304
* [Recognize faces on a Raspberry Pi w/ camera](https://github.com/ageitgey/face_recognition/blob/master/examples/facerec_on_raspberry_pi.py)
305305
* [Run a web service to recognize faces via HTTP (Requires Flask to be installed)](https://github.com/ageitgey/face_recognition/blob/master/examples/web_service_example.py)
306-
306+
* [Recognize faces with a K-nearest neighbors classifier](https://github.com/ageitgey/face_recognition/blob/master/examples/face_recognition_knn.py)
307307
## How Face Recognition Works
308308

309309
If you want to learn how face location and recognition work instead of

examples/face_recognition_knn.py

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

examples/knn_examples/test/obama1.jpg

77.4 KB
Loading
546 KB
Loading
187 KB
Loading
345 KB
Loading
264 KB
Loading

0 commit comments

Comments
 (0)