Python人脸识别 Python3.7+OpenCV+Dlib+罗技C920摄像头 实现离线实时摄像头画面人脸检测+识别
示例效果 文末付完整工程链接
打码了
参考博客
基于python的dlib库的人脸识别 Legolas~
Dlib人脸检测的基本原理 普罗米修斯1024
本文中文乱码问题参考博客 天上飞下一毛雪
一、准备数据
-
需要事先下载的资料:
shape_predictor_68_face_landmarks.dat
dlib_face_recognition_resnet_model_v1.dat
SIMYOU.TTF
haarcascade_frontalface_default.xml -
准备模型的训练数据
新建一个文件夹取名:train_images,事先准备每个人的照片,并做好标签备注,图片名即标签。如下图所示:
-
准备模型的测试数据
新建另一个文件夹取名:test_images,放一些train_images里同一个人的不同的照片,去掉标签,如下图:
二、训练数据
# -*- coding:utf-8 -*-
import cv2
import dlib
import numpy as np
import os
import glob
import json
import time
'''
# 参数说明:
# train_image_path 为 上文 train_images 文件夹路径
# predictor_path 为 shape_predictor_68_face_landmarks.dat 文件所在路径
# recognition_path 为 dlib_face_recognition_resnet_model_v1.dat文件所在路径
# isNew 为 判断是训练全新的模型还是说我需要在原来的模型基础上新增数据 True 为训练全新模型
# old_model_path 为 旧模型文件的路径 txt文件
# new_model_path 为 训练完后新模型的保存路径 txt文件
# class_name_path 为 每个人脸图片的标签保存路径 txt文件
'''
def train_face_jpg(train_image_path, predictor_path, recognition_path, isNew, old_model_path, new_model_path, class_name_path):
global class_names # 人脸标签的保存列表
detector = dlib.get_frontal_face_detector() # 获取图片中的人脸(dlib在这块的性能比opencv差点,计算的时候可以在前后用两个time.time()相减看一下dlib人脸检测耗费的时间,模型训练部分暂时就先用这个,实时画面检测改用opencv去实现)
predictor = dlib.shape_predictor(predictor_path) # 获取人脸68个关键点
face_rec = dlib.face_recognition_model_v1(recognition_path) # 人脸识别
descriptors = [] # 保存训练后的人脸信息的列表,最终转化为数组形式保存到txt文件
# 判断是整个重新训练新模型还是在旧模型增加训练数据
if isNew: # 训练全新的模型
descriptors = []
class_names = []
else:
# 为旧模型增加新的训练数据,训练一次模型是非常耗时的,
# 当人脸数据比较少还好,如果数据很多的话,每次添加人脸都要从头重新训练的话费时费力,
# 所以我们可以在以前的模型基础上,更新模型数据
# 例如调用方法:train_face_jpg('new_train_images', 'shape_predictor_68_face_landmarks.dat', 'dlib_face_recognition_resnet_model_v1.dat', False, 'result.txt', 'result.txt', 'class_names.txt')
# 其中 new_train_images 为新添加的人脸图的文件夹路径,训练完后的数据保存到原来的result.txt和class_names.txt即可
if os.path.isfile(old_model_path): # 判断旧模型文件是否存在
descriptors = np.loadtxt(old_model_path, dtype=list) #
descriptors = descriptors.tolist()
if os.path.isfile(class_name_path): # 判断旧模型对应的标签文件是否存在
class_names = get_class_names(class_name_path)
# 读取train_images文件夹下的所有图片
for path in glob.glob(os.path.join(train_image_path, "*.jpg")):
# 可读取中文路径
image = cv2.imdecode(np.fromfile(path, dtype=np.uint8), -1)
detection_train = detector(image, 1)
# 计算每个图片的人脸数据
for i, det in enumerate(detection_train):
face_mark_train = predictor(image, det)
face_des_train = face_rec.compute_face_descriptor(image, face_mark_train)
# 将数据保存到列表中
descriptors.append(np.array(face_des_train))
# 将图片名作为人脸标签保存
temp_name = path.split('\\')[-1].split('.')[0]
class_names.append(temp_name)
print(class_names)
# 保存人脸标签到class_name_path
with open(class_name_path, 'w+') as f:
for i in class_names:
f.write(json.dumps(i, ensure_ascii=False))
if i == class_names[len(class_names)-1]:
break
f.write("\n")
# 将训练后的模型数据保存到txt
np.savetxt(new_model_path, descriptors, fmt="%s")
# 返回 人脸标签列表
return get_class_names(class_name_path)
# 调用
if __name__ == '__main__':
train_face_jpg('train_images', 'shape_predictor_68_face_landmarks.dat', 'dlib_face_recognition_resnet_model_v1.dat', True, 'result.txt', 'result.txt', 'class_names.txt')
三、单张图片测试训练后的模型
模型训练完后,可以在工程路径下看到result.txt和class_names.txt,前者为模型的训练数据,后者为对应的标签文件
现在我们任取test_images文件夹中的一张照片来和训练数据做对比
# coding=utf-8
import dlib
import cv2
import numpy as np
import time
from PIL import Image, ImageDraw, ImageFont
class_names = []
detector = 0
predictor = 0
face_rec = 0
face_des_array = 0
descriptors = []
face_casecade = 0
# 初始化模型
'''
# 参数说明:
# predictor_path 为 shape_predictor_68_face_landmarks.dat 文件所在路径
# recognition_path 为 dlib_face_recognition_resnet_model_v1.dat 文件所在路径
# descriptors_path 为 模型的训练数据 也就是 result.txt 文件的路径
# face_casecade_path 为 haarcascade_frontalface_default.xml 文件所在路径
'''
def face_recognition_init_from_txt(predictor_path, recognition_path, descriptors_path, face_casecade_path):
global detector, predictor, face_rec, descriptors, face_casecade
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(predictor_path)
face_rec = dlib.face_recognition_model_v1(recognition_path)
descriptors = np.loadtxt(descriptors_path)
face_casecade = cv2.CascadeClassifier(face_casecade_path)
# 获取标签名数组
'''
# 参数说明:
# class_name_path 为 标签文件的路径 即 class_names.txt 文件的路径
'''
def get_class_names(class_name_path):
label_list = []
if os.path.exists(class_name_path):
f = open(class_name_path, 'r', encoding='gb18030', errors='ignore').read()
label_list = f.replace('\"', '').split('\n')
#print(label_list)
return label_list
# 单张图片对比
# 根据路径识别人脸
'''
# 参数说明:
# file_path 为 需要测试的图片路径
# class_names 为 人脸标签的数组 可通过加载 get_class_names(class_name_path) 方法获取
'''
def face_recognition_from_path(file_path, class_names):
global detector, predictor, face_rec, face_des_array
dist = []
image = cv2.imread(file_path)
detection_test = detector(image, 1) # 获取测试图片中人脸信息
for i, det in enumerate(detection_test):
face_mark_test = predictor(image, det)
face_des_test = face_rec.compute_face_descriptor(image, face_mark_test)
face_des_array = np.array(face_des_test)
for v in descriptors:
distance = np.linalg.norm(v - face_des_array) # 测试图片的数据与模型中的人脸数据做对比,求范数
dist.append(distance)
# dist.append(np.sqrt(np.sum(np.square(face_des_array-v))))
print(class_names)
print(dist)
# np.argsort获取列表中最小的数的下标
test_result = np.argsort(dist)
print(test_result)
# 打印最小的的数,这个是和模型中所有训练数据对比后取一个最小数值
print(dist[test_result[0]])
# 数值越小,代表相似度越高,但是这个最小数值可能也很大,就是说测试的图片可能在模型中没有,即为录入,
# 所以加个0.5的阈值判断,最小的数值小于0.45,才算匹配到,否侧视为人脸未录入,这个最小数值根据实际情况取调整
if dist[test_result[0]] < 0.45:
if class_names[test_result[0]] in class_names:
print("识别结果:", class_names[test_result[0]])
else:
print('人脸信息未录入')
else:
print('人脸信息未录入')
# 调用
if __name__ == '__main__':
class_names = get_class_names('class_names.txt')
face_recognition_from_path('test_images/test0.jpg', class_names)
根据上图准备的测试数据 test0.jpg 为局座,对比后最小的数值为0.269,下标为2 ,在标签列表中,下表2的标签为局座
识别结果:
多试几个: test4.jpg 为 乌蝇哥
face_recognition_from_path('test_images/test4.jpg', class_names)
test1.jpg 为 断水流大师兄 ,我们的模型里面是没有训练大师兄的数据的,所以即使返回了下标1,但是最小数值0.4895是大于0.45的,所以判断为未录入。
face_recognition_from_path('test_images/test1.jpg', class_names)
那我们来更新一下训练数据 ,新建一个文件夹取名 new_train_images,然后将断水流大师兄的照片放进去 如下图:
然后只需再调用一下train_face_jpg() ,修改一下参数设置,参数说明如下:
# 其中 new_train_images 为新添加的人脸图的文件夹路径,训练完后的数据保存到原来的result.txt和class_names.txt即可
# False 为 不是全新的模型,而是更新旧模型
train_face_jpg('new_train_images', 'shape_predictor_68_face_landmarks.dat', 'dlib_face_recognition_resnet_model_v1.dat', False, 'result.txt', 'result.txt', 'class_names.txt')
再次识别:
四、实时获取摄像头画面中的人脸并识别
dlib在人脸检测方面相比opencv更耗时,但是在识别方面更加专业,所以此处采用opecv去获取摄像头画面中人脸位置信息,然后再用dlib去做对比识别
# 首先将上文根据图片路径测试的方法改为根据Mat来识别
# 根据Mat识别人脸
def face_recognition_from_Mat(face_Mat, class_names):
global detector, predictor, face_des_array
dist = []
name = ''
image = face_Mat
detection_test = detector(image, 1)
for i, det in enumerate(detection_test):
face_mark_test = predictor(image, det)
face_des_test = face_rec.compute_face_descriptor(image, face_mark_test)
face_des_array = np.array(face_des_test)
for v in descriptors:
distance = np.linalg.norm(v - face_des_array)
dist.append(distance)
# dist.append(np.sqrt(np.sum(np.square(face_des_array-v))))
test_result = np.argsort(dist)
if dist[test_result[0]] < 0.45:
if class_names[test_result[0]] in class_names:
name = class_names[test_result[0]]
print("识别结果是:", name)
else:
name = '未录入'
else:
name = '未录入'
print('人脸信息未录入')
return name
# 判断画面中是否存在人脸,存在再识别
def get_face(frame, gray, train_images_names):
global face_casecade # face_casecade 为上文初始化模型的时候的 face_casecade
face = face_casecade.detectMultiScale(gray, 1.1, 5) # 利用opencv检测画面中是否存在人脸
face_img = []
for x, y, w, h in face:
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
face_img = gray[y:y + h, x:w + x]
face_img = cv2.cvtColor(face_img, cv2.COLOR_GRAY2BGR)
name = face_recognition_from_Mat(img, train_images_names) # 人脸识别并返回识别到的人名
frame = change_cv2_draw(frame, name, (x, y - 50), 50, (255, 0, 0)) # 显示中文人名
return frame
#由于直接用opencv显示中文会乱码,所以先将图片格式转化为PIL库的格式,用PIL的方法写入中文,然后在转化为CV的格式
'''
# 参数说明:
# image 为 输入的图像
# strs 为 需要显示的中文
# local 为 中文显示的坐标位置
# sizes 为 文字大小
# colour 为 颜色 RBG 空间 opencv则为BGR
'''
def change_cv2_draw(image,strs,local,sizes,colour):
cv2img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
pilimg = Image.fromarray(cv2img)
draw = ImageDraw.Draw(pilimg)
font = ImageFont.truetype("SIMYOU.TTF",sizes, encoding="utf-8") #SIMYOU.TTF为字体文件
draw.text(local, strs, colour, font=font)
image = cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR)
return image
cap = cv2.VideoCapture(0) #
while True:
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = get_face(frame, gray, class_names)
cv2.imshow('frame', frame)
cv2.waitKey(10)
到此结束
dlib库的人脸识别其实还是挺耗时的,一秒钟,大概能运算3次左右吧,如果觉得性能不够,可以自行尝试有没有什么方式可以优化一下性能
五、完整代码
train_face_data.py
# -*- coding:utf-8 -*-
import dlib
import cv2
import numpy as np
import os
import glob
import json
import time
class_names = []
def get_class_names(class_name_path):
label_list = []
if os.path.exists(class_name_path):
f = open(class_name_path, 'r', encoding='gb18030', errors='ignore').read()
label_list = f.replace('\"', '').split('\n')
#print(label_list)
return label_list
def train_face_jpg(train_image_path, predictor_path, recognition_path, isNew, old_model_path, new_model_path, class_name_path):
global class_names
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(predictor_path)
face_rec = dlib.face_recognition_model_v1(recognition_path)
descriptors = []
# 判断是整个重新训练新模型还是在旧模型增加训练数据
if isNew:
descriptors = []
class_names = []
else:
if os.path.isfile(old_model_path):
descriptors = np.loadtxt(old_model_path, dtype=list)
descriptors = descriptors.tolist()
if os.path.isfile(class_name_path):
class_names = get_class_names(class_name_path)
for path in glob.glob(os.path.join(train_image_path, "*.jpg")):
image = cv2.imdecode(np.fromfile(path, dtype=np.uint8), -1)
detection_train = detector(image, 1)
for i, det in enumerate(detection_train):
face_mark_train = predictor(image, det)
face_des_train = face_rec.compute_face_descriptor(image, face_mark_train)
descriptors.append(np.array(face_des_train))
temp_name = path.split('\\')[-1].split('.')[0]
class_names.append(temp_name)
print(class_names)
with open(class_name_path, 'w+') as f:
for i in class_names:
f.write(json.dumps(i, ensure_ascii=False))
if i == class_names[len(class_names)-1]:
break
f.write("\n")
np.savetxt(new_model_path, descriptors, fmt="%s")
return get_class_names(class_name_path)
face_recog.py
# 根据现有训练数据进行人脸识别类
# coding=utf-8
import dlib
import cv2
import numpy as np
import time
from PIL import Image, ImageDraw, ImageFont
detector = 0
predictor = 0
face_rec = 0
face_des_array = 0
descriptors = []
face_casecade = 0
# 初始化模型
def face_recognition_init_from_txt(predictor_path, recognition_path, descriptors_path, face_casecade_path):
global detector, predictor, face_rec, descriptors, face_casecade
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(predictor_path)
face_rec = dlib.face_recognition_model_v1(recognition_path)
descriptors = np.loadtxt(descriptors_path)
face_casecade = cv2.CascadeClassifier(face_casecade_path)
#根据路径识别人脸
def face_recognition_from_path(file_path, class_names):
global detector, predictor, face_rec, face_des_array
dist = []
image = cv2.imread(file_path)
detection_test = detector(image, 1)
for i, det in enumerate(detection_test):
face_mark_test = predictor(image, det)
face_des_test = face_rec.compute_face_descriptor(image, face_mark_test)
face_des_array = np.array(face_des_test)
for v in descriptors:
distance = np.linalg.norm(v - face_des_array)
dist.append(distance)
# dist.append(np.sqrt(np.sum(np.square(face_des_array-v))))
print(class_names)
print(dist)
test_result = np.argsort(dist)
print(test_result[0])
print(dist[test_result[0]])
if dist[test_result[0]] < 0.45:
if class_names[test_result[0]] in class_names:
print("人脸识别的结果是:", class_names[test_result[0]])
else:
pass
else:
print('人脸信息未录入')
#根据Mat识别人脸
def face_recognition_from_Mat(face_Mat, class_names):
global detector, predictor, face_des_array
dist = []
name = ''
image = face_Mat
detection_test = detector(image, 1)
for i, det in enumerate(detection_test):
face_mark_test = predictor(image, det)
face_des_test = face_rec.compute_face_descriptor(image, face_mark_test)
face_des_array = np.array(face_des_test)
for v in descriptors:
distance = np.linalg.norm(v - face_des_array)
dist.append(distance)
# dist.append(np.sqrt(np.sum(np.square(face_des_array-v))))
test_result = np.argsort(dist)
if dist[test_result[0]] < 0.45:
if class_names[test_result[0]] in class_names:
name = class_names[test_result[0]]
print("识别结果是:", name)
else:
name = '未录入'
else:
name = '未录入'
print('人脸信息未录入')
return name
# 判断画面中是否存在人脸
def get_face(frame, gray, train_images_names):
global face_casecade
face = face_casecade.detectMultiScale(gray, 1.1, 5)
face_img = []
for x, y, w, h in face:
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
face_img = gray[y:y + h, x:w + x]
face_img = cv2.cvtColor(face_img, cv2.COLOR_GRAY2BGR)
for img in face_img:
name = face_recognition_from_Mat(img, train_images_names)
frame = change_cv2_draw(frame, name, (x, y - 50), 50, (255, 0, 0))
return frame
#由于直接用opencv显示中文会乱码,所以先将图片格式转化为PIL库的格式,用PIL的方法写入中文,然后在转化为CV的格式
def change_cv2_draw(image,strs,local,sizes,colour):
cv2img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
pilimg = Image.fromarray(cv2img)
draw = ImageDraw.Draw(pilimg)
font = ImageFont.truetype("SIMYOU.TTF",sizes, encoding="utf-8") #SIMYOU.TTF为字体文件
draw.text(local, strs, colour, font=font)
image = cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR)
return image
main.py
import face_recog as fr
import train_face_data as tfd
import json
import cv2
import time
# 在原有的模型基础上添加数据
# tfd.train_face_jpg('new_train_images', 'shape_predictor_68_face_landmarks.dat', 'dlib_face_recognition_resnet_model_v1.dat', False, 'result.txt', 'result.txt', 'class_names.txt')
# 训练全新的模型
# class_names = tfd.train_face_jpg('train_images', 'shape_predictor_68_face_landmarks.dat', 'dlib_face_recognition_resnet_model_v1.dat', True, 'result.txt', 'result.txt', 'class_names.txt')
#初始化模型
fr.face_recognition_init_from_txt('shape_predictor_68_face_landmarks.dat', 'dlib_face_recognition_resnet_model_v1.dat', 'result.txt', 'haarcascade_frontalface_default.xml')
class_names = tfd.get_class_names('class_names.txt')
# 单张图片测试
fr.face_recognition_from_path('test_images/test1.jpg', class_names)
# 实时摄像头画面识别
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame = fr.get_face(frame, gray, class_names)
cv2.imshow('frame', frame)
cv2.waitKey(10)