opencv face recognition (five) tkinter be using the GUI rendering module to integrate face recognition

Because learned before tkinter library, so learning the preparation of the face recognition module,

We intend to draw a simple GUI to apply face recognition function.

The main interface is as follows:

 

Open sign face recognition directly point to open, if successful, will automatically turn off the video window.

Entered a new face interface:

 

 Enter the name to open the camera and began shooting people in front of the camera pictures, and then generate a training file.

And can query historical attendance record.

 

It should add another module is about the database, where selected sqlite, the desired function is also very simple,

Only two tables, used to store a user name, a record used to store check.

Here is the code of the database module

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值对应。

 

Guess you like

Origin www.cnblogs.com/blsx/p/11272744.html