PyQt5+Caffe+Opencv搭建人臉識別登入介面
最近開始學習Qt,結合之前學習過的caffe一起搭建了一個人臉識別登入系統的程式,新手可能有理解不到位的情況,還請大家多多指教。
我的想法是用opencv自帶的人臉檢測演算法檢測出面部,利用caffe訓練好的卷積神經網路來提取特徵,通過計算當前檢測到的人臉與已近註冊的所有使用者的面部特徵之間的相似度,如果最大的相似度大於一個閾值,就可以確定當前檢測到的人臉對應為這個相似度最大的使用者了。
###Caffe人臉識別
因為不斷有新的使用者加入,然而新增新使用者後重新調整CNN的網路結構太費時間,所以不能用CNN去判別一個使用者屬於哪一類。一個訓練好的人臉識別網路擁有很強大的特徵提取能力(例如這裡用到的VGG face),我們finetune預訓練的網路時會調整最後一層的分類數目,所以最後一層的目的是為了分類,倒數第二個全連線層(或者前面的)提取到的特徵通過簡單的計算距離也可以達到很高的準確率,因此可以用計算相似度的方式判斷類別。
載入finetune後的VGG模型
程式碼就不詳細解釋了,我用的是拿1000個人臉微調後的VGGface,效果比用直接下載來的weight檔案好一點,這裡可以用原始的權重檔案代替。
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,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',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層的輸出,也就是image通過網路提取到的特徵;A的維度為[1,4096],為需要檢測的目標,B的維度為[n,表示所有已註冊的使用者的特徵,cal_cos返回n個相似度,值越大,越可能是同一個人。
###Opencv人臉檢測
檢測人臉位置的演算法用了opencv自帶的人臉檢測器。
import cv2 face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
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,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,USRNAME if self.usrNameLine.text() not in ALLUSER: QMessageBox.information(self,"Information","使用者不存在,請註冊") elif ALLUSER[self.usrNameLine.text()] == self.passWordLine.text(): QMessageBox.information(self,"Welcome!") else: QMessageBox.information(self,"密碼錯誤!") def signUp(self): global ALLFEATURE,USRNAME if self.usrNameLine.text() in ALLUSER: QMessageBox.information(self,"使用者已存在!") elif len(self.passWordLine.text()) < 3: QMessageBox.information(self,"密碼太短!") 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後,繪製一個藍色的框,然後resize到固定的大小(對應網路的輸入)。將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,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,"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),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,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,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,"Welcome," + USRNAME[m]) else: QMessageBox.information(self,"識別失敗!") self.label.setPixmap(self.pixMap) def capPicture(self): if (self.cap.isOpened()): # get a frame ret,self.label.height()))
###效果
密碼登入,輸入合法的密碼後點擊sign in,顯示歡迎。
註冊介面
識別介面
登入成功
點選capPicture按鈕後,開始計算相似度,大於0.9提示登入成功,並顯示使用者名稱。
###缺點和不足
程式用pyinstaller打包後,親測可以在別的linux電腦下執行。程式碼和檔案可以參考我的Github(沒有VGG face的權重),第一次寫部落格,非常感謝大家的意見。總結一下不足:
1.初始話caffe模型很費時間,所以程式開啟時要等一兩秒;
2.使用者資訊用檔案的形式儲存並不安全,可以用mysql儲存到資料庫,需要時調出;
3.人臉位置檢測可以用faster rcnn代替,再加上對齊;
4.識別很耗費時間,因此不能實時檢測,應該可以用多執行緒解決。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。