1.准备工作:
(1)编程环境:Anaconda–Jupyter Notebook
(2)建立一个文件夹(face_recognition),用于存放训练集图片和测试集图片。 为避免中文乱码问题,文件名统一采用英文及拼音命名。
训练集文件夹(trains)存放的第一个人
训练集文件夹(trains)存放的第二个人
存放一张测试集图片test.jpg
2.程序代码:
import os
import os.path
import cv2
import sklearn.neighbors
import pickle
from PIL import Image, ImageDraw, ImageFont
import face_recognition as fr
from face_recognition.face_detection_cli import image_files_in_folder
#################################
# 2-训练模型
#################################
#1.建立一个数据集x:128维度加上y一个维度,总共是129维度。
# 2.对每一个照片操作
# 3.决定n
# 4.训练出分类器
# 5.保存分类器
def train(train_dir, model_save_path="trained_knn_model.clf", n_neighbors=4, knn_algo="ball_tree"):
'''
功能:训练一个KNN分类器
:train_dir: 训练目录。其下对每个已知的人,分别以其名字,建立一个文件夹
:model_save_path:模型保存的位置
:n_neighbors:邻居数默认为4
:knn_algo: 支持KNN的数据结构;ball_tree是一种树型结构。
:return: KNN分类器
'''
#生成训练集
x = [] #注意x最终是18维
y = []
#遍历训练集中的每一个人
for class_dir in os.listdir(train_dir): #os.listdir()方法,用于返回指定的文件夹包含的文件或文件夹的名字的列表。这个列表以字母顺序。 它不包括 '.' 和'..' 即使它在文件夹中。
if not os.path.isdir(os.path.join(train_dir, class_dir)): #判断是否是目录
continue
# 遍历这个人的每一张照片
for img_path in image_files_in_folder(os.path.join(train_dir, class_dir)):
image = fr.load_image_file(img_path) #传入人脸的位置
boxes = fr.face_locations(image) #定出人脸位置
# #对于当前图片,增加编码到训练集合
x.append(fr.face_encodings(image, known_face_locations=boxes)[0])#编码返回时一个128维度的向量。
y.append(class_dir)
# 决定n
if n_neighbors is None:
n_neighbors = 3
# 训练分类器
knn_clf = sklearn.neighbors.KNeighborsClassifier(n_neighbors=n_neighbors)
knn_clf.fit(x, y)
# 保存分类器
if model_save_path is not None:
with open(model_save_path, "wb") as f: #wb,b二进制
pickle.dump(knn_clf, f) #模型存储
# 返回
return knn_clf
# 预测
def predict(x_img_path, knn_clf=None, model_path=None, distance_threshold=0.45):
'''
利用KNN分类器识别给定的照片中的人脸
:x_imag_path:必须对应照片的地址而不是照片的文件夹
:knn_clf:
:distance_threshold:
:return: [(人名1,边界盒子1),...]
'''
if knn_clf is None and model_path is None:
raise Exception("必须提供KNN分类器")#KNN分类器:可选方式为knn_clf或model_path'
# 加载训练好的KNN模型
# rb 读入二进制数据
if knn_clf is None:
with open(model_path, "rb") as f: #https://www.cnblogs.com/tianyiliang/p/8192703.html
knn_clf = pickle.load(f)
# 加载图片,发现人脸的位置
x_img = fr.load_image_file(x_img_path)
x_face_location = fr.face_locations(x_img)
# 对测试图片中的人脸进行编码
encodings = fr.face_encodings(x_img, known_face_locations=x_face_location) #http://www.360doc.com/content/18/0403/18/48868863_742603302.shtml
# 利用knn model找出测试人脸最匹配的人脸
# encodings : 128个人脸的特征构成
closest_distances = knn_clf.kneighbors(encodings, n_neighbors=1)
are_matches = [closest_distances[0][i][0] <= distance_threshold
for i in range(len(x_face_location))]
# 预测分类,并移除不在阀值内的分类
#pred 预测值,loc头像位置
return [(pred, loc) if rec else ("unknown", loc)
for pred, loc, rec in zip(knn_clf.predict(encodings),
x_face_location, are_matches)]
# 结果可视化
def show_names_on_image(img_path, predictions):
'''
人脸识别可视化
:param img_path: 待识别图片的位置
:param predictions: 预测的结果
:return:
'''
pil_image = Image.open(img_path).convert("RGB") #将图片转换成格式 #https://blog.csdn.net/icamera0/article/details/50843172
draw = ImageDraw.Draw(pil_image) #ImageDraw类支持各种几何图形的绘制和文本的绘制https://blog.csdn.net/guduruyu/article/details/71213717
for name, (top, right, bottom, left) in predictions:
# 用pillow快捷画出人脸边界盒子
draw.rectangle(((left, top), (right, bottom)), outline=(255,0,255))
#pillow 里可能生成UTF-8格式,所以这里做如下转换
#这里有draw不能解码出name字体的问题。
name = name.encode("UTF-8")
name = name.decode("ascii")
#在人脸下写下名字,作为标签
text_width, text_height = draw.textsize(name)
draw.rectangle(((left, bottom + text_height - 10), (right, bottom)),
fill=(255,0,255), outline=(255,0,255))
# Font4 = ImageFont.truetype("C:\Windows\Fonts\SHOWG.TTF",48)
# draw.text((left + 6, bottom - text_height - 5), name, fill=(255,0,255))
Font4 = ImageFont.truetype("C:\Windows\Fonts\Arial.ttf",20)
draw.text((left + 6, bottom - text_height - 5), name, font=Font4)
#遍加名字到li_names
li_names.append(name)
#从内存删除draw
del draw
#显示结果图
pil_image.show()
#读入测试集图片(具体到图片名)
predictions = predict('E://face_recognition/test.jpg', model_path="trained_knn_model.clf")
show_names_on_image('E://face_recognition/test.jpg', predictions)