案例引入
首先简要讲解数据集训练生成模型的原理,这里使用的是LBPH算法,在OpenCV模块中已经有内嵌的方法cv2.face.LBPHFaceRecognizer_create(),为了方便小伙伴们读懂之后的代码,在这里先举一个简单的人脸模型训练的小案例。
第一步:采集人脸数据,网络上有许多案例Demo,不再赘述,代码如下:
import cv2
detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
cap = cv2.VideoCapture(0)
sampleNum = 0
#输入人脸图像数据类别
Id = input('enter your id: ')
while True:
ret, img = cap.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = 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), 2)
sampleNum = sampleNum + 1
#命名规则为User.[ID].[SampleNumber].jpg
#如果是2号人的第十张照片,我们可以将它命名为User.2.10.jpg
cv2.imwrite("dataSet/User." + str(Id) + '.' + str(sampleNum) + ".jpg", gray[y:y + h, x:x + w]) #
cv2.imshow('frame', img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
elif sampleNum > 20:
break
cap.release()
cv2.destroyAllWindows()
采集效果 如下:(ps:图来自原作者)
第二步:使用OpenCV中LBPH算法的方法建立人脸数据模型,代码如下:
import cv2
import os
import numpy as np
from PIL import Image
#初始化识别器和人脸检测器
'''
如果face.LBPHFaceRecognizer_create或createLBPHFaceRecognizer显示不存在
则需要下载opencv-contrib-python pip install opencv-contrib-python
'''
# recognizer = cv2.createLBPHFaceRecognizer()
detector = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
recognizer = cv2.face.LBPHFaceRecognizer_create()
'''
遍历图片路径,导入图片和id,添加到list
'''
def get_images_and_labels(path):
image_paths = [os.path.join(path, f) for f in os.listdir(path)]
face_samples = []
ids = []
for image_path in image_paths:
#灰度图片
image = Image.open(image_path).convert('L')
#将图片转换成了Numpy数组
image_np = np.array(image, 'uint8')
#为了获取到id,我们将图片的路径分裂一下并获取相关信息
if os.path.split(image_path)[-1].split(".")[-1] != 'jpg':
continue
image_id = int(os.path.split(image_path)[-1].split(".")[1])
faces = detector.detectMultiScale(image_np)
#将图片和id都添加在list中
for (x, y, w, h) in faces:
face_samples.append(image_np[y:y + h, x:x + w])
ids.append(image_id)
return face_samples, ids
#让LBPH识别器去训练
faces, Ids = get_images_and_labels('dataSet')
recognizer.train(faces, np.array(Ids))
recognizer.save('trainner.yml')
运行程序即可便捷快速生成模型文件“trainner.yml”, 打开模型文件,可以看到人脸数据信息,如下图所示:
本节项目
接下来看看本节训练人脸识别模型小案例吧,只不过是在上面代码的基础添加了图像预处理、数据库操作和GUI操作而已,导入第二节采集到的人脸数据,点击训练即可,当人脸数据类别较多时,可以使用数据库进行查询或者删除操作,效果如下:
确定无误后即可训练模型,效果如下:
训练仅需几秒即可,训练过程中程序会暂停响应,训练成功后就会生成所需模型,大功告成~
最后分享本节实现代码~
#!/usr/bin/env python3
#CSDN:https://blog.csdn.net/m0_38106923
#邮箱:[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
from PyQt5.QtWidgets import QTableWidgetItem, QAbstractItemView
from PyQt5.uic import loadUi
###########
###########
from datetime import datetime
#from ui import DataManage
# 自定义数据库记录不存在异常
class RecordNotFound(Exception):
pass
class DataManageUI(QWidget):
logQueue = multiprocessing.Queue() # 日志队列
receiveLogSignal = pyqtSignal(str) # 日志信号
def __init__(self):
super(DataManageUI, self).__init__()
loadUi('./ui/GUI/DataManage.ui', self)
self.setWindowIcon(QIcon('./icons/icon.png'))
self.setFixedSize(999, 614)
# 设置tableWidget只读,不允许修改
self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
# 数据库
self.database = './Facedata/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
# 训练人脸数据
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> 系统已生成g:/Practice/spyder/python/Python-OpenCv-AI/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('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())
参考文章:
1.https://blog.csdn.net/m0_38106923/article/details/88879876