opencv實現人臉識別(五) 運用tkinter進行GUI繪製 整合人臉識別模塊

由於以前學習過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刪去,在執行的動做函數中,分別定義局部變量來獲取攝像頭資源,而後函數結束時釋放資源。

 

9. 報錯:sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 3 supplied

在寫數據庫裏的插入函數時,遇到這個問題,例如conn.execute(‘inssert into xxtable values(?)’, a)  假設我插入a時輸的是‘abc’,就會提示我 there are 3 supplied。也就是這時候傳入的是字符串的長度。

而從下圖能夠看出,把a 設爲元組,那麼傳的就是長度爲一的元組中的元素

 

所以改成: conn.execute(‘inssert into xxtable values(?)’, (a, ))

10.數據庫中設置了兩個表,一個記錄用戶姓名,一個記錄用戶簽到記錄,刪除表中原有記錄,可是因爲兩個表的主鍵id都設置爲autoincrement, 在記錄刪除後,id並無置零。其實在數據庫中,有一個表sqlite_sequence,這個表中會記錄自增量的值。

所以在刪除記錄後,須要將自增量也清零

UPDATE sqlite_sequence SET seq = 0 WHERE name = ‘TableName’

11.對於treeview控件的使用,特別是其中的insert函數, 這裏要插入的每條記錄都是由三個元素組成的,所以須要一個元組組成的列表。而正好數據庫中游標的fetchall(),正好符合要求。

 

12.數據庫中 建表時候, 要用到當前時間,能夠用 create xxtable (cur_time timestamp ) ,插入數據時則用 datetime.now來獲取當前本地時間

 

13.  報錯:IndexError:List index out of range

username = names[idnum-1]

我猜想是idnum的數值越界了。 我打印出函數中idnum的值,發現有35,而原本idnum的值對應的是第幾個錄入人臉數據的人,當是只錄入了兩我的,明顯有錯誤。idnum的值由cv2.face.LBPHFaceRecognize_create().predict  函數返回,這個函數與訓練文件的生成有關,因而在getImageAndLabel函數中把idnum值從新對應。

開始時對應關係錯誤,idnum與 表示第幾張圖片的count值對應, 而應該改成與表示地幾我的的id值對應。

相關文章
相關標籤/搜索