基于wxpython+MySQL--实现人脸检测识别的宿舍人脸检测系统
一.功能需求
1)假定该系统完成后将工作于学生宿舍门口,摄像头安装于门的猫眼处、或者门侧边墙上;对本宿舍的学生进行人脸识别(身份验证),通过验证就播放语音“欢迎光临!”(模拟开门),否则播放“抱歉,无法验证您的身份!”,同时用数据库记录来访者的身份、人脸图片和访问时间等信息。
2)先在笔记本电脑上进行代码编程、实验验证,然后移植到树莓派平台;
3)如果没有连接树莓派的摄像头,可以读取事先录制的视频文件;
4)开发编程软件不作限定,但要求跨平台设计,建议采用MySQL数据库和Python语言进行开发。
先展示下程序结果:
代码:Apartment_Manager1.zip
模型:人脸识别与训练模型.zip
二.数据库建立
2.1 初始化数据库
初始化数据库实现代码:
#初始化数据库
def initDatabase(self):
conn = sqlite3.connect("inspurer.db") #建立数据库连接
cur = conn.cursor() #得到游标对象
cur.execute('''create table if not exists worker_info
(name text not null,
id int not null primary key,
face_feature array not null)''')
cur.execute('''create table if not exists logcat
(datetime text not null,
id int not null,
name text not null)''')
cur.close()
conn.commit()
conn.close()
2.2 压缩/解压数据流
压缩/解压数据流实现代码:
def adapt_array(self,arr):
out = io.BytesIO()
np.save(out, arr)
out.seek(0)
dataa = out.read()
# 压缩数据流
return sqlite3.Binary(zlib.compress(dataa, zlib.Z_BEST_COMPRESSION))
def convert_array(self,text):
out = io.BytesIO(text)
out.seek(0)
dataa = out.read()
# 解压缩数据流
out = io.BytesIO(zlib.decompress(dataa))
return np.load(out)
2.3 输入数据
输入数据实现代码:
def insertARow(self,Row,type):
conn = sqlite3.connect("inspurer.db") # 建立数据库连接
cur = conn.cursor() # 得到游标对象
if type == 1:
cur.execute("insert into worker_info (id,name,face_feature) values(?,?,?)",
(Row[0],Row[1],self.adapt_array(Row[2])))
print("写人脸数据成功")
if type == 2:
cur.execute("insert into logcat (id,name,datetime) values(?,?,?)",
(Row[0],Row[1],Row[2]))
print("写日志成功")
pass
cur.close()
conn.commit()
conn.close()
pass
def loadDataBase(self,type):
conn = sqlite3.connect("inspurer.db") # 建立数据库连接
cur = conn.cursor() # 得到游标对象
if type == 1:
self.knew_id = []
self.knew_name = []
self.knew_face_feature = []
cur.execute('select id,name,face_feature from worker_info')
origin = cur.fetchall()
for row in origin:
print(row[0])
self.knew_id.append(row[0])
print(row[1])
self.knew_name.append(row[1])
print(self.convert_array(row[2]))
self.knew_face_feature.append(self.convert_array(row[2]))
if type == 2:
self.logcat_id = []
self.logcat_name = []
self.logcat_datetime = []
# self.logcat_late = []
cur.execute('select id,name,datetime from logcat')
origin = cur.fetchall()
for row in origin:
print(row[0])
self.logcat_id.append(row[0])
print(row[1])
self.logcat_name.append(row[1])
print(row[2])
self.logcat_datetime.append(row[2])
# print(row[3])
# self.logcat_late.append(row[3])
三.宿舍管理
3.1 新建录入
新建录入实现代码:
def register_cap(self,event):
# 创建 cv2 摄像头对象
self.cap = cv2.VideoCapture(0)
# cap.set(propId, value)
# 设置视频参数,propId设置的视频参数,value设置的参数值
# self.cap.set(3, 600)
# self.cap.set(4,600)
# cap是否初始化成功
while self.cap.isOpened():
# cap.read()
# 返回两个值:
# 一个布尔值true/false,用来判断读取视频是否成功/是否到视频末尾
# 图像对象,图像的三维矩阵
flag, im_rd = self.cap.read()
# 每帧数据延时1ms,延时为0读取的是静态帧
kk = cv2.waitKey(1)
# 人脸数 dets
dets = detector(im_rd, 1)
# 检测到人脸
if len(dets) != 0:
biggest_face = dets[0]
#取占比最大的脸
maxArea = 0
for det in dets:
w = det.right() - det.left()
h = det.top()-det.bottom()
if w*h > maxArea:
biggest_face = det
maxArea = w*h
# 绘制矩形框
cv2.rectangle(im_rd, tuple([biggest_face.left(), biggest_face.top()]),
tuple([biggest_face.right(), biggest_face.bottom()]),
(255, 0, 0), 2)
img_height, img_width = im_rd.shape[:2]
image1 = cv2.cvtColor(im_rd, cv2.COLOR_BGR2RGB)
pic = wx.Bitmap.FromBuffer(img_width, img_height, image1)
# 显示图片在panel上
self.bmp.SetBitmap(pic)
# 获取当前捕获到的图像的所有人脸的特征,存储到 features_cap_arr
shape = predictor(im_rd, biggest_face)
features_cap = facerec.compute_face_descriptor(im_rd, shape)
# 对于某张人脸,遍历所有存储的人脸特征
for i,knew_face_feature in enumerate(self.knew_face_feature):
# 将某张人脸与存储的所有人脸数据进行比对
compare = return_euclidean_distance(features_cap, knew_face_feature)
if compare == "same": # 找到了相似脸
self.infoText.AppendText(self.getDateAndTime()+"学号:"+str(self.knew_id[i])
+" 姓名:"+self.knew_name[i]+" 的人脸数据已存在\r\n")
self.flag_registed = True
self.OnFinishRegister()
_thread.exit()
face_height = biggest_face.bottom()-biggest_face.top()
face_width = biggest_face.right()- biggest_face.left()
im_blank = np.zeros((face_height, face_width, 3), np.uint8)
try:
for ii in range(face_height):
for jj in range(face_width):
im_blank[ii][jj] = im_rd[biggest_face.top() + ii][biggest_face.left() + jj]
if len(self.name)>0:
cv2.imencode('.jpg', im_blank)[1].tofile(
PATH_FACE + self.name + "/img_face_" + str(self.pic_num) + ".jpg") # 正确方法
self.pic_num += 1
print("写入本地:", str(PATH_FACE + self.name) + "/img_face_" + str(self.pic_num) + ".jpg")
self.infoText.AppendText(self.getDateAndTime()+"图片:"+str(PATH_FACE + self.name) + "/img_face_" + str(self.pic_num) + ".jpg保存成功\r\n")
except:
print("保存照片异常,请对准摄像头")
if self.new_register.IsEnabled():
_thread.exit()
if self.pic_num == 10:
self.OnFinishRegister()
_thread.exit()
def OnNewRegisterClicked(self,event):
self.new_register.Enable(False)
self.finish_register.Enable(True)
self.loadDataBase(1)
while self.id == ID_WORKER_UNAVIABLE:
self.id = wx.GetNumberFromUser(message="请输入您的学号(-1不可用)",
prompt="学号", caption="温馨提示",
value=ID_WORKER_UNAVIABLE,
parent=self.bmp,max=100000000,min=ID_WORKER_UNAVIABLE)
for knew_id in self.knew_id:
if knew_id == self.id:
self.id = ID_WORKER_UNAVIABLE
wx.MessageBox(message="学号已存在,请重新输入", caption="警告")
while self.name == '':
self.name = wx.GetTextFromUser(message="请输入您的的姓名,用于创建姓名文件夹",
caption="温馨提示",
default_value="", parent=self.bmp)
# 监测是否重名
for exsit_name in (os.listdir(PATH_FACE)):
if self.name == exsit_name:
wx.MessageBox(message="姓名文件夹已存在,请重新输入", caption="警告")
self.name = ''
break
os.makedirs(PATH_FACE+self.name)
_thread.start_new_thread(self.register_cap,(event,))
pass
3.2 人员删除
人员删除实现代码:
def delmember(self,event):
self.new_register.Enable(False)
self.delmember.Enable(Enable)
self.loadDataBase(1)
self.name = wx.GetTextFromUser(message="请输入您的的姓名",
caption="温馨提示",
default_value="", parent=self.bmp)
filePath = PATH_FACE + self.name
if os.path.exists(filePath):
con = sqlite3.connect('inspurer.db')
cur = con.cursor()
del_sql = "delete from worker_info where name = "+self.name+""
print(del_sql)
try:
cur.execute(del_sql)#删除的数据为一个元组,当只有一个数据的时候,需要加一个逗号,PyMySQL则不需要加逗号
con.commit()#提交事务
except Exception as e:
# print(e)
con.rollback()#删除失败则回滚
finally:
cur.close()
con.close()
for fileList in os.walk(filePath):
for name in fileList[2]:
os.chmod(os.path.join(fileList[0],name), stat.S_IWRITE)
os.remove(os.path.join(fileList[0],name))
shutil.rmtree(filePath)
wx.MessageBox(message="删除成功", caption="提示")
else:
wx.MessageBox(message="此人不存在", caption="警告")
self.initData()
四.检测人员
4.1 开始检测
开始检测实现代码:
def punchcard_cap(self,event):
self.cap = cv2.VideoCapture(0)
# cap.set(propId, value)
# 设置视频参数,propId设置的视频参数,value设置的参数值
# self.cap.set(3, 600)
# self.cap.set(4,600)
# cap是否初始化成功
while self.cap.isOpened():
# cap.read()
# 返回两个值:
# 一个布尔值true/false,用来判断读取视频是否成功/是否到视频末尾
# 图像对象,图像的三维矩阵
flag, im_rd = self.cap.read()
# 每帧数据延时1ms,延时为0读取的是静态帧
kk = cv2.waitKey(1)
# 人脸数 dets
dets = detector(im_rd, 1)
# 检测到人脸
if len(dets) != 0:
biggest_face = dets[0]
# 取占比最大的脸
maxArea = 0
for det in dets:
w = det.right() - det.left()
h = det.top() - det.bottom()
if w * h > maxArea:
biggest_face = det
maxArea = w * h
# 绘制矩形框
cv2.rectangle(im_rd, tuple([biggest_face.left(), biggest_face.top()]),
tuple([biggest_face.right(), biggest_face.bottom()]),
(255, 0, 255), 2)
img_height, img_width = im_rd.shape[:2]
image1 = cv2.cvtColor(im_rd, cv2.COLOR_BGR2RGB)
pic = wx.Bitmap.FromBuffer(img_width, img_height, image1)
# 显示图片在panel上
self.bmp.SetBitmap(pic)
# 获取当前捕获到的图像的所有人脸的特征,存储到 features_cap_arr
shape = predictor(im_rd, biggest_face)
features_cap = facerec.compute_face_descriptor(im_rd, shape)
# 对于某张人脸,遍历所有存储的人脸特征
for i, knew_face_feature in enumerate(self.knew_face_feature):
# 将某张人脸与存储的所有人脸数据进行比对
compare = return_euclidean_distance(features_cap, knew_face_feature)
if compare == "same": # 找到了相似脸
nowdt = self.getDateAndTime()
for j,logcat_name in enumerate(self.logcat_name):
if logcat_name == self.knew_name[i] and nowdt[0:nowdt.index(" ")] == self.logcat_datetime[j][0:self.logcat_datetime[j].index(" ")]:
break
else:
engine = pyttsx3.init()
engine.say("Welcome to 1221")
engine.runAndWait()
# playsound("D:\\asus\\Documents\\audio.mp3")
# wx.MessageBox(message="欢迎光临!!",caption="提示")
self.insertARow([self.knew_id[i],self.knew_name[i],nowdt],2)
if self.start_punchcard.IsEnabled():
self.bmp.SetBitmap(wx.Bitmap(self.pic_index))
_thread.exit()
def OnStartPunchCardClicked(self,event):
# cur_hour = datetime.datetime.now().hour
# print(cur_hour)
# if cur_hour>=8 or cur_hour<6:
# wx.MessageBox(message='''您错过了今天的签到时间,请明天再来\n
# 每天的签到时间是:6:00~7:59''', caption="警告")
# return
self.start_punchcard.Enable(False)
self.end_puncard.Enable(True)
self.loadDataBase(2)
threading.Thread(target=self.punchcard_cap,args=(event,)).start()
#_thread.start_new_thread(self.punchcard_cap,(event,))
pass
4.2 退出检测
退出检测实现代码:
def OnEndPunchCardClicked(self,event):
self.start_punchcard.Enable(True)
self.end_puncard.Enable(False)
pass
五.流动日志
5.1 打开日志
打开日志实现代码:
def OnOpenLogcatClicked(self,event):
self.loadDataBase(2)
#必须要变宽才能显示 scroll
self.SetSize(980,560)
grid = wx.grid.Grid(self,pos=(320,0),size=(920,560))
grid.CreateGrid(100, 4)
for i in range(100):
for j in range(4):
grid.SetCellAlignment(i,j,wx.ALIGN_CENTER,wx.ALIGN_CENTER)
grid.SetColLabelValue(0, "学号")
grid.SetColLabelValue(1, "姓名") #第一列标签
grid.SetColLabelValue(2, "电话号码")
grid.SetColLabelValue(3, "进入时间")
grid.SetColSize(0,120)
grid.SetColSize(1,120)
grid.SetColSize(2,150)
grid.SetColSize(3,150)
for i,id in enumerate(self.logcat_id):
grid.SetCellValue(i,0,str(id))
grid.SetCellValue(i,1,self.logcat_name[i])
grid.SetCellValue(i,2,"13308345090")
grid.SetCellValue(i,3,self.logcat_datetime[i])
5.2 关闭日志
关闭日志实现代码:
def OnCloseLogcatClicked(self,event):
self.SetSize(920,560)
self.initGallery()
pass