设计语言:python
Python库:numpy、PIL、pyttsx3、mysql.connector、cv2、os
改进:加入了数据库功能,不是从文本读取数据,并对其中一些逻辑做了改进。
具备功能:
能够实现人脸识别并保存、人脸数据训练、人脸鉴别。将信息保存在数据库中,并查询签到信息(可以查询全部,也可以查询某个人,也可以查询某天的信息),并进行语音播报。
数据库中会定义两个表
Personal和Check_table表
create table Personal(
id int primary key auto_increment,
name varchar(40) not null)charset utf8;
create table Check_table(
id int primary key auto_increment,
name varchar(45) not null,
check_time datetime)charset utf8;
对这两个表的操作有查看是否存在,不存在则创建;对其进行查询、插入操作。
mysql函数定义
import mysql.connector
# 错误 unread result found 连接时加上 buffered=True
def Mysql_Init():
conn = mysql.connector.connect(user='root', password='yangzhi', database='face', charset='utf8', buffered=True)
# 使用cursor()方法获取操作游标
cursor = conn.cursor()
return conn, cursor
# 检测表是否存在
def Check_Tables(conn, cursor):
# 首先检测是否存在 Personal 和 check_in 表
cursor.execute("show tables like 'people'")
result = cursor.fetchone()
if result is None:
Create_table_Personal(conn, cursor)
cursor.execute("show tables like 'Check_table'")
result = cursor.fetchone()
if result is None:
Create_table_Check(conn, cursor)
# 创建个人信息表
def Create_table_Personal(conn, cursor):
sql = """create table Personal(
id int primary key auto_increment,
name varchar(40) not null)charset utf8;
"""
try:
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
except:
conn.rollback()
# 创建签到表
def Create_table_Check(conn, cursor):
sql = """create table Check_table(
id int primary key auto_increment,
name varchar(45) not null,
check_time datetime)charset utf8;
"""
try:
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
except:
conn.rollback()
def Insert_Data_Personal(conn, cursor, name):
sql = "insert into Personal values (null, '%s')" % name
try:
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
except:
# 发生错误时回滚
conn.rollback()
def Insert_Data_Check(conn, cursor, name):
sql = "insert into check_table values (null,'%s', now())" % name
try:
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
except:
# 发生错误时回滚
conn.rollback()
def Query_Data_Personal(conn, cursor):
sql = "select name from personal"
cursor.execute(sql)
conn.commit()
results = cursor.fetchall()
list_name = []
for x in results:
x = list(x)
list_name += x
return list_name
def Query_Data_Check(conn, cursor, name='', *dates):
if dates is ():
if name == '':
sql = "select * from check_table;"
else:
sql = "select * from check_table where name = '%s';" % name
# print(sql)
cursor.execute(sql)
else:
if name == '':
for date in dates:
sql = """select * from check_table where check_time = any (
select check_time from check_table where DATEDIFF(date(check_time),'%s') < 1)
""" % date
# print(sql)
# 执行sql语句
cursor.execute(sql)
else:
for date in dates:
print(date)
sql = """select * from check_table where name = '%s' and check_time = any (
select check_time from check_table where DATEDIFF(date(check_time),'%s') < 1);
""" % name, date
# print(sql)
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
results = cursor.fetchall()
for x in results:
print("("+ str(x[0])+", "+x[1] +", "+ str(x[2]) +")")
# 关闭数据库
def dataBase_Close(conn):
conn.close()
print('database close')
if __name__ == '__main__':
conn, cursor = Mysql_Init()
# Check_Tables(conn, cursor)
# Insert_Data_Personal(conn, cursor, 'lisi')
# Insert_Data_Check(conn, cursor, "lisi")
# Query_Data_Check(conn, cursor, '', '2019-07-04')
# dataBase_Close(conn)
print(Query_Data_Personal(conn,cursor))
# Query_Data_Personal(conn, cursor)
说明:
首先在mysql数据库中先创建好face数据库。
def Mysql_Init():
# 连接mysql数据库 root账户 密码yangzhi 使用数据库 face
conn = mysql.connector.connect(user='root', password='yangzhi', database='face', charset='utf8', buffered=True)
# 使用cursor()方法获取操作游标
cursor = conn.cursor()
return conn, cursor
这个是你自己的人脸分类器所在地址
face_detector = cv2.CascadeClassifier('./lib/haarcascade_frontalface_default.xml')
如:
初始实现代码:
import cv2
import os
import numpy as np
from PIL import Image
import pyttsx3
import sys
import json
import time
import mysql.connector
def Mysql_Init():
conn = mysql.connector.connect(user='root', password='yangzhi', database='face', charset='utf8', buffered=True)
# 使用cursor()方法获取操作游标
cursor = conn.cursor()
return conn, cursor
def Check_Tables(conn, cursor):
# 首先检测是否存在 Personal 和 check_in 表
cursor.execute("show tables like 'people'")
result = cursor.fetchone()
if result is None:
Create_table_Personal(conn, cursor)
cursor.execute("show tables like 'check'")
result = cursor.fetchone()
if result is None:
Create_table_Check(conn, cursor)
def Create_table_Personal(conn, cursor):
sql = """create table Personal(
id int primary key auto_increment,
name varchar(40) not null)charset utf8;
"""
try:
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
except:
conn.rollback()
def Create_table_Check(conn, cursor):
sql = """create table Check_table(
id int primary key auto_increment,
name varchar(45) not null,
check_time datetime)charset utf8;
"""
try:
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
except:
conn.rollback()
def Insert_Data_Personal(conn, cursor, name):
sql = "insert into Personal values (null, '%s')" % name
try:
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
except:
# 发生错误时回滚
conn.rollback()
def Insert_Data_Check(conn, cursor, name):
sql = "insert into check_table values (null,'%s', now())" % name
try:
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
except:
# 发生错误时回滚
conn.rollback()
def Query_Data_Personal(conn, cursor):
sql = "select name from personal"
cursor.execute(sql)
conn.commit()
results = cursor.fetchall()
list_name = []
for x in results:
x = list(x)
list_name += x
return list_name
def Query_Data_Check(conn, cursor, name='', *dates):
if dates is ():
if name == '':
sql = "select * from check_table;"
else:
sql = "select * from check_table where name = '%s';" % name
print(sql)
cursor.execute(sql)
else:
if name == '':
for date in dates:
sql = """select * from check_table where check_time = any (
select check_time from check_table where DATEDIFF(date(check_time),'%s') < 1)
""" % date
print(sql)
# 执行sql语句
cursor.execute(sql)
else:
for date in dates:
print(date)
sql = """select * from check_table where name = '%s' and check_time = any (
select check_time from check_table where DATEDIFF(date(check_time),'%s') < 1);
""" % name, date
print(sql)
# 执行sql语句
cursor.execute(sql)
# 提交到数据库执行
conn.commit()
results = cursor.fetchall()
for x in results:
print("("+ str(x[0])+", "+x[1] +", "+ str(x[2]) +")")
def dataBase_Close(conn):
conn.close()
print('database close')
def makeDir(engine,x):
if not os.path.exists("face_trainer"):
print("创建预训练环境")
engine.say('检测到第一次启动,正在创建预训练环境')
os.mkdir("face_trainer")
engine.say('创建成功')
engine.runAndWait()
x=2
if not os.path.exists("Facedata"):
print("创建训练环境")
engine.say('正在创建训练环境')
os.mkdir("Facedata")
engine.say('创建成功')
engine.runAndWait()
x=2
return x
def getFace(cap,face_id):
face_detector = cv2.CascadeClassifier('./lib/haarcascade_frontalface_default.xml')
print('\n Initializing face capture. Look at the camera and wait ...')
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+w), (255, 0, 0))
count += 1
cv2.imwrite("Facedata/User." + str(face_id) + '.' + 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 >= 100: # 得到1000个样本后退出摄像
break
cv2.destroyAllWindows()
def getImagesAndLabels(path,detector):
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')
id = int(os.path.split(imagePath)[-1].split(".")[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():
# 人脸数据路径
path = 'Facedata'
recognizer = cv2.face.LBPHFaceRecognizer_create()
detector = cv2.CascadeClassifier("./lib/haarcascade_frontalface_default.xml")
print('Training faces. It will take a few seconds. Wait ...')
faces, ids = getImagesAndLabels(path,detector)
recognizer.train(faces, np.array(ids))
recognizer.write(r'face_trainer\trainer.yml')
print("{0} faces trained. Exiting Program".format(len(np.unique(ids))))
def checkFace(cam,names,engine):
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.read('face_trainer/trainer.yml')
cascadePath = "./lib/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:
idnum = names[idnum]
confidence = "{0}%".format(round(100 - confidence))
say(engine, "欢迎 "+idnum+"签到成功!")
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)
time.sleep(2)
Insert_Data_Check(conn, cursor, idnum)#签到信息插入数据库
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()
def say(engine,str):
engine.say(str)
engine.runAndWait()
if __name__ == '__main__':
conn, cursor = Mysql_Init()
Check_Tables(conn, cursor)
names = []
names=Query_Data_Personal(conn, cursor)
print(names)
"""
txt读取名字列表
if os.path.exists("name.txt"):
with open("name.txt") as f:
names = json.loads(f.read())
print(names)
"""
engine = pyttsx3.init()
rate = engine.getProperty('rate')
engine.setProperty('rate', rate - 20)
say(engine, "欢迎使用人脸识别签到系统")
simply_0=1#标志变量 象征是否第一次使用系统
simply_0=makeDir(engine,simply_0)
while True:
if simply_0 == '1': # 不是第一次使用
say(engine, "输入0录入新的人脸信息 输入其他跳转至人脸签到 ")
value = input("0-录入 or other-签到")
if value == '0':
say(engine, "请输入您的姓名")
name = input("请输入姓名:")
names.append(name)
# 新成员信息已保存到数据库中
Insert_Data_Personal(conn, cursor, name)
say(engine, "新的人员信息已保存到数据库中")
say(engine,"正在打开摄像头")
cam = cv2.VideoCapture(0)
say(engine, "注视摄像头,开始采集人脸数据")
getFace(cam,len(names)-1)
say(engine, "采集完毕,开始训练")
trainFace()
say(engine, "训练完毕,开始人脸签到")
else:
say(engine, "开始人脸识别")
say(engine, "正在打开摄像头")
cam = cv2.VideoCapture(0)
else:#是第一次使用系统
say(engine, "这是首次录入人脸信息,请输入您的姓名 ")
name = input("请输入姓名:")
names.append(name)
#新成员信息保存到数据库中
Insert_Data_Personal(conn, cursor, name)
say(engine, "新的人员信息已保存到数据库中")
say(engine, "正在打开摄像头")
cam = cv2.VideoCapture(0)
say(engine, "注视摄像头,开始采集人脸数据")
getFace(cam, len(names) - 1)
say(engine, "采集完毕,开始训练")
trainFace()
say(engine, "训练完毕,跳转至人脸签到")
say(engine, "输入0进行人脸签到,输入其他跳转至主菜单")
simply_1 = input("输入0进行人脸签到,输入其他跳转至主菜单")
if simply_1=='0':
checkFace(cam, names, engine)
say(engine, "签到记录信息已保存到数据库中")
else:
cam.release()
cv2.destroyAllWindows()
"""保存
with open("name.txt", 'w') as f:
f.write(json.dumps(names))
say(engine, "信息已保存")
"""
say(engine, "输入 0 退出系统 ,输入1 查询签到记录 其他任意键 录入人脸模块")
key = input("输入key:(0 - 退出系统 ,1-查询签到记录 other - 重新启动系统)")
if key=='1':
say(engine, "输入要查询记录的姓名或日期")
name_0 = input("输入要查询记录的名字")
date_0=input("输入要查询记录的日期")
Query_Data_Check(conn, cursor, name_0, *date_0)
say(engine, "签到记录查询完毕")
say(engine, "系统即将跳转至录入人脸模块")
if key == '0':
say(engine, "系统将退出,欢迎下次使用")
dataBase_Close(conn)
sys.exit(0)
改进:可以加以个界面、C/S架构保证数据库安全
先设计一个界面,之后再说吧
界面设计初始:(无功能)
from tkinter import *
import cv2
import os
from PIL import Image, ImageTk
from tkinter import ttk
from multiprocessing import Process
import facial_recognition
import database
class APP:
def __init__(self):
self.camera = None # 摄像头
self.root = Tk()
self.root.title('FACE')
self.root.geometry('%dx%d' % (800, 600))
self.createFirstPage()
mainloop()
def createFirstPage(self):
self.page1 = Frame(self.root)
self.page1.pack()
Label(self.page1, text='欢迎使用人脸识别系统', font=('粗体', 20)).pack()
image = Image.open("1.jpg")
photo = ImageTk.PhotoImage(image = image)
self.data1 = Label(self.page1, width=780,image = photo)
self.data1.image = photo
self.data1.pack(padx=5, pady=5)
self.button11 = Button(self.page1, width=18, height=2, text="签到打卡", bg='red', font=("宋", 12),
relief='raise',command = self.createSecondPage)
self.button11.pack(side=LEFT, 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.pack(side=LEFT, 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.pack(side=LEFT, 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.pack(side=LEFT, padx=25, pady = 10)
def createSecondPage(self):
self.camera = cv2.VideoCapture(0)
self.page1.pack_forget()
self.page2 = Frame(self.root)
self.page2.pack()
Label(self.page2, text='欢迎使用人脸识别系统', font=('粗体', 20)).pack()
self.data2 = Label(self.page2)
self.data2.pack(padx=5, pady=5)
self.button21 = Button(self.page2, width=18, height=2, text="返回", bg='gray', font=("宋", 12),
relief='raise',command = self.backFirst)
self.button21.pack(padx=25,pady = 10)
self.video_loop(self.data2)
def video_loop(self, panela):
success, img = self.camera.read() # 从摄像头读取照片
if success:
cv2image = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) # 转换颜色从BGR到RGBA
current_image = Image.fromarray(cv2image) # 将图像转换成Image对象
imgtk = ImageTk.PhotoImage(image=current_image)
panela.imgtk = imgtk
panela.config(image=imgtk)
self.root.after(1, lambda: self.video_loop(panela))
# 签到信息展示
# noinspection PyAttributeOutsideInit
def checkDataView(self):
self.page3 = Frame(self.root)
self.page1.pack_forget()
self.root.geometry('700x360')
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='签到时间')
# 例子
# data = {"item0": ["1a", "2a", "4a"],
# "item1": ["1c", "2c", "3c"]}
# self.checkDate.insert('', 'end', values=data['item0'])
# self.checkDate.insert('', 'end', values=data['item1'])
# # 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)
self.page3.pack()
def backFirst(self):
self.page2.pack_forget()
self.page1.pack()
# 释放摄像头资源
self.camera.release()
cv2.destroyAllWindows()
def backMain(self):
self.root.geometry('900x600')
self.page3.pack_forget()
self.page1.pack()
def quitMain(self):
sys.exit(0)
# # 个人信息展示
# def Dataview(self):
# self.personalData = ttk.Treeview(self.root, show='headings', column=('sid', 'name', 'sex', 'address'))
# self.personalData.column('sid', width=150, anchor="center")
# self.personalData.column('name', width=150, anchor="center")
# self.personalData.column('phone', width=150, anchor="center")
# self.personalData.column('address', width=150, anchor="center")
#
# self.personalData.heading('sid', text='学号')
# self.personalData.heading('name', text='名字')
# self.personalData.heading('phone', text='电话')
# self.personalData.heading('address', text='地址')
# self.personalData.pack()
if __name__ == '__main__':
demo = APP()
效果图:
点击签到打卡
点击查询签到信息
现在的问题:
1、GUI中显示实时图像使从摄像头一帧一帧截取图像并显示在这里,如果要借助这个GUI来实现人脸签到,必然会导致opencv争夺摄像头资源从而卡住,如何解决??(求助)
(可以退而求其次,直接不要这个GUI实时图像,就按钮,文字,也可以实现基本的功能)
2、opencv的窗口如何屏蔽掉?只显示GUI上的图像。
最终实现:https://www.cnblogs.com/blsx/p/11272744.html