PyQt5结合Caffe与OpenCV构建人脸识别登录系统详解
本文介绍如何使用PyQt5、Caffe和OpenCV搭建一个人脸识别登录系统。系统利用OpenCV进行人脸检测,通过微调后的VGG Face网络提取特征,并计算余弦相似度完成身份验证。文章涵盖界面设计、用户注册、密码登录及人脸识别流程,并分析了模型加载耗时、数据存储安全性及实时性等方面的不足与优化建议。
系统概述与核心思路
近期开始接触Qt开发,并尝试将之前学习的Caffe框架结合,构建了一个基于人脸识别的登录系统程序。作为新手,文中可能存在理解不够深入之处,恳请各位读者批评指正。
核心思路是利用OpenCV内置的人脸检测算法定位面部区域,随后使用Caffe训练好的卷积神经网络(CNN)提取面部特征。通过计算当前检测到的人脸与已注册用户库中所有面部特征之间的相似度,若最大相似度超过设定阈值,则判定当前人脸属于该相似度最高的注册用户。
Caffe人脸识别原理
###Caffe人脸识别
由于系统需支持新用户持续加入,若每次添加用户都重新调整CNN网络结构耗时过长,因此不宜直接使用CNN进行类别判别。经过预训练的人脸识别网络(如本文使用的VGG Face)具备强大的特征提取能力。在微调预训练网络时,通常仅调整最后一层的分类数量,该层用于分类;而倒数第二层全连接层(或更早层)提取的特征,通过简单的距离计算即可实现高准确率识别。因此,采用计算相似度的方式来判断用户类别更为高效。
载入finetune后的VGG模型
此处省略代码详细解释,本文使用的是基于1000张人脸数据微调后的VGG Face模型,其效果优于直接使用下载的原始权重文件,当然实际应用中也可替换为原始权重文件。
import caffe
model_def = 'VGG_FACE_deploy.prototxt'
model_weights = 'VGG_Face_finetune_1000_iter_900.caffemodel'
# create transformer for the input called 'data'
net = caffe.Net(model_def, # defines the structure of the model
model_weights, # contains the trained weights
caffe.TEST)
transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
transformer.set_transpose('data', (2,0,1)) # move image channels to outermost dimension
transformer.set_mean('data', np.array([104, 117, 123])) # subtract the dataset-mean value in each channel
transformer.set_raw_scale('data', 255) # rescale from [0, 1] to [0, 255]
transformer.set_channel_swap('data', (2,1,0)) # swap channels from RGB to BGRxpor计算余弦相似度
import numpy as np
# 计算余弦距离
def cal_cos(A,B):
num = A.dot(B.T) #若为行向量则 A * B.T
print(B.shape)
if B.ndim == 1:
denom = np.linalg.norm(A) * np.linalg.norm(B)
else:
denom = np.linalg.norm(A) * np.linalg.norm(B, axis=1)
#print(num)
cos = num / denom #余弦值
sim = 0.5 + 0.5 * cos #归一化
return sim
def cal_feature(image):
#for i,img_name in enumerate(os.listdir(path)):
#image = caffe.io.load_image(os.path.join(path,img_name))
transformed_image = transformer.preprocess('data', image)
net.blobs['data'].data[0,:,:,:] = transformed_image
output = net.forward()
return net.blobs['fc7'].data[0]cal_feature函数返回fc7层的输出,即图像通过网络提取到的特征向量;A的维度为[1, 4096],代表待检测的目标图像特征;B的维度为[n,4096],表示所有已注册用户的面部特征集合。cal_cos函数返回n个相似度值,数值越大,表明目标与注册用户越可能是同一人。
OpenCV人脸检测实现
###Opencv人脸检测
人脸位置检测算法采用OpenCV自带的人脸检测器实现。
import cv2
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')PyQt界面设计与功能实现
PyQt界面
定义全局变量以存储用户信息及提取的特征数据。采用文件形式将这些信息保存至本地,以便程序下次运行时能够提前载入历史数据。
import sys
import os
import pickle
global ALLFEATURE, NEWFEATURE, tempUsrName, ALLUSER, USRNAME
with open('USRNAME.pickle', 'rb') as f:
USRNAME = pickle.load(f)
with open('ALLUSER.pickle', 'rb') as f:
ALLUSER = pickle.load(f)
ALLFEATURE = np.load('usrfeature.npy')
NEWFEATURE = np.array([])
tempUsrName = {}设计一个登录界面
使用PyQt设计用户界面,实现用户注册(需录入照片)、用户密码登录以及人脸识别登录等功能模块。
创建一个TabWidget界面
tab1标签页用于实现密码登录与用户注册功能;tab2标签页用于实现人脸识别登录功能。
from PyQt5.QtWidgets import (QWidget, QMessageBox, QLabel, QDialog,
QApplication, QPushButton, QDesktopWidget, QLineEdit, QTabWidget)
from PyQt5.QtGui import QIcon, QPixmap, QImage, QPalette, QBrush
from PyQt5.QtCore import Qt, QTimer
class TabWidget(QTabWidget):
def __init__(self, parent=None):
super(TabWidget, self).__init__(parent)
self.setWindowTitle('Face Recognition')
self.setWindowIcon(QIcon('camera.png'))
self.resize(400, 260)
self.center()
self.mContent = passWordSign()
self.mIndex = faceSign()
self.addTab(self.mContent, QIcon('camera.png'), u"密码登录")
self.addTab(self.mIndex, u"人脸识别")
palette=QPalette()
icon=QPixmap('background.jpg').scaled(400, 260)
palette.setBrush(self.backgroundRole(), QBrush(icon)) #添加背景图片
self.setPalette(palette)
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
if __name__ == '__main__':
app = QApplication(sys.argv)
t = TabWidget()
t.show()
#ex = Example()
sys.exit(app.exec_())用户注册和密码登录
界面分别添加两个按钮和两个文本框,文本框用于输入用户名和密码,按钮分别绑定注册和登录事件。addPicture函数用于在注册过程中录入用户照片。
class passWordSign(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#self.setGeometry(0, 0, 450, 300)
self.signUpButton = QPushButton(QIcon('camera.png'), 'Sign up', self)
self.signUpButton.move(300, 200)
self.signInButton = QPushButton(QIcon('camera.png'), 'Sign in', self)
self.signInButton.move(200, 200)
self.usrNameLine = QLineEdit( self )
self.usrNameLine.setPlaceholderText('User Name')
self.usrNameLine.setFixedSize(200, 30)
self.usrNameLine.move(100, 50)
self.passWordLine = QLineEdit(self)
self.passWordLine.setEchoMode(QLineEdit.Password)
self.passWordLine.setPlaceholderText('Pass Word')
self.passWordLine.setFixedSize(200, 30)
self.passWordLine.move(100, 120)
self.signInButton.clicked.connect(self.signIn)
self.signUpButton.clicked.connect(self.signUp)
self.show()
def signIn(self):
global ALLFEATURE, NEWFEATURE, tempUsrName, ALLUSER, USRNAME
if self.usrNameLine.text() not in ALLUSER:
QMessageBox.information(self,"Information","用户不存在,请注册")
elif ALLUSER[self.usrNameLine.text()] == self.passWordLine.text():
QMessageBox.information(self,"Information","Welcome!")
else:
QMessageBox.information(self,"Information","密码错误!")
def signUp(self):
global ALLFEATURE, NEWFEATURE, tempUsrName, ALLUSER, USRNAME
if self.usrNameLine.text() in ALLUSER:
QMessageBox.information(self,"Information","用户已存在!")
elif len(self.passWordLine.text()) < 3:
QMessageBox.information(self,"Information","密码太短!")
else:
tempUsrName.clear()
tempUsrName[self.usrNameLine.text()] = self.passWordLine.text()
self.addPicture()
def addPicture(self):
dialog = Dialog(parent=self)
dialog.show()录入用户人脸
点击sign up按钮后弹出对话框,利用label控件显示摄像头捕获的画面。首先通过OpenCV打开摄像头,使用内置人脸检测器检测人脸self.face,并绘制蓝色边框,随后调整图像大小至固定尺寸以匹配网络输入要求。将OpenCV图片格式转换为QLabel可显示的格式,并利用Qtimer定时器定期刷新画面。监听鼠标点击事件mousePressEvent,点击鼠标后保存当前录入的用户注册信息及对应的面部特征。最后关闭摄像头并提示注册成功。
class Dialog(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self.resize(240, 200)
self.label = QLabel(self)
self.label.setFixedWidth(150)
self.label.setFixedHeight(150)
self.label.move(40, 20)
pixMap = QPixmap("face.jpg").scaled(self.label.width(),self.label.height())
self.label.setPixmap(pixMap)
self.label.show()
self.timer = QTimer()
self.timer.start()
self.timer.setInterval(100)
self.cap = cv2.VideoCapture(0)
self.timer.timeout.connect(self.capPicture)
def mousePressEvent(self, event):
global ALLFEATURE, NEWFEATURE, tempUsrName, ALLUSER, USRNAME
self.cap.release()
NEWFEATURE = cal_feature(self.face).reshape([1,-1])
if NEWFEATURE.size > 0:
for key, value in tempUsrName.items():
ALLUSER[key] = value
USRNAME.append(key)
with open('ALLUSER.pickle', 'wb') as f:
pickle.dump(ALLUSER, f)
with open('USRNAME.pickle', 'wb') as f:
pickle.dump(USRNAME, f)
print(ALLFEATURE,NEWFEATURE)
ALLFEATURE = np.concatenate((ALLFEATURE, NEWFEATURE), axis=0)
np.save('usrfeature.npy', ALLFEATURE)
QMessageBox.information(self,"Information","Success!")
def capPicture(self):
if (self.cap.isOpened()):
# get a frame
ret, img = self.cap.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
self.face = cv2.resize(img[y:y+h, x:x+w],(224, 224), interpolation=cv2.INTER_CUBIC)
height, width, bytesPerComponent = img.shape
bytesPerLine = bytesPerComponent * width
# 变换彩色空间顺序
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)
# 转为QImage对象
self.image = QImage(img.data, width, height, bytesPerLine, QImage.Format_RGB888)
self.label.setPixmap(QPixmap.fromImage(self.image).scaled(self.label.width(),self.label.height()))人脸识别登录
登录模块设计与前述类似,添加一个label控件用于显示视频流,以及两个按钮分别用于启动检测和选择图片。当计算出的最大相似度大于0.9时,界面显示登录成功。
class faceSign(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.label = QLabel(self)
self.label.setFixedWidth(260)
self.label.setFixedHeight(200)
self.label.move(20, 15)
self.pixMap = QPixmap("face.jpg").scaled(self.label.width(),self.label.height())
self.label.setPixmap(self.pixMap)
self.label.show()
self.startButton = QPushButton('start', self)
self.startButton.move(300, 50)
self.capPictureButton = QPushButton('capPicture', self)
self.capPictureButton.move(300, 150)
self.startButton.clicked.connect(self.start)
self.capPictureButton.clicked.connect(self.cap)
#self.cap = cv2.VideoCapture(0)
#self.ret, self.img = self.cap.read()
self.timer = QTimer()
self.timer.start()
self.timer.setInterval(100)
def start(self,event):
self.cap = cv2.VideoCapture(0)
self.timer.timeout.connect(self.capPicture)
def cap(self,event):
global ALLFEATURE, NEWFEATURE, tempUsrName, ALLUSER, USRNAME
self.cap.release()
feature = cal_feature(self.face)
#np.save('usrfeature.npy', ALLFEATURE)
sim = cal_cos(feature,np.array(ALLFEATURE))
m = np.argmax(sim)
if max(sim)>0.9:
print(sim, USRNAME)
QMessageBox.information(self,"Information","Welcome," + USRNAME[m])
else:
QMessageBox.information(self,"Information","识别失败!")
self.label.setPixmap(self.pixMap)
def capPicture(self):
if (self.cap.isOpened()):
# get a frame
ret, img = self.cap.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
self.face = cv2.resize(img[y:y+h, x:x+w],(224, 224), interpolation=cv2.INTER_CUBIC)
height, width, bytesPerComponent = img.shape
bytesPerLine = bytesPerComponent * width
# 变换彩色空间顺序
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)
# 转为QImage对象
self.image = QImage(img.data, width, height, bytesPerLine, QImage.Format_RGB888)
self.label.setPixmap(QPixmap.fromImage(self.image).scaled(self.label.width(),self.label.height()))系统效果展示
###效果
密码登录:输入合法密码后点击sign in按钮,界面显示欢迎信息。

注册界面

识别界面

登录成功
点击capPicture按钮后,系统开始计算相似度,若结果大于0.9则提示登录成功,并显示对应用户名。

不足与优化建议
###缺点和不足
程序使用pyinstaller打包后,经测试可在其他Linux电脑上运行。代码及文件参考我的Github仓库(未包含VGG Face权重文件)。首次撰写博客,感谢大家的意见。总结不足之处如下:
1.初始化Caffe模型耗时较长,导致程序启动时需等待一两秒; 2.用户信息以文件形式保存存在安全隐患,建议改用MySQL数据库存储,并在需要时调用; 3.人脸位置检测可考虑使用faster rcnn替代,并增加人脸对齐步骤; 4.识别过程耗时较多,无法实现实时检测,建议通过多线程技术解决。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









