由於以前學習過tkinter庫,因此在學習了人臉識別模塊的編寫後,sql
打算繪製一個簡單的GUI來應用人臉識別功能。數據庫
主界面以下所示:多線程
簽到打開在點開後直接進行人臉識別,若是成功則自動關閉視頻窗口。app
錄入新的人臉界面:ide
輸入姓名後打開攝像頭,開始拍攝鏡頭前的人的照片,而後生成訓練文件。函數
而且能夠查詢歷史簽到記錄。oop
這裏須要另外添加的模塊是關於數據庫的,這裏選用了sqlite,所需的功能也很簡單,學習
只須要兩個表,一個用來存放用戶姓名,一個用來存放簽到記錄。fetch
這裏是數據庫模塊的代碼ui
db.py:
import sqlite3 from datetime import * class record: def __init__(self): # 建立或打開一個數據庫 # check_same_thread 屬性用來規避多線程操做數據庫的問題 self.conn = sqlite3.connect("recordinfo.db", check_same_thread=False) # 建立遊標 self.cursor = self.conn.cursor() # 建表 self.conn.execute('create table if not exists record_table(' 'id integer primary key autoincrement,' 'name varchar(30) ,' 'record_time timestamp)') self.conn.execute('create table if not exists name_table(' 'id integer primary key autoincrement,' 'name varchar(30))') # 插入數據 def insert_record(self, name): self.conn.execute('insert into record_table values (null, ?, ?)', (name, datetime.now())) self.conn.commit() def insert_name(self, name): self.conn.execute('insert into name_table values (null, ?)', [name]) self.conn.commit() # 搜索用戶名 def query_name(self): self.cursor.execute("select name from name_table") results = self.cursor.fetchall() name_list = [] for i in results: i = list(i) name_list += i return name_list def query_record(self): self.cursor.execute('select * from record_table') results = self.cursor.fetchall() return results def close(self): self.cursor.close() self.conn.close()
而後是把以前的拍照模塊,訓練模塊整合在一塊兒,綁定到錄入新的人臉的 肯定按鈕上
add_face.py:
import os import cv2 from PIL import Image, ImageTk import numpy as np import db def makeDir(): if not os.path.exists("face_trainer"): os.mkdir("face_trainer") if not os.path.exists("FaceData"): os.mkdir("FaceData") def getFace(name): cap = cv2.VideoCapture(0) face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') count = 0 while True: sucess, img = cap.read() # 從攝像頭讀取圖片 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_detector.detectMultiScale(gray, 1.3, 5) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0)) count += 1 cv2.imwrite("FaceData/User." + name.get() + '.' + str(count) + '.jpg', gray[y: y + h, x: x + w]) cv2.imshow('image', img) # 保持畫面的持續。 k = cv2.waitKey(1) if k == 27: # 經過esc鍵退出攝像 break elif count >= 20: # 獲得1000個樣本後退出攝像 break cap.release() cv2.destroyAllWindows() def getImagesAndLabels(path,detector, usernames): imagePaths = [os.path.join(path, f) for f in os.listdir(path)] faceSamples = [] ids = [] for imagePath in imagePaths: PIL_img = Image.open(imagePath).convert('L') img_numpy = np.array(PIL_img, 'uint8') username = os.path.split(imagePath)[-1].split(".")[1] id = 1 for x in usernames: if username == x: break else: id += 1 faces = detector.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 def trainFace(names): # 人臉數據路徑 path = 'FaceData' recognizer = cv2.face.LBPHFaceRecognizer_create() detector = cv2.CascadeClassifier("haarcascade_frontalface_default.xml") faces, ids = getImagesAndLabels(path, detector, names) recognizer.train(faces, np.array(ids)) recognizer.write(r'face_trainer\trainer.yml') def add_face(name, names): makeDir() getFace(name) trainFace(names) user = db.record() user.insert_name(name.get())
而後把識別人臉的模塊綁定給主界面的簽到按鈕
detect.py:
import cv2 import time import db def check( names): cam = cv2.VideoCapture(0) recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read('face_trainer/trainer.yml') cascadePath = "haarcascade_frontalface_default.xml" faceCascade = cv2.CascadeClassifier(cascadePath) font = cv2.FONT_HERSHEY_SIMPLEX minW = 0.1 * cam.get(3) minH = 0.1 * cam.get(4) while True: ret, img = cam.read() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = faceCascade.detectMultiScale( gray, scaleFactor=1.2, minNeighbors=5, minSize=(int(minW), int(minH)) ) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) idnum, confidence = recognizer.predict(gray[y:y + h, x:x + w]) if confidence < 100: username = names[idnum-1] confidence = "{0}%".format(round(100 - confidence)) cv2.putText(img, str(username), (x + 5, y - 5), font, 1, (0, 0, 255), 1) cv2.putText(img, str(confidence), (x + 5, y + h - 5), font, 1, (0, 0, 0), 1) cv2.imshow('camera', img) time.sleep(2) db.record().insert_record(username) # 簽到信息插入數據庫 cam.release() cv2.destroyAllWindows() return else: idnum = "unknown" confidence = "{0}%".format(round(100 - confidence)) cv2.putText(img, str(idnum), (x + 5, y - 5), font, 1, (0, 0, 255), 1) cv2.putText(img, str(confidence), (x + 5, y + h - 5), font, 1, (0, 0, 0), 1) cv2.imshow('camera', img) k = cv2.waitKey(10) if k == 27: break cam.release() cv2.destroyAllWindows()
把上述模塊整合到GUI界面中
from tkinter import * from tkinter import ttk import add_face import db import detect class APP: def __init__(self): self.root = Tk() self.root.title('FACE') self.root.geometry('%dx%d' % (400, 300)) # 數據庫實例建立 self.mydb = db.record() self.createFirstPage() # 新錄入的人的姓名 self.name = StringVar() mainloop() def createFirstPage(self): self.page1 = Frame(self.root) self.page1.grid() Label(self.page1, height=4, text='人臉識別系統', font=('粗體', 20)).grid(columnspan=2) #self.usernames 是 用戶名字組成的列表 self.usernames = [] self.usernames = self.mydb.query_name() self.button11 = Button(self.page1, width=18, height=2, text="簽到打卡", bg='red', font=("宋", 12), relief='raise', command = lambda :detect.check( self.usernames)) self.button11.grid(row=1, column=0, padx=25, pady=10) self.button12 = Button(self.page1, width=18, height=2, text="錄入新的人臉", bg='green', font=("宋", 12), relief='raise', command = self.createSecondPage) self.button12.grid(row=1, column=1, padx=25, pady=10) self.button13 = Button(self.page1, width=18, height=2, text="查詢簽到信息", bg='white', font=("宋", 12), relief='raise',command = self.checkDataView) self.button13.grid( row=2, column=0,padx=25, pady=10) self.button14 = Button(self.page1, width=18, height=2, text="退出系統", bg='gray', font=("宋", 12), relief='raise',command = self.quitMain) self.button14.grid(row=2, column=1,padx=25, pady=10) def createSecondPage(self): # self.camera = cv2.VideoCapture(0) self.page1.grid_forget() self.page2 = Frame(self.root) self.page2.pack() Label(self.page2, text='歡迎使用人臉識別系統', font=('粗體', 20)).pack() # 輸入姓名的文本框 font1 = ('宋',18) # self.name = StringVar() self.text = Entry(self.page2, textvariable=self.name, width=20, font=font1).pack(side=LEFT) self.name.set('請輸入姓名') # 確認名字的按鈕 self.button21 = Button(self.page2, text='確認', bg='white', font=("宋", 12), relief='raise', command=lambda :add_face.add_face( self.name, self.usernames)) self.button21.pack(side=LEFT, padx=5, pady=10) # 返回按鈕 self.button22 = Button(self.page2, text="返回", bg='white', font=("宋", 12), relief='raise',command = self.backFirst) self.button22.pack(side=LEFT, padx=10, pady=10) def checkDataView(self): self.page3 = Frame(self.root) self.page1.grid_forget() self.root.geometry('700x360') self.page3.pack() Label(self.page3, text='今日簽到信息', bg='white', fg='red', font=('宋體', 25)).pack(side=TOP, fill='x') # 簽到信息查看視圖 self.checkDate = ttk.Treeview(self.page3, show='headings', column=('sid', 'name', 'check_time')) self.checkDate.column('sid', width=100, anchor="center") self.checkDate.column('name', width=200, anchor="center") self.checkDate.column('check_time', width=300, anchor="center") self.checkDate.heading('sid', text='簽到序號') self.checkDate.heading('name', text='名字') self.checkDate.heading('check_time', text='簽到時間') # 插入數據 self.records = self.mydb.query_record() for i in self.records: self.checkDate.insert('', 'end', values=i) # y滾動條 yscrollbar = Scrollbar(self.page3, orient=VERTICAL, command=self.checkDate.yview) self.checkDate.configure(yscrollcommand=yscrollbar.set) yscrollbar.pack(side=RIGHT, fill=Y) self.checkDate.pack(expand=1, fill=BOTH) # 返回按鈕 Button(self.page3, width=20, height=2, text="返回", bg='gray', font=("宋", 12), relief='raise',command =self.backMain).pack(padx = 20, pady = 20) def backFirst(self): self.page2.pack_forget() self.root.geometry('400x300') self.page1.grid() def backMain(self): self.root.geometry('400x300') self.page3.pack_forget() self.page1.grid() def quitMain(self): sys.exit(0) if __name__ == '__main__': demo = APP()
整合的代碼邏輯還能夠更清楚一點,之後有時間改進,
而後列出了部分學習過程當中遇到的新知識和問題,以做參考
QA:
1.Python: cv2.waitKey([delay]) → retval
waitKey()函數的功能是不斷刷新圖像,頻率時間爲delay,單位爲ms。
返回值爲當前鍵盤按鍵值。
用cv2.waitKey(n) == 27 break
即爲 按下esc鍵時退出當前畫面
2.os.path.join()函數:
鏈接兩個或更多的路徑名組件
os.listdir() 方法
用於返回指定的文件夾包含的文件或文件夾的名字的列表。這個列表以字母順序
3.Image.open(imagePath).convert('L')
L表明轉換到灰度圖
4.os.path.split 把路徑分割成 dirname 和 basename,返回一個元組
5.ret,frame = cap.read()
cap.read()按幀讀取視頻,ret,frame是獲cap.read()方法的兩個返回值。其中ret是布爾值,若是讀取幀是正確的則返回True,若是文件讀取到結尾,它的返回值就爲False。frame就是每一幀的圖像,是個三維矩陣。
6.把StringVar類型變量傳入函數中時,用StringVar.get()方法能夠獲得字符串類型,並進行字符串鏈接等操做
7.tkinter庫中的組件,在使用command參數時,在函數名前加 lambda: 就能夠在函數名後加函數的參數,不然會報錯。 例如 Button(root, command= lambda: FUNC(a,b)).pack()
8.遇到一個問題,在進行一次新的人臉錄入後,就沒法進行簽到,必須退出系統從新進入,才能進行簽到,報的錯誤是 error: (-215:Assertion failed) !_src.empty() in function 'cvtColor'
通過檢查, 我在寫GUI的類時,一開始寫的是 self.camera = cv2.VideoCapture(0), 而後將self.camera 調入綁定的動做函數,而在多個動做函數中,都有cv2.VideoCapture(0).release的操做。問題就在於只獲取了一次攝像頭資源,但釋放了屢次。
解決方案:把GUI類中的self.camera刪去,在執行的動做函數中,分別定義局部變量來獲取攝像頭資源,而後函數結束時釋放資源。
username = names[idnum-1]