This repository was archived by the owner on Nov 25, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 197
/
Copy pathdataManage.py
324 lines (282 loc) · 13.2 KB
/
dataManage.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#!/usr/bin/env python3
# Author: winterssy <[email protected]>
import cv2
import numpy as np
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import QIcon, QTextCursor
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QTableWidgetItem, QAbstractItemView
from PyQt5.uic import loadUi
import logging
import logging.config
import os
import shutil
import sqlite3
import sys
import threading
import multiprocessing
from datetime import datetime
# 自定义数据库记录不存在异常
class RecordNotFound(Exception):
pass
class DataManageUI(QWidget):
logQueue = multiprocessing.Queue() # 日志队列
receiveLogSignal = pyqtSignal(str) # 日志信号
def __init__(self):
super(DataManageUI, self).__init__()
loadUi('./ui/DataManage.ui', self)
self.setWindowIcon(QIcon('./icons/icon.png'))
self.setFixedSize(931, 577)
# 设置tableWidget只读,不允许修改
self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
# 数据库
self.database = './FaceBase.db'
self.datasets = './datasets'
self.isDbReady = False
self.initDbButton.clicked.connect(self.initDb)
# 用户管理
self.queryUserButton.clicked.connect(self.queryUser)
self.deleteUserButton.clicked.connect(self.deleteUser)
# 直方图均衡化
self.isEqualizeHistEnabled = False
self.equalizeHistCheckBox.stateChanged.connect(
lambda: self.enableEqualizeHist(self.equalizeHistCheckBox))
# 训练人脸数据
self.trainButton.clicked.connect(self.train)
# 系统日志
self.receiveLogSignal.connect(lambda log: self.logOutput(log))
self.logOutputThread = threading.Thread(target=self.receiveLog, daemon=True)
self.logOutputThread.start()
# 是否执行直方图均衡化
def enableEqualizeHist(self, equalizeHistCheckBox):
if equalizeHistCheckBox.isChecked():
self.isEqualizeHistEnabled = True
else:
self.isEqualizeHistEnabled = False
# 初始化/刷新数据库
def initDb(self):
# 刷新前重置tableWidget
while self.tableWidget.rowCount() > 0:
self.tableWidget.removeRow(0)
try:
if not os.path.isfile(self.database):
raise FileNotFoundError
conn = sqlite3.connect(self.database)
cursor = conn.cursor()
res = cursor.execute('SELECT * FROM users')
for row_index, row_data in enumerate(res):
self.tableWidget.insertRow(row_index)
for col_index, col_data in enumerate(row_data):
self.tableWidget.setItem(row_index, col_index, QTableWidgetItem(str(col_data)))
cursor.execute('SELECT Count(*) FROM users')
result = cursor.fetchone()
dbUserCount = result[0]
except FileNotFoundError:
logging.error('系统找不到数据库文件{}'.format(self.database))
self.isDbReady = False
self.initDbButton.setIcon(QIcon('./icons/error.png'))
self.logQueue.put('Error:未发现数据库文件,你可能未进行人脸采集')
except Exception:
logging.error('读取数据库异常,无法完成数据库初始化')
self.isDbReady = False
self.initDbButton.setIcon(QIcon('./icons/error.png'))
self.logQueue.put('Error:读取数据库异常,初始化/刷新数据库失败')
else:
cursor.close()
conn.close()
self.dbUserCountLcdNum.display(dbUserCount)
if not self.isDbReady:
self.isDbReady = True
self.logQueue.put('Success:数据库初始化完成,发现用户数:{}'.format(dbUserCount))
self.initDbButton.setText('刷新数据库')
self.initDbButton.setIcon(QIcon('./icons/success.png'))
self.trainButton.setToolTip('')
self.trainButton.setEnabled(True)
self.queryUserButton.setToolTip('')
self.queryUserButton.setEnabled(True)
else:
self.logQueue.put('Success:刷新数据库成功,发现用户数:{}'.format(dbUserCount))
# 查询用户
def queryUser(self):
stu_id = self.queryUserLineEdit.text().strip()
conn = sqlite3.connect(self.database)
cursor = conn.cursor()
try:
cursor.execute('SELECT * FROM users WHERE stu_id=?', (stu_id,))
ret = cursor.fetchall()
if not ret:
raise RecordNotFound
face_id = ret[0][1]
cn_name = ret[0][2]
except RecordNotFound:
self.queryUserButton.setIcon(QIcon('./icons/error.png'))
self.queryResultLabel.setText('<font color=red>Error:此用户不存在</font>')
except Exception as e:
logging.error('读取数据库异常,无法查询到{}的用户信息'.format(stu_id))
self.queryResultLabel.clear()
self.queryUserButton.setIcon(QIcon('./icons/error.png'))
self.logQueue.put('Error:读取数据库异常,查询失败')
else:
self.queryResultLabel.clear()
self.queryUserButton.setIcon(QIcon('./icons/success.png'))
self.stuIDLineEdit.setText(stu_id)
self.cnNameLineEdit.setText(cn_name)
self.faceIDLineEdit.setText(str(face_id))
self.deleteUserButton.setEnabled(True)
finally:
cursor.close()
conn.close()
# 删除用户
def deleteUser(self):
text = '从数据库中删除该用户,同时删除相应人脸数据,<font color=red>该操作不可逆!</font>'
informativeText = '<b>是否继续?</b>'
ret = DataManageUI.callDialog(QMessageBox.Warning, text, informativeText, QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if ret == QMessageBox.Yes:
stu_id = self.stuIDLineEdit.text()
conn = sqlite3.connect(self.database)
cursor = conn.cursor()
try:
cursor.execute('DELETE FROM users WHERE stu_id=?', (stu_id,))
except Exception as e:
cursor.close()
logging.error('无法从数据库中删除{}'.format(stu_id))
self.deleteUserButton.setIcon(QIcon('./icons/error.png'))
self.logQueue.put('Error:读写数据库异常,删除失败')
else:
cursor.close()
conn.commit()
if os.path.exists('{}/stu_{}'.format(self.datasets, stu_id)):
try:
shutil.rmtree('{}/stu_{}'.format(self.datasets, stu_id))
except Exception as e:
logging.error('系统无法删除删除{}/stu_{}'.format(self.datasets, stu_id))
self.logQueue.put('Error:删除人脸数据失败,请手动删除{}/stu_{}目录'.format(self.datasets, stu_id))
text = '你已成功删除学号为 <font color=blue>{}</font> 的用户记录。'.format(stu_id)
informativeText = '<b>请在右侧菜单重新训练人脸数据。</b>'
DataManageUI.callDialog(QMessageBox.Information, text, informativeText, QMessageBox.Ok)
self.stuIDLineEdit.clear()
self.cnNameLineEdit.clear()
self.faceIDLineEdit.clear()
self.initDb()
self.deleteUserButton.setIcon(QIcon('./icons/success.png'))
self.deleteUserButton.setEnabled(False)
self.queryUserButton.setIcon(QIcon())
finally:
conn.close()
# 检测人脸
def detectFace(self, img):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
if self.isEqualizeHistEnabled:
gray = cv2.equalizeHist(gray)
face_cascade = cv2.CascadeClassifier('./haarcascades/haarcascade_frontalface_default.xml')
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.3, minNeighbors=5, minSize=(90, 90))
if (len(faces) == 0):
return None, None
(x, y, w, h) = faces[0]
return gray[y:y + w, x:x + h], faces[0]
# 准备图片数据
def prepareTrainingData(self, data_folder_path):
dirs = os.listdir(data_folder_path)
faces = []
labels = []
face_id = 1
conn = sqlite3.connect(self.database)
cursor = conn.cursor()
# 遍历人脸库
for dir_name in dirs:
if not dir_name.startswith('stu_'):
continue
stu_id = dir_name.replace('stu_', '')
try:
cursor.execute('SELECT * FROM users WHERE stu_id=?', (stu_id,))
ret = cursor.fetchall()
if not ret:
raise RecordNotFound
cursor.execute('UPDATE users SET face_id=? WHERE stu_id=?', (face_id, stu_id,))
except RecordNotFound:
logging.warning('数据库中找不到学号为{}的用户记录'.format(stu_id))
self.logQueue.put('发现学号为{}的人脸数据,但数据库中找不到相应记录,已忽略'.format(stu_id))
continue
subject_dir_path = data_folder_path + '/' + dir_name
subject_images_names = os.listdir(subject_dir_path)
for image_name in subject_images_names:
if image_name.startswith('.'):
continue
image_path = subject_dir_path + '/' + image_name
image = cv2.imread(image_path)
face, rect = self.detectFace(image)
if face is not None:
faces.append(face)
labels.append(face_id)
face_id = face_id + 1
cursor.close()
conn.commit()
conn.close()
return faces, labels
# 训练人脸数据
# Reference:https://github.com/informramiz/opencv-face-recognition-python
def train(self):
try:
if not os.path.isdir(self.datasets):
raise FileNotFoundError
text = '系统将开始训练人脸数据,界面会暂停响应一段时间,完成后会弹出提示。'
informativeText = '<b>训练过程请勿进行其它操作,是否继续?</b>'
ret = DataManageUI.callDialog(QMessageBox.Question, text, informativeText,
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if ret == QMessageBox.Yes:
face_recognizer = cv2.face.LBPHFaceRecognizer_create()
if not os.path.exists('./recognizer'):
os.makedirs('./recognizer')
faces, labels = self.prepareTrainingData(self.datasets)
face_recognizer.train(faces, np.array(labels))
face_recognizer.save('./recognizer/trainingData.yml')
except FileNotFoundError:
logging.error('系统找不到人脸数据目录{}'.format(self.datasets))
self.trainButton.setIcon(QIcon('./icons/error.png'))
self.logQueue.put('未发现人脸数据目录{},你可能未进行人脸采集'.format(self.datasets))
except Exception as e:
logging.error('遍历人脸库出现异常,训练人脸数据失败')
self.trainButton.setIcon(QIcon('./icons/error.png'))
self.logQueue.put('Error:遍历人脸库出现异常,训练失败')
else:
text = '<font color=green><b>Success!</b></font> 系统已生成./recognizer/trainingData.yml'
informativeText = '<b>人脸数据训练完成!</b>'
DataManageUI.callDialog(QMessageBox.Information, text, informativeText, QMessageBox.Ok)
self.trainButton.setIcon(QIcon('./icons/success.png'))
self.logQueue.put('Success:人脸数据训练完成')
self.initDb()
# 系统日志服务常驻,接收并处理系统日志
def receiveLog(self):
while True:
data = self.logQueue.get()
if data:
self.receiveLogSignal.emit(data)
else:
continue
# LOG输出
def logOutput(self, log):
time = datetime.now().strftime('[%Y/%m/%d %H:%M:%S]')
log = time + ' ' + log + '\n'
self.logTextEdit.moveCursor(QTextCursor.End)
self.logTextEdit.insertPlainText(log)
self.logTextEdit.ensureCursorVisible() # 自动滚屏
# 系统对话框
@staticmethod
def callDialog(icon, text, informativeText, standardButtons, defaultButton=None):
msg = QMessageBox()
msg.setWindowIcon(QIcon('./icons/icon.png'))
msg.setWindowTitle('OpenCV Face Recognition System - DataManage')
msg.setIcon(icon)
msg.setText(text)
msg.setInformativeText(informativeText)
msg.setStandardButtons(standardButtons)
if defaultButton:
msg.setDefaultButton(defaultButton)
return msg.exec()
if __name__ == '__main__':
logging.config.fileConfig('./config/logging.cfg')
app = QApplication(sys.argv)
window = DataManageUI()
window.show()
sys.exit(app.exec())