• opencv实现人脸识别(五) 运用tkinter进行GUI绘制 整合人脸识别模块


    因为之前学习过tkinter库,所以在学习了人脸识别模块的编写后,

    打算绘制一个简单的GUI来应用人脸识别功能。

    主界面如下所示:

    签到打开在点开后直接进行人脸识别,如果成功则自动关闭视频窗口。

    录入新的人脸界面:

     输入姓名后打开摄像头,开始拍摄镜头前的人的照片,然后生成训练文件。

    并且可以查询历史签到记录。

    这里需要另外添加的模块是关于数据库的,这里选用了sqlite,所需的功能也很简单,

    只需要两个表,一个用来存放用户姓名,一个用来存放签到记录。

    这里是数据库模块的代码

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

  • 相关阅读:
    管道流
    构造方法中用泛型
    代码实现长提闪烁
    关联事件,向窗体中添加控件,设置控件属性等系列操作
    picturebox中添加图片
    typeof gettype
    groupbox
    static用法
    运算符重载
    类修饰符
  • 原文地址:https://www.cnblogs.com/blsx/p/11272744.html
Copyright © 2020-2023  润新知