本人普通四非本科,毕设选了这个比较大众且成熟的选题,四处借鉴后完成了论文,现在写写一些我完成毕设期间的历程。
在完成论文开题报告后我开始寻求代码以期完成一个简易人脸识别签到系统的设计,开始我用了舍友选修课人工智能的大作业,他所采用的是传统的haar算法,但发现其代码只能用于检测以及识别人脸,不能做到调用摄像头实时识别。于是我又去网上荡了几份代码下来跑(代码已放代码包里——基于haar算法的人脸识别)。
本人采用的是vscode来实现代码的运行,考虑到vscode的多样性,以后也许其他语言的汇编也可以通用,所以我没有下pycharm。直接在官网下最新的vscode以及搭配最新的anaconda环境,由于都是新版本,所以后续有些报错会与我查阅的文章解决方法不同或不适用了,这给我带来不小的麻烦,具体是哪些我隔了半年我也忘了哈哈哈哈哈,连历史记录都找不到了,只能照着记忆粗略描述,第一次写不够严谨,出现很多问题还请各位大佬多多指正。
初进vscode各种配置我就不一一介绍了,大体扩展处下个python的扩展包就可以了,然后在第一次运行.py文件时会要你选择编译环境,这个要选好,或者后续摁F1弹出搜索框搜Python:Select Interpreter选择也可。我的理解是相当于一个虚拟编译环境,你不同的项目所用到的模块导入下载后都在各自的环境里,并不通用,打比方你在这个项目刚导入了cv2模块,重开一个项目在另外的编译环境里又得重下,如图。我都选在了在anaconda下集成模块编译,因为同个环境换项目的时候模块导入也不用重新下载。
要是缺少某个模块报错,如图,根据报错信息到百度搜一下也基本都有解决方案,例如第3行的就是缺少了opencv,可以在base环境下使用pip install opencv-python,本人由于在anaconda的编译环境,所以都是使用conda install安装各种模块,包括下文用到dlib的模型,也是采用conda install -c conda-forge dlib或直接conda install dlib。
haar作为一款非常经典的算法而言,其优点是可以检测不同尺度的人脸,而且得益于其简单的架构,使其可以在CPU上近乎实时工作。haar算法主要是建立haar级联器,对输入的图像进行灰度化处理,然后调用函数接口来进行人脸识别。上文提到本文一开始的人脸识别是计划基于haar算法实现的,通过实验发现其对于完整的脸部检测效果不错,但其不适用于非正面图像,脸部受到轻微遮挡的情况下近乎起不到作用,同时识别率不高,经常给出许多错误预测,在近亲人脸识别中几乎识别不出两人的区别,经常混淆。如图.yml是是将训练对象的数据保存下来,然后照片则是点击拍照时截取并保存在本地文件夹的。
于是又一次去借鉴了网上各路大佬,物色到了一个基于dlib改进的人脸识别签到系统。dlib曾于2017年2月刷新了LFW人脸数据库的识别榜单,准确率高达0.9938 ± 0.0027。该模型是本系统所用的人脸编码方法,具有27 层的 ResNet网络。它的实质是ResNet-34网络,但却是在这基础上进行了简化,删除了一部分网络层并减少了一半使用的滤波器,使其可以更加高效快速的完成任务。代码如下:
# 主程序
import sys
import cv2
import os
import numpy as np
import uuid # 生成随机文件名
import dlib
from PIL import Image, ImageDraw, ImageFont
import datetime
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLineEdit, QLabel
a = 0
b = 0
def name(face_id): # 创建班级表
with open('info.csv', 'r+') as f:
# 使用 with as 操作已经打开的文件对象(本身就是上下文管理器),无论期间是否抛出异常,都能保证 with as 语句执行完毕后自动关闭已经打开的文件。
# r+是open的参数,代表可读可写,若文件不存在就报错
myInfo = f.readlines()
namelist = []
for line in myInfo:
enty = line.split(',')
# 数据中遇到','就隔开
namelist.append(enty[0])
if face_id not in namelist:
state = "已录入"
f.writelines(f'\n{face_id},{state}')
# 如果是新的面孔id,即不在现有面容列表里的(namelist)则显示已录入
def getMax_faces(face_rects):
if len(face_rects) == 0:
return 0, 0
face_areas = []
for rect in face_rects:
# 计算每个人脸的面积(高x宽)只录入最大的那个人脸
area = (rect.bottom() - rect.top()) * (rect.right() - rect.left())
face_areas.append(area)
index = np.argmax(face_areas)
return face_areas[index], face_rects[index]
def gen_face_name(str_face_id):
# 生成图片文件
return str_face_id + '_' + str(uuid.uuid4()) + '.jpg'
def ChineseText(img, text, position, textColor=(255, 0, 0), textsize=30):
if (isinstance(img, np.ndarray)):
# isinstance(object, classinfo)如果参数object是classinfo的实例,或者object是classinfo类的子类的一个实例, 返回True。如果object不是一个给定类型的的对象, 则返回结果总是False。所以此句判断是否OpenCV图片类型
# np.array 只是一个便捷的函数,用来创建一个ndarray,它本身不是一个类。ndarray数组,是用 np.ndarray类的对象 表示n维数组对象,所以ndarray是一个类对象,而array是一个方法。
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img)
# 创建一个可以在给定图像上绘图的对象
fontSytle = ImageFont.truetype("simsun.ttc", textsize, encoding='utf-8')
# 字体的格式
draw.text(position, text, textColor, font=fontSytle)
# 绘制文本
return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
# 转换回OpenCV格式
# 创建已打卡表
def saveInfo(face_id): # 创建已打卡表
with open('sign.csv', 'r+') as f:
myInfo = f.readlines()
namelist = []
for line in myInfo:
enty = line.split(',')
namelist.append(enty[0])
if face_id not in namelist:
now = datetime.datetime.now() # 获取当前时间
dtString = now.strftime("%H:%M:%S") # 设置时间格式hour,minutes,second
state = "已签到"
f.writelines(f'\n{face_id},{dtString},{state}')
def absenteeism(): # 建立缺勤名单
with open('sign.csv', 'r+') as f:
myInfo = f.readlines()
namelist = []
for line in myInfo:
enty = line.split(',')
namelist.append(enty[0])
with open('info.csv', 'r+') as f:
myInfo = f.readlines()
name = []
for line in myInfo:
en = line.split(',')
name.append(en[0])
ab = list(set(name).difference(set(namelist)))
# set() 函数创建一个无序不重复元素集
# difference():用于查找两个集合之间的差,该方法以该集合(set1)调用,另一个集合(set2)作为参数传递,并且它返回set2中不存在的元素集。注意:difference()可以获得差集,但是这个差集是“出现在第一个集合但不出现在第二个集合”的元素,也就是说如果第二个集合包含第一个,结果就是空。set_name1.difference(set_name2)
print(ab)
for i in ab:
with open('absenteeism.csv', 'r+') as f:
myInf = f.readlines()
namelis = []
for line in myInf:
ent = line.split(',')
namelis.append(ent[0])
if i not in namelis:
state = "未签到"
f.writelines(f'\n{i},{state}')
print(i)
def load_model(file_scp): # 加载训练好的模型
with open(file_scp, 'r', encoding='utf-8') as f:
lines = f.read().splitlines() # 拆分
face_ids = [line.split()[0] for line in lines] # 该列表存放人脸id
face_models = [np.load(line.split()[-1]) for line in lines] # 该列表存放人脸模型
return face_ids, face_models
def face_recognize(face_vec, face_models, face_ids): # 计算欧氏距离,越相似数值越小
scores = []
for model in face_models:
N = model.shape[0]
# shape[0]读取矩阵第一维度的长度,而对于图像来说等于图片高
diffMat = np.tile(face_vec, (N, 1))-model
# 计算欧式距离
# np.tile(A,reps)函数可对输入的数组,元组或列表进行重复构造,其输出是数组,A:输入的数组,元组或列表;reps:重复构造的形状,可为数组,元组或列表,reps:(m,n),其中m控制纵向重复,n控制横向重复
sqDiffMat = diffMat ** 2
# **:将数组中每个元素进行平方;而matrix(a)**2则是矩阵乘积,而不是数组中的元素乘积。
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
# 找到最小距离
score = np.min(distances)
scores.append(score)
index = np.argmin(scores)
return face_ids[index], scores[index] # 返回id和距离
# 点击信息录入
def do_info():
info_btn.hide() # 隐藏信息录入
sign_btn.hide() # 隐藏开始签到
oversign_btn.hide() # 隐藏结束签到
camera_btn.show() # 显示开始拍照
change_btn.show() # 显示换人录入
ovecam_btn.show() # 显示结束录入
user_edit.show()
usr_label.show()
global a
# 定义全局变量
faceid = user_edit.text() # 获取文本框内容
if __name__ == "__main__":
os.makedirs('faces', exist_ok='True') # 创建faces文件夹用于保存人脸数据
det_face = dlib.get_frontal_face_detector() # 创建人脸检测器
cap = cv2.VideoCapture(0) # 打开摄像头
str_face_id = "" # 创建空的人脸id字符串
while True:
if str_face_id.strip() == "": # 判断id是否为空,为空创建face-id
str_face_id = faceid # 循环开始时输入id,并在faces目录下创建一个人脸id的目录
print(a)
print(str_face_id)
path_face = os.path.join('faces', str_face_id)
os.makedirs(path_face, exist_ok='True')
success, img = cap.read() # 读取每一帧的图像
# sucess是布尔型,读取帧正确返回True;img是每一帧的图像(BGR存储格式)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度
face_rects = det_face(gray, 0) # 检测人脸区域
max_area, max_rect = getMax_faces(face_rects) # 得到最大的人脸
k = cv2.waitKey(1) # 获取按键值
if a == 1: # 进行人脸采集
a = 0
if max_area > 10: # 保存最大人脸
roi = img[max_rect.top(): max_rect.bottom(),
max_rect.left(): max_rect.right()]
# 生成文件名
save_face_name = os.path.join(
'faces', str_face_id, gen_face_name(str_face_id)) # .encode('utf8')
# cv2.imwrite(save_face_name, roi) # 保存文件
cv2.imencode('.jpg', roi)[1].tofile(save_face_name) # 保存
print('save_face', save_face_name)
elif a == 2: # 切换人脸id
a = 0
str_face_id = ""
faceid = user_edit.text()
elif a == 3: # 退出
a = 0
break
if max_area > 10: # 画框
cv2.rectangle(img, (max_rect.left(), max_rect.top()), (max_rect.right(), max_rect.bottom()),
(0, 0, 255),
2) # x y x+w y+h
cv2.imshow("FACE", img)
cap.release()
cv2.destroyAllWindows() # 关闭摄像头
# 加载人脸特征提取器
facerec = dlib.face_recognition_model_v1(
"dlib_face_recognition_resnet_model_v1.dat")
# 加载人脸标志点检测器
sp = dlib.shape_predictor("shape_predictor_68_face_landmarks_GTX.dat")
# 记录所有模型信息
with open('model.scp', 'w', encoding='utf-8') as f: # 记录人脸id与人脸模型
base_path = 'faces' # 遍历faces文件夹
for face_id in os.listdir(base_path):
# os.listdir()用于返回一个由文件名和目录名组成的列表,需要注意的是它接收的参数需要是一个绝对的路径。
face_dir = os.path.join(base_path, face_id)
if os.path.isdir(face_dir):
# os.path.isdir()用于判断对象是否为一个目录。
file_face_model = os.path.join(
face_dir, face_id + '.npy') # 遍历base_path/face_id文件夹
face_vectors = [] # 人脸特征list
for face_img in os.listdir(face_dir): # 遍历,查找所有文件
if os.path.splitext(face_img)[-1] == '.jpg': # 寻找所有.jpg文件
# splitext分离文件名和扩展名
# img = cv2.imread(os.path.join(face_dir, face_img)) # 读取图片并转换格式
img = cv2.imdecode(np.fromfile(os.path.join(
face_dir, face_img), dtype=np.uint8), -1)
# cv2.imdecode()函数从指定的内存缓存中读取数据,并把数据转换(解码)成图像格式;主要用于从网络传输数据中恢复出图像。cv2.imencode()函数是将图片格式转换(编码)成流数据,赋值到内存缓存中;主要用于图像数据格式的压缩,方便网络传输。
# fromfile和tofile既可以读写二进制文件,也可以读写文本文件,是非常灵活的文件读取函数。他们之间互为对偶函数(对于任意一个逻辑函数,若把式中的运算符“.”换成“+”,“+”换成“.”;常量“0”换成“1”,“1”换成“0”,所得的新函数式为原函数式F的对偶式F′,也称对偶函数.对偶规则--如果两个函数式相等,则它们对应的对偶式也相等.)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = np.array(img) # 存放的是已经截取好的人脸图片,所以在图内检测标志点
h, w, _ = np.shape(img)
rect = dlib.rectangle(0, 0, w, h) # 整个区域
shape = sp(img, rect) # 辅助人脸定位,获取关键位
print("Generate face vector of", face_img)
face_vector = facerec.compute_face_descriptor(
img, shape) # 获取128维人脸特征
face_vectors.append(face_vector) # 保存图像和人脸id
name(face_id)
if len(face_vectors) > 0: # 人脸模型保存,并写入model.scp文件
np.save(file_face_model, face_vectors)
f.write('%s %s\n' % (face_id, file_face_model))
# 点击开始拍照
def do_camera():
info_btn.hide() # 隐藏信息录入
sign_btn.hide() # 隐藏开始签到
oversign_btn.hide() # 隐藏结束签到
camera_btn.show() # 显示开始拍照
change_btn.show() # 显示换人录入
ovecam_btn.show() # 显示结束录入
user_edit.show()
usr_label.show()
global a
a = 1
print(a)
# 点击切换名字录入
def do_change():
info_btn.hide() # 隐藏信息录入
sign_btn.hide() # 隐藏开始签到
oversign_btn.hide() # 隐藏结束签到
camera_btn.show() # 显示开始拍照
change_btn.show() # 显示换人录入
ovecam_btn.show() # 显示结束录入
user_edit.show()
usr_label.show()
global a
a = 2
# 点击结束录入
def do_ovecam():
info_btn.show() # 显示信息录入
sign_btn.show() # 显示开始签到
oversign_btn.hide() # 隐藏结束签到
camera_btn.hide() # 隐藏开始拍照
change_btn.hide() # 隐藏换人录入
ovecam_btn.hide() # 隐藏结束录入
user_edit.show()
usr_label.show()
global a
a = 3
# 点击开始签到
def do_sign():
info_btn.hide()
sign_btn.hide()
oversign_btn.show() # 显示结束签到
camera_btn.hide() # 隐藏开始拍照
change_btn.hide() # 隐藏换人录入
ovecam_btn.hide()
user_edit.hide()
usr_label.hide()
global b
if __name__ == "__main__":
face_ids, face_models = load_model("model.scp") # 加载训练好的人脸模型
print(face_ids)
detector = dlib.get_frontal_face_detector() # dlib人脸检测器
sp = dlib.shape_predictor(
"shape_predictor_68_face_landmarks_GTX.dat") # 人脸标志检测器
# 人脸特征提取器
facerec = dlib.face_recognition_model_v1(
"dlib_face_recognition_resnet_model_v1.dat")
cap = cv2.VideoCapture(0) # 打开摄像头
while True:
success, img = cap.read() # 读取每一帧的图像
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转换格式
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度
face_rects = detector(gray, 0) # 检测人脸
for k, rect in enumerate(face_rects): # 遍历检测的人脸
cv2.rectangle(img, (rect.left(), rect.top()),
(rect.right(), rect.bottom()), (0, 0, 255), 2)
shape = sp(img_rgb, rect) # 标志点检测
face_vector = facerec.compute_face_descriptor(
img_rgb, shape) # 获取人脸特征
# 计算人脸特征和人脸模型的距离
face_id, score = face_recognize(
np.array(face_vector), face_models, face_ids) # 进行识别,返回id和距离
if score < 0.40: # 设定阈值来判定是否为同一人
str_face = face_id
str_confidence = " %.2f" % (score)
saveInfo(face_id)
st = "签到成功"
else:
str_face = "unknow"
str_confidence = " %.2f" % (score)
st = "未知"
# 检查结果画图显示
img = ChineseText(img, str_face + str_confidence +
st, (rect.left(), rect.top() - 30))
# cv2.putText(img, str_face + str_confidence, (rect.left(), rect.top() - 5), cv2.FONT_HERSHEY_SIMPLEX, 1,
# (0, 0, 255), 2)
cv2.imshow("FACE", img) # 显示窗口
key = cv2.waitKey(1)
if b == 1:
b = 0
break
absenteeism()
cap.release()
cv2.destroyAllWindows() # 关闭摄像头
# 点击结束签到
def do_oversign():
info_btn.show() # 显示信息录入
sign_btn.show() # 显示开始签到
oversign_btn.hide() # 隐藏结束签到
camera_btn.hide() # 隐藏开始拍照
change_btn.hide() # 隐藏换人录入
ovecam_btn.hide() # 隐藏结束录入
user_edit.show()
usr_label.show()
global b
b = 1
if __name__ == "__main__":
app = QApplication(sys.argv) # 加载应用程序
my_win = QWidget() # 创建一个窗口
my_win.setWindowTitle("签到系统") # 窗口的名字
my_win.resize(300, 300) # 窗口大小
my_win.move(382, 238) # 窗口位置
info_btn = QPushButton("信息录入", my_win) # 创建第二个按钮,放在my_win里面
info_btn.setGeometry(100, 80, 80, 40) # 设置按钮的位置及大小,X Y W H
info_btn.clicked.connect(do_info) # 设置点击按钮的相应事件
# train_btn.hide(); # 隐藏该按钮
camera_btn = QPushButton("开始拍照", my_win) # 创建按钮,放在my_win里面
camera_btn.setGeometry(100, 140, 80, 40) # 设置按钮的位置及大小,X Y W H
camera_btn.clicked.connect(do_camera) # 设置点击按钮的相应事件
camera_btn.hide() # 隐藏该按钮
change_btn = QPushButton("换人录入", my_win) # 创建按钮,放在my_win里面
change_btn.setGeometry(100, 80, 80, 40) # 设置按钮的位置及大小,X Y W H
change_btn.clicked.connect(do_change) # 设置点击按钮的相应事件
change_btn.hide() # 隐藏该按钮
ovecam_btn = QPushButton("结束录入", my_win) # 创建按钮,放在my_win里面
ovecam_btn.setGeometry(100, 200, 80, 40) # 设置按钮的位置及大小,X Y W H
ovecam_btn.clicked.connect(do_ovecam) # 设置点击按钮的相应事件
ovecam_btn.hide() # 隐藏该按钮
sign_btn = QPushButton("开始签到", my_win) # 创建第三个按钮,放在my_win里面
sign_btn.setGeometry(100, 140, 80, 40) # 设置按钮的位置及大小,X Y W H
sign_btn.clicked.connect(do_sign) # 设置点击按钮的相应事件
oversign_btn = QPushButton("结束签到", my_win) # 创建第三个按钮,放在my_win里面
oversign_btn.setGeometry(100, 140, 80, 40) # 设置按钮的位置及大小,X Y W H
oversign_btn.clicked.connect(do_oversign) # 设置点击按钮的相应事件
oversign_btn.hide()
usr_label = QLabel("姓名", my_win) # 创建文本,放在my_win里面
usr_label.setGeometry(55, 15, 100, 50) # 设置文本的位置及大小,X Y W H
# usr_label.hide(); # 隐藏该按钮
user_edit = QLineEdit(my_win) # 创建文本输入框,放在my_win里面
user_edit.setPlaceholderText("请输入姓名")
user_edit.setGeometry(90, 20, 100, 40) # 设置文本框的位置及大小,X Y W H
# user_edit.hide(); # 隐藏该按钮
my_win.show() # 显示窗口
sys.exit(app.exec_()) # 不退出系统,保持窗口显示
此次利用pythonqt5做了简易的签到界面,对比之前的方案此次输入信息还可以输入中文。通过调用dlib里的人脸特征提取器"dlib_face_recognition_resnet_model_v1.dat"和人脸标志点检测器"shape_predictor_68_face_landmarks_GTX.dat"截取人脸特征信息保存本地,相比上个方案,该系统截取时还会去除背景无用信息,只保留人脸关键点信息,如图。
针对dlib的高识别率,本人也借鉴了波代码进行了粗略的验证,在BIT官网上下载了CASIA-FaceV5的数据集,它是由中国科学院的自动化研究所(CASIA)所收集的,都是亚洲的人脸,代码如下。我没能找到能自动迭代进行两两比较的代码,自己琢磨了也没写出来,只能手动更换要比对的两张照片一次次去跑害。
# coding:utf-8
'''
本本次封装,我主要是做两张人脸对比。
就只人脸识别部分,简单应用。
# 调用注意事项,因为模型底层是外国人写的。所以路径图片名字千万别使用中文,这样它直接找不到
好像是OpenCV的问题吧,一直没有解决。中文他会乱码。真的坑。
'''
from PIL import Image
import itertools
import dlib
import cv2
import glob
import numpy as np
import os
class face_recognition:
'''
模型路径
predictor_path = "./face_model/shape_predictor_68_face_landmarks.dat"
face_rec_model_path = "./face_model/dlib_face_recognition_resnet_model_v1.dat"
# 调用注意事项,因为模型底层是外国人写的。所以路径图片名字千万别使用中文,这样它直接找不到
好像是OpenCV的问题吧,一直没有解决。中文他会乱码。真的坑。
'''
def __init__(self, predictor_path, face_rec_model_path):
self.predictor_path = predictor_path
self.face_rec_model_path = face_rec_model_path
self.detector = dlib.get_frontal_face_detector()
self.shape_predictor = dlib.shape_predictor(self.predictor_path)
self.face_rec_model = dlib.face_recognition_model_v1(
self.face_rec_model_path)
def face_detection(self, url_img_1, url_img_2):
img_path_list = [url_img_1, url_img_2]
dist = []
for img_path in img_path_list:
img = cv2.imread(img_path)
# 转换rgb顺序的颜色。
b, g, r = cv2.split(img)
img2 = cv2.merge([r, g, b])
# 检测人脸
faces = self.detector(img, 1)
if len(faces):
for index, face in enumerate(faces):
# # 提取68个特征点
shape = self.shape_predictor(img2, face)
# 计算人脸的128维的向量
face_descriptor = self.face_rec_model.compute_face_descriptor(
img2, shape)
dist.append(list(face_descriptor))
else:
pass
return dist
# 欧式距离
def dist_o(self, dist_1, dist_2):
dis = np.sqrt(sum((np.array(dist_1)-np.array(dist_2))**2))
return dis
def score(self, url_img_1, url_img_2):
url_img_1 = glob.glob(url_img_1)[0]
url_img_2 = glob.glob(url_img_2)[0]
data = self.face_detection(url_img_1, url_img_2)
goal = self.dist_o(data[0], data[1])
# 判断结果,如果goal小于0.6的话是同一个人,否则不是。我所用的是欧式距离判别
# return 1-goal
return goal
# 调用 模型下载地址:http://dlib.net/files/
predictor_path = "shape_predictor_68_face_landmarks_GTX.dat"
face_rec_model_path = "dlib_face_recognition_resnet_model_v1.dat"
face_ = face_recognition(predictor_path, face_rec_model_path)
img_1 = './test/052/052_2.bmp'
img_2 = './test/052/052_1.bmp'
goal = face_.score(img_1, img_2)
print(goal)
# # i=j=k=0
# # while i<1:
# # while j<10:
# # while k<10:
# base_path = 'test' # 遍历test文件夹
# for face_id in os.listdir(base_path):
# # os.listdir()用于返回一个由文件名和目录名组成的列表,需要注意的是它接收的参数需要是一个绝对的路径。
# face_dir = os.path.join(base_path, face_id)
# for face_img in os.listdir(face_dir): # 遍历,查找所有文件
# if os.path.splitext(face_img)[-1] == '.bmp': # 寻找所有.bmp文件
# # splitext分离文件名和扩展名
# # img = cv2.imread(os.path.join(face_dir, face_img)) # 读取图片并转换格式
# img = cv2.imdecode(np.fromfile(os.path.join(
# face_dir, face_img), dtype=np.uint8), -1)
# # cv2.imdecode()函数从指定的内存缓存中读取数据,并把数据转换(解码)成图像格式;主要用于从网络传输数据中恢复出图像。cv2.imencode()函数是将图片格式转换(编码)成流数据,赋值到内存缓存中;主要用于图像数据格式的压缩,方便网络传输。
# # fromfile和tofile既可以读写二进制文件,也可以读写文本文件,是非常灵活的文件读取函数。他们之间互为对偶函数(对于任意一个逻辑函数,若把式中的运算符“.”换成“+”,“+”换成“.”;常量“0”换成“1”,“1”换成“0”,所得的新函数式为原函数式F的对偶式F′,也称对偶函数.对偶规则--如果两个函数式相等,则它们对应的对偶式也相等.)
# # 定义源文件夹的路径
# source_dir = "C:/Dd/vscode/end campus/borrow code/FaceProject/test"
# # 遍历源文件夹及其子文件夹,找出所有的子文件夹
# for root, dirs, files in os.walk(source_dir):
# # 如果有子文件夹,进入子文件夹
# if dirs:
# # 遍历所有的子文件夹
# for dir in dirs:
# # 获取子文件夹的绝对路径
# dir_path = os.path.join(root, dir)
# # 获取子文件夹下的所有图片文件
# image_files = [file for file in os.listdir(
# dir_path) if file.lower().endswith((".jpg", ".png", ".gif","bmp"))]
# # 如果有图片文件,生成所有可能的两两排列组合
# if image_files:
# image_pairs = itertools.combinations(image_files, 2)
# # 遍历所有的排列组合
# for pair in image_pairs:
# # 定义两个空列表,用于存储两张图片的内容和属性
# image1 = []
# image2 = []
# # 遍历每一对图片
# for i, image in enumerate(pair):
# # 获取图片的绝对路径
# image_path = os.path.join(dir_path, image)
# # 用PIL模块打开图片文件,获取其内容和属性
# image_obj = Image.open(image_path)
# width, height = image_obj.size
# format, mode = image_obj.format, image_obj.mode
# # 如果是第一张图片,将图片对象和相关信息添加到第一个列表中
# if i == 0:
# image1.append(
# (image_obj, width, height, format, mode))
# # 如果是第二张图片,将图片对象和相关信息添加到第二个列表中
# else:
# image2.append(
# (image_obj, width, height, format, mode))
# # 将两个列表分别赋值给两个变量
# variable1 = image1[0]
# variable2 = image2[0]
# goal= face_.score(variable1,variable2)
最终代码输出的是欧氏距离,结合文献以及自身的一些条件, 本人设置的阈值是0.4。最终在实验结果中,在实验的500张图片中,出现了3个人脸检测不到的情况,识别准确率为97%,而且都是在侧脸状态下,这说明侧脸对提取人脸特征的影响还是比较大。但实际情况的准确率应该会高于此值,一个是现实生活中签到人脸侧脸的幅度并不会很大,一般都会正对摄像头;另一方面本实验设置一个身份只要有一张人脸与其他四张任意一张被判定为不同的脸就会认为错误,有些过于严格。其余情况下基于dlib改进的人脸识别基本都能识别出人脸,识别准确率来到了100%,由此可见dlib的识别能力非常强悍。
然后是我自己系统的实验,也是发现在光照充足的情况下,经过dlib改进的人脸识别算法十分强悍,速度快、准确率高。但是当光照不足时,越是阴暗的教室,摄像头进光量不够,导致像素点模糊,不能很好的提取到人脸特征,极大影响识别速度甚至准确率。
其实本毕设还有很多不足,出于时间和精力和技术实在小白,简单提供些小小的展望:在针对光照不足导致的识别率和速度降低的情况下,可以考虑引进双分支人脸检测器(DSFD)或LFFD等算法提升检测率和鲁棒性。
为了进一步提高识别的精度与速度,本文还考虑了利用GPU来提高识别的效率。但是可能受限于电脑配置原因,利用GPU版本进行人脸识别会产生极大的延迟,往往摄像头前人脸已经移动了,画面隔了2、3秒才开始卡顿移动,在捣鼓无果后,本文暂且搁置此版本,留待以后深究。
本系统的签到界面还比较简陋,功能缺乏,未来时间充足可以重新打造UI,并完善注册登录功能。还可以考虑将签到系统的实时性进行优化,以满足实际应用中对快速签到的需求。
设计的人脸识别签到系统虽然满足日常课堂使用,但可以将其打包成一个单独的APP,未来可以将此系统此技术推广应用到更多的场景中去,例如企业考勤、社区管理等。
现实签到可能还会存在着诸多问题,本人认为还可以引入在线活体检测来避免个别学生利用照片或者动态视频来假签到。
最后,因为是小白,本篇文章写的我总感觉像是叙事文,写了我做了什么事情,而不是偏技术向,可能让大佬看笑话了,还是慢慢成长吧。
ps:主代码借鉴了Your Mei老哥的,验证代码太久忘了(失礼了哈哈哈哈哈)