FaceID技术明细
项目源代码请访问我的GayHub仓库
技术选型
- PyQt5 + Python3.7 + OpenCV
- 依赖: PyQt5 PyQt5-tools Pillow numpy opencv-python opencv-contrib-python matplotlib
- 开发环境: Windows11 + PyCharm
实现人员注册,信息修改,人脸识别获取相关信息
技术方案
页面绘制
采用PyQt5完成
class Ui_Menu(QWidget): def __init__(self): super(Ui_Menu, self).__init__() # 创建label并设置文本内容 self.label = QLabel('欢迎使用Face-ID识别系统', self) # 创建普通用户和管理员按键 self.btn_ordinary = QPushButton('身份辨认', self) self.btn_admin = QPushButton('人脸注册', self) self.btn_edit = QPushButton('信息编辑', self) file = open("./datafile.txt", 'ab+') file.close() if os.path.getsize("./datafile.txt"): self.datafile = open("./datafile.txt", 'rb') global data data = pickle.load(self.datafile) self.datafile.close() print(data) # 初始化界面 self.init_ui() def init_ui(self): # 设置窗口大小 self.resize(1280, 800) # 设置label框的位置 self.label.move(140, 200) # 设置按键框的位置和大小 self.btn_ordinary.setGeometry(550, 420, 181, 61) self.btn_admin.setGeometry(550, 510, 181, 61) self.btn_edit.setGeometry(550, 600, 181, 61) # 设置label样式(字体、大小、颜色等) self.label.setStyleSheet( "QLabel{color:rgb(0,0,0,255);" # 字体颜色为黑色 "font-size:82px;font-weight:bold;" # 大小为70 加粗 "font-family:Roman times;}") # Roman times字体 self.btn_ordinary.setStyleSheet( "QPushButton{color:rgb(0,0,0,255);" # 字体颜色为黑色 "font-size:30px;" # 大小为30 "font-family:Roman times;}") # Roman times字体 self.btn_admin.setStyleSheet( "QPushButton{color:rgb(0,0,0,255);" # 字体颜色为黑色 "font-size:30px;" # 大小为30 "font-family:Roman times;}") # Roman times字体 self.btn_edit.setStyleSheet( "QPushButton{color:rgb(0,0,0,255);" # 字体颜色为黑色 "font-size:30px;" # 大小为30 "font-family:Roman times;}") # Roman times字体 # 点击管理员按钮事件 self.btn_admin.clicked.connect(self.slot_btn_admin) # 点击普通用户按钮事件 self.btn_ordinary.clicked.connect(self.slot_btn_ordinary) self.btn_edit.clicked.connect(self.slot_btn_edit) # 点点击管理员按钮事件 def slot_btn_admin(self): self.manager_face = Ui_manager_face() self.manager_face.show() self.hide() # 点击普通用户按钮事件 def slot_btn_ordinary(self): self.face_reco = Ui_face_reco() self.face_reco.show() self.hide() def slot_btn_edit(self): self.edit = Ui_edit() self.edit.show() self.hide()
人脸认证
信息采集, 通过调用摄像头持续采集60张人脸照片, 利用cv2进行处理, 使用FaceCascade 检测图像中的多个人脸, 保存到Face_data文件夹下.
初始化识别器
recognizer = cv2.face.LBPHFaceRecognizer_create()
此处利用的训练集是OpenCV内置的haarcascade_frontalface_default.xml
具体代码如下:
def thread_pic(self): print("线程出没!!!") print(self.Edit_ID.text()) # 创建目录,将获取的人脸照片放入指定的文件夹 self.file = "./Face_data/" while (True): ret, self.img = self.cap.read() # 垂直翻转视频图像 # 灰度化处理 gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY) faces = faceCascade2.detectMultiScale(gray, 1.3, 5) # 判断是否存在文件夹如果不存在则创建为文件夹 self.folder = os.path.exists(self.file) if not self.folder: # makedirs 满权限创建文件时如果路径不存在会创建这个路径 os.makedirs(self.file) os.chmod(self.file, 0o777) for (x, y, w, h) in faces: cv2.rectangle(self.img, (x, y), (x + w, y + h), (255, 0, 0), 2) self.count += 1 # 将捕获的图像保存到指定的文件夹中 bool = cv2.imwrite(self.file + "/User." + str(self.Edit_ID.text()) + '.' + str(self.count) + ".png", gray[y:y + h, x:x + w]) # 取60张人脸样本,停止录像 if self.count >= 60: print("OK!") break self.recognizer = cv2.face.LBPHFaceRecognizer_create() # 函数获取图像和标签数据 def getImagesAndLabels(path): imagePaths = [os.path.join(path, f) for f in os.listdir(path)] faceSamples = [] ids = [] self.step = 65 # self.progressBar.setProperty("value", self.step) for imagePath in imagePaths: # 转换为灰度 PIL_img = Image.open(imagePath).convert('L') img_numpy = np.array(PIL_img, 'uint8') id = int(os.path.split(imagePath)[-1].split(".")[1]) faces = faceCascade3.detectMultiScale(img_numpy) for (x, y, w, h) in faces: faceSamples.append(img_numpy[y:y + h, x:x + w]) ids.append(id) return faceSamples, ids self.step = 75 # self.progressBar.setProperty("value", self.step) print("\n [INFO] Training faces. It will take a few seconds. Wait ...") # 调用函数,传递文件夹路径参数 faces, ids = getImagesAndLabels(self.file) self.recognizer.train(faces, np.array(ids)) self.step = 85 # self.progressBar.setProperty("value", self.step) # 创建文件夹 self.triningfile = "./Face_training/" self.folder1 = os.path.exists(self.triningfile) if not self.folder1: os.makedirs(self.triningfile) os.chmod(self.triningfile, 0o777) # 将训练好的数据保存到指定文件夹中 self.recognizer.write(self.triningfile + "/trainer.yml") global data student = Student(str(self.Edit_ID.text()), "未命名", "", "", "") data[self.Edit_ID.text()] = student self.datafile = open("./datafile.txt", 'wb') pickle.dump(data, self.datafile) self.datafile.close() print(data) # 打印经过训练的人脸编号和结束程序 print(" [INFO] {0} faces trained. Exiting Program".format(len(np.unique(ids)))) self.step = 100 # self.progressBar.setProperty("value", self.step)
修改ProcessBar的step更新时间,解决程序异常终止的问题
此处另外涉及的问题在于progressBar. 原版方案代码为
def slot_btn_enter(self): self.count = 0 self.step = 0 # 创建线程并开启 self.thread = threading.Thread(target=self.thread_pic) self.thread.start() # 开启进度条定时器 self.timer.start(100, self) def timerEvent(self, e): if self.step > 58: self.timer.stop() return self.step = self.count+1 self.progressBar.setValue(self.count) def thread_pic(self): while(True): for (x,y,w,h) in faces: cv2.rectangle(self.img, (x,y), (x+w,y+h), (255,0,0), 2) self.count += 1 if self.count >= 60: print("OK!") break self.progressBar.setProperty("value", 75)
如上代码会导致程序异常退出, 分析原因在于在子线程中进行progerssBar的显示状态更新, 导致子线程运行结束后, progressBar同时崩溃
对此进行修复, 修复后代码如下
def slot_btn_enter(self): self.count = 0 self.step = 0 # 创建线程并开启 self.thread = threading.Thread(target=self.thread_pic) self.thread.start() # 开启进度条定时器 self.timer.start(100, self) def reflash(self): self.progressBar.setProperty("value", self.step) # 加载进度条 def timerEvent(self, e): if self.step == 100: self.progressBar.setValue(self.step) self.timer.stop() return elif self.step > 58: self.progressBar.setValue(self.step) return self.step = self.count + 1 self.progressBar.setValue(self.count) def thread_pic(self): while (True): for (x, y, w, h) in faces: cv2.rectangle(self.img, (x, y), (x + w, y + h), (255, 0, 0), 2) self.count += 1 self.step = 65 # self.progressBar.setProperty("value", self.step)
不在子线程内使用self.progressBar顺利解决此程序暴毙问题😘
通过Pillow模块对图像处理
甲方要求身份标注为姓名
但由于cv2.puttext的限制, 使用以下代码进行改动会出现 ??? 无法正常显示
cv2.putText(self.image, "黄粱", (x+5,y-5), self.font, 1, (255,255,255), 2)
对此进行优化:
img_PIL = Image.fromarray(cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)) font = ImageFont.truetype('font.ttf', 20) color = (0, 255, 0, 0) position = (x, y) draw = ImageDraw.Draw(img_PIL) draw.text(position, str(id), font=font, fill=color) self.image = cv2.cvtColor(np.asarray(img_PIL), cv2.COLOR_RGB2BGR)
调用pillow模块进行图像处理顺利解决问题.
数据持久化
为保证存储用户信息, 且尽可能简便, 使用pickle完成用户数据序列化, 并存储于datafile.txt中
class Student(): def __init__(self, id, name, age, sex, parent): self.id = id self.name = name self.age = age self.sex = sex self.parent = parent global data student = Student(str(self.Edit_ID.text()), "未命名", "", "", "") data[self.Edit_ID.text()] = student self.datafile = open("./datafile.txt", 'wb') pickle.dump(data, self.datafile) self.datafile.close() print(data)
用户信息编辑页面通过读取写入文本文件完成对信息修改.