Skip to content

Commit f2a687a

Browse files
committed
Merge branch 'master' into docker-continuous-integration
2 parents 0705136 + 49bee99 commit f2a687a

File tree

492 files changed

+1893
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

492 files changed

+1893
-0
lines changed

face-recognition/README.md

Lines changed: 20 additions & 0 deletions
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import argparse
2+
import pickle
3+
from collections import Counter
4+
from pathlib import Path
5+
6+
import face_recognition
7+
from PIL import Image, ImageDraw
8+
9+
DEFAULT_ENCODINGS_PATH = Path("output/encodings.pkl")
10+
BOUNDING_BOX_COLOR = "blue"
11+
TEXT_COLOR = "white"
12+
13+
# Create directories if they don't already exist
14+
Path("training").mkdir(exist_ok=True)
15+
Path("output").mkdir(exist_ok=True)
16+
Path("validation").mkdir(exist_ok=True)
17+
18+
parser = argparse.ArgumentParser(description="Recognize faces in an image")
19+
parser.add_argument("--train", action="store_true", help="Train on input data")
20+
parser.add_argument(
21+
"--validate", action="store_true", help="Validate trained model"
22+
)
23+
parser.add_argument(
24+
"--test", action="store_true", help="Test the model with an unknown image"
25+
)
26+
parser.add_argument(
27+
"-m",
28+
action="store",
29+
default="hog",
30+
choices=["hog", "cnn"],
31+
help="Which model to use for training: hog (CPU), cnn (GPU)",
32+
)
33+
parser.add_argument(
34+
"-f", action="store", help="Path to an image with an unknown face"
35+
)
36+
args = parser.parse_args()
37+
38+
39+
def encode_known_faces(
40+
model: str = "hog", encodings_location: Path = DEFAULT_ENCODINGS_PATH
41+
) -> None:
42+
"""
43+
Loads images in the training directory and builds a dictionary of their
44+
names and encodings.
45+
"""
46+
names = []
47+
encodings = []
48+
49+
for filepath in Path("training").glob("*/*"):
50+
name = filepath.parent.name
51+
image = face_recognition.load_image_file(filepath)
52+
53+
face_locations = face_recognition.face_locations(image, model=model)
54+
face_encodings = face_recognition.face_encodings(image, face_locations)
55+
56+
for encoding in face_encodings:
57+
names.append(name)
58+
encodings.append(encoding)
59+
60+
name_encodings = {"names": names, "encodings": encodings}
61+
with encodings_location.open(mode="wb") as f:
62+
pickle.dump(name_encodings, f)
63+
64+
65+
def recognize_faces(
66+
image_location: str,
67+
model: str = "hog",
68+
encodings_location: Path = DEFAULT_ENCODINGS_PATH,
69+
) -> None:
70+
"""
71+
Given an unknown image, get the locations and encodings of any faces and
72+
compares them against the known encodings to find potential matches.
73+
"""
74+
with encodings_location.open(mode="rb") as f:
75+
loaded_encodings = pickle.load(f)
76+
77+
input_image = face_recognition.load_image_file(image_location)
78+
79+
input_face_locations = face_recognition.face_locations(
80+
input_image, model=model
81+
)
82+
input_face_encodings = face_recognition.face_encodings(
83+
input_image, input_face_locations
84+
)
85+
86+
pillow_image = Image.fromarray(input_image)
87+
draw = ImageDraw.Draw(pillow_image)
88+
89+
for bounding_box, unknown_encoding in zip(
90+
input_face_locations, input_face_encodings
91+
):
92+
name = _recognize_face(unknown_encoding, loaded_encodings)
93+
if not name:
94+
name = "Unknown"
95+
_display_face(draw, bounding_box, name)
96+
97+
del draw
98+
pillow_image.show()
99+
100+
101+
def _recognize_face(unknown_encoding, loaded_encodings):
102+
"""
103+
Given an unknown encoding and all known encodings, find the known
104+
encoding with the most matches.
105+
"""
106+
boolean_matches = face_recognition.compare_faces(
107+
loaded_encodings["encodings"], unknown_encoding
108+
)
109+
votes = Counter(
110+
name
111+
for match, name in zip(boolean_matches, loaded_encodings["names"])
112+
if match
113+
)
114+
if votes:
115+
return votes.most_common(1)[0][0]
116+
117+
118+
def _display_face(draw, bounding_box, name):
119+
"""
120+
Draws bounding boxes around faces, a caption area, and text captions.
121+
"""
122+
top, right, bottom, left = bounding_box
123+
draw.rectangle(((left, top), (right, bottom)), outline=BOUNDING_BOX_COLOR)
124+
text_left, text_top, text_right, text_bottom = draw.textbbox(
125+
(left, bottom), name
126+
)
127+
draw.rectangle(
128+
((text_left, text_top), (text_right, text_bottom)),
129+
fill=BOUNDING_BOX_COLOR,
130+
outline=BOUNDING_BOX_COLOR,
131+
)
132+
draw.text(
133+
(text_left, text_top),
134+
name,
135+
fill=TEXT_COLOR,
136+
)
137+
138+
139+
def validate(model: str = "hog"):
140+
"""
141+
Runs recognize_faces on a set of images with known faces to validate
142+
known encodings.
143+
"""
144+
for filepath in Path("validation").rglob("*"):
145+
if filepath.is_file():
146+
recognize_faces(
147+
image_location=str(filepath.absolute()), model=model
148+
)
149+
150+
151+
if __name__ == "__main__":
152+
if args.train:
153+
encode_known_faces(model=args.m)
154+
if args.validate:
155+
validate(model=args.m)
156+
if args.test:
157+
recognize_faces(image_location=args.f, model=args.m)
105 KB
Binary file not shown.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dlib==19.24.0
2+
face-recognition==1.3.0
3+
numpy==1.24.2
4+
Pillow==9.4.0
8.16 KB
19.6 KB
11.4 KB
14.1 KB
11.3 KB

0 commit comments

Comments
 (0)