漢中門戶網(wǎng)工程招標(biāo)杭州seo網(wǎng)站哪家好
一、簡(jiǎn)介
這個(gè)人臉識(shí)別考勤簽到系統(tǒng)是基于大佬的人臉識(shí)別陌生人報(bào)警系統(tǒng)二次開(kāi)發(fā)的。
項(xiàng)目使用Python實(shí)現(xiàn),基于OpenCV框架進(jìn)行人臉識(shí)別和攝像頭硬件調(diào)用,同時(shí)也用OpenCV工具包處理圖片。交互界面使用pyqt5實(shí)現(xiàn)。
該系統(tǒng)實(shí)現(xiàn)了從學(xué)生信息輸入、人臉數(shù)據(jù)錄入、人臉數(shù)據(jù)訓(xùn)練,學(xué)生信息多條件搜索、修改,多選刪除,人臉數(shù)據(jù)訓(xùn)練,人臉識(shí)別、追蹤、簽到等完整流程的各項(xiàng)功能。甚至允許生成簽到表格和導(dǎo)出Excel格式簽到表。
根據(jù)功能分配,系統(tǒng)分為三個(gè)部分實(shí)現(xiàn)各部分流程,
- 錄入端負(fù)責(zé)數(shù)據(jù)導(dǎo)入,
- 管理端負(fù)責(zé)數(shù)據(jù)刪改查以及人臉數(shù)據(jù)訓(xùn)練,
- 監(jiān)控端負(fù)責(zé)人臉識(shí)別以及簽到功能。
二、效果圖
源碼下載地址
源碼下載地址https://download.csdn.net/download/u013749113/87897481
監(jiān)控端使用界面如圖所示:
管理端使用界面如圖所示:
錄入端使用界面如圖所示:
三、開(kāi)發(fā)過(guò)程
本系統(tǒng)在原始項(xiàng)目的框架基礎(chǔ)上做了大量修改,針對(duì)系統(tǒng)功能的不同以及部分模塊實(shí)現(xiàn)的不完整做了補(bǔ)充和優(yōu)化。原項(xiàng)目在功能上實(shí)現(xiàn)了完整的人臉數(shù)據(jù)錄入過(guò)程,并已經(jīng)存在數(shù)據(jù)管理、數(shù)據(jù)錄入、核心(Core)三個(gè)基本模塊。
監(jiān)控端修改部分
(1)人臉識(shí)別實(shí)現(xiàn)
在主要部分【核心】的實(shí)現(xiàn)上,原項(xiàng)目采用LBPH實(shí)現(xiàn)了人臉識(shí)別的基本功能,并使用haar-like實(shí)現(xiàn)人臉位置捕獲,使用dlib目標(biāo)追蹤器實(shí)現(xiàn)同一畫面下多個(gè)人臉的目標(biāo)追蹤,嘗試主要追蹤,次要捕獲的方式優(yōu)化人臉捕獲的過(guò)程。識(shí)別速度與畫面幀率很高,
但識(shí)別準(zhǔn)確率并不理想,并且嚴(yán)重受到光照條件影響?!緦?shí)際使用時(shí)證明,若錄入人臉數(shù)據(jù)時(shí)存在單方面光源照射,在識(shí)別時(shí),光源位置一旦改變就完全識(shí)別不出來(lái)了】
并且錄入數(shù)據(jù)時(shí)需要大量人臉數(shù)據(jù)集【100張人臉圖以上】,才能獲得較高的識(shí)別置信度。
二次開(kāi)發(fā)后,保留原本速度較快的LBPH人臉識(shí)別,新增效果更好的dlib_face_recognition_resnet_model深度學(xué)習(xí)殘差網(wǎng)絡(luò)識(shí)別模型。實(shí)際使用時(shí)發(fā)現(xiàn),雖然實(shí)時(shí)視頻流幀率明顯降低,但是識(shí)別準(zhǔn)確率大大提高。
(2)攝像頭啟動(dòng)與關(guān)閉
在原項(xiàng)目中,在攝像頭調(diào)用的設(shè)計(jì)上,錄入端和監(jiān)控端同樣都是用了攝像頭,并進(jìn)行人臉捕獲,但監(jiān)控端只允許打開(kāi)和關(guān)閉一次攝像頭,之后就禁用了攝像頭控制按鈕,而錄入端則允許隨意多次的打開(kāi)和關(guān)閉攝像頭。
其原因是具體實(shí)現(xiàn)上,因?yàn)楸O(jiān)控端需要啟動(dòng)單獨(dú)線程執(zhí)行人臉識(shí)別任務(wù),而錄入端沒(méi)有人臉識(shí)別的復(fù)雜處理過(guò)程,在一個(gè)線程中就可以實(shí)現(xiàn)需要的功能。監(jiān)控端開(kāi)啟攝像頭后,同時(shí)啟動(dòng)人臉識(shí)別線程處理單幀圖像,并啟動(dòng)報(bào)警監(jiān)聽(tīng)線程,設(shè)計(jì)思路上是報(bào)警線程一旦開(kāi)啟,全程保持監(jiān)聽(tīng),直到程序關(guān)閉,而關(guān)閉攝像頭時(shí)代碼實(shí)現(xiàn)上只關(guān)閉,也只能夠關(guān)閉人臉識(shí)別線程,對(duì)于報(bào)警監(jiān)聽(tīng)線程無(wú)法控制。導(dǎo)致如果容許攝像頭二次開(kāi)啟,人臉識(shí)別線程能夠啟動(dòng),但作為同一個(gè)線程實(shí)例的報(bào)警監(jiān)聽(tīng)模塊將被二次啟動(dòng),導(dǎo)致程序出錯(cuò)。
這里的人臉識(shí)別線程繼承自QTread類,允許使用.stop()函數(shù)控制線程實(shí)例的開(kāi)始與終止,而報(bào)警監(jiān)聽(tīng)模塊則是普通的threading.Thread(),開(kāi)啟后無(wú)法控制關(guān)閉,導(dǎo)致出現(xiàn)上述問(wèn)題。改進(jìn)方法是將報(bào)警監(jiān)聽(tīng)線程使用QTread實(shí)現(xiàn),使用與人臉識(shí)別線程相同的控制方法,攝像頭開(kāi)啟時(shí)啟動(dòng),攝像頭關(guān)閉時(shí)終止,實(shí)現(xiàn)二次啟動(dòng)功能。
(3)報(bào)警系統(tǒng)改為簽到系統(tǒng)
因?yàn)橄到y(tǒng)的功能修改前后有所改變,但是實(shí)現(xiàn)的技術(shù)實(shí)際上是一樣的。原始項(xiàng)目的報(bào)警功能實(shí)際上可以作為簽到系統(tǒng)的監(jiān)聽(tīng)線程。原始代碼邏輯為,發(fā)現(xiàn)置信度低于閾值的臉即陌生人臉,隨機(jī)人臉識(shí)別線程通過(guò)通信隊(duì)列告知報(bào)警系統(tǒng)執(zhí)行截圖拍攝、消息推送、報(bào)警響鈴等功能。改變?yōu)楹灥较到y(tǒng)后,人臉識(shí)別線程將置信度閾值以上【即數(shù)據(jù)庫(kù)中存在并認(rèn)為可信的臉】,且匹配度最高的人臉作為簽到信號(hào),通過(guò)隊(duì)列通信發(fā)送給簽到線程,并執(zhí)行之前沒(méi)有的數(shù)據(jù)庫(kù)錄入操作。
簡(jiǎn)單來(lái)說(shuō)就是,之前是把認(rèn)不出來(lái)的臉進(jìn)行記錄和報(bào)警,轉(zhuǎn)變?yōu)檎J(rèn)出來(lái)且最像的人臉進(jìn)行記錄和執(zhí)行聲音提示。
(4)簽到表格的創(chuàng)建
這個(gè)屬于簽到系統(tǒng)特有的新增功能,說(shuō)道考勤簽到就一定會(huì)想到教師用來(lái)點(diǎn)名的簽到表,因此該簽到系統(tǒng)需要生成一個(gè)包含當(dāng)前課程需要簽到的所有學(xué)生信息,通過(guò)預(yù)處理進(jìn)行創(chuàng)建,并在人臉識(shí)別過(guò)程中實(shí)時(shí)修改記錄簽到信息,主要內(nèi)容為學(xué)生姓名、學(xué)號(hào)、簽到時(shí)間。該功能內(nèi)建了一個(gè)基于MySQL查詢的QTableWidget控件,用于方便用戶之間從數(shù)據(jù)庫(kù)中選擇并創(chuàng)建需要簽到的學(xué)生名單。
管理端修改部分
(1)學(xué)生信息管理
原始的管理端非常簡(jiǎn)陋,存儲(chǔ)信息僅包含學(xué)生姓名、faceID、學(xué)號(hào)這樣的普通信息,想要查詢學(xué)生信息也只能進(jìn)行單人查詢,并且只能通過(guò)學(xué)號(hào),查詢的唯一目的就是通過(guò)學(xué)號(hào)刪除查詢結(jié)果。因此無(wú)論是查詢還是刪除都非常簡(jiǎn)陋。甚至根本不存在修改信息的功能。
修改后的管理端新增了多條件模糊查詢,并且大大增加了信息維數(shù),允許直接雙擊修改學(xué)生信息,并實(shí)時(shí)同步到數(shù)據(jù)庫(kù)中。同時(shí)刪除信息的功能也和信息查詢分離開(kāi)來(lái),信息查詢結(jié)果動(dòng)態(tài)顯示在QTableWidget控件中【與原項(xiàng)目使用的技術(shù)相同,只是做了更大的功能擴(kuò)展】。支持用戶多選刪除,不不必“查一個(gè),刪一個(gè)”。
(2)人臉數(shù)據(jù)訓(xùn)練
原項(xiàng)目中的人臉數(shù)據(jù)訓(xùn)練功能同樣集成在管理端中,原作者自定義了一個(gè)讀取數(shù)據(jù)集并將其與學(xué)生通過(guò)faceID進(jìn)行唯一匹配的函數(shù),也就是LBPH數(shù)據(jù)訓(xùn)練中的人臉數(shù)據(jù)(faces)與分類標(biāo)簽(labels)。讀取并整理成LBPH.train()所需要的數(shù)據(jù)結(jié)構(gòu)后即可直接將數(shù)據(jù)作為參數(shù)調(diào)用封裝好的函數(shù)進(jìn)行訓(xùn)練。
值得一提的是,LBPH的數(shù)據(jù)訓(xùn)練非???#xff0c;即使在三百人的,每人人臉數(shù)據(jù)集平均20張的情況下,訓(xùn)練時(shí)間依然能控制在十幾秒內(nèi)【但是結(jié)果其實(shí)并不好】,因此原作者并沒(méi)有將其作為單獨(dú)的線程來(lái)執(zhí)行,而是直接甩給用戶一個(gè)提示:訓(xùn)練期間系統(tǒng)窗口可能會(huì)無(wú)響應(yīng),請(qǐng)耐心等待。。。
這樣的結(jié)果是導(dǎo)致用戶體驗(yàn)極差,會(huì)以為程序崩了,其實(shí)只是訓(xùn)練計(jì)算時(shí)間過(guò)長(zhǎng),導(dǎo)致windows消息監(jiān)聽(tīng)一段時(shí)間內(nèi)無(wú)回應(yīng),被認(rèn)為是程序無(wú)響應(yīng)。。。
二次開(kāi)發(fā)后我將其獨(dú)立為一個(gè)線程單獨(dú)執(zhí)行,并為執(zhí)行函數(shù)增加了進(jìn)度條,讓用戶直觀的看到訓(xùn)練過(guò)程。順便,在代碼實(shí)現(xiàn)過(guò)程中,發(fā)現(xiàn)大量時(shí)間實(shí)際上消耗在圖片數(shù)據(jù)集的讀取上,訓(xùn)練的過(guò)程反倒沒(méi)有那么久,因此進(jìn)度條實(shí)際展示的是讀取過(guò)程,邏輯上是先進(jìn)行的數(shù)據(jù)讀取,再計(jì)算特征值,然后繼續(xù)讀取下一個(gè)數(shù)據(jù)集,因此將數(shù)據(jù)讀取作為進(jìn)度來(lái)衡量不會(huì)有時(shí)間上的偏差。
三、代碼具體實(shí)現(xiàn)
這里就簡(jiǎn)單貼一下數(shù)據(jù)錄入端的代碼。項(xiàng)目完整代碼還請(qǐng)移步本文開(kāi)始位置的鏈接。
#!/usr/bin/env python3
# Author: kuronekonano <god772525182@gmail.com>
# 人臉信息錄入
import re
import string
import timeimport cv2
import pymysql
import shutilfrom PyQt5.QtCore import QTimer, QRegExp, pyqtSignal, QThread
from PyQt5.QtGui import QImage, QPixmap, QIcon, QRegExpValidator, QTextCursor
from PyQt5.QtWidgets import QDialog, QApplication, QWidget, QMessageBox, QFileDialog, QProgressBar
from PyQt5.uic import loadUiimport logging
import logging.config
import queue
import threading
import os
import sys
import xlrd
import randomfrom datetime import datetime# 用戶取消了更新數(shù)據(jù)庫(kù)操作
class OperationCancel(Exception):pass# 采集過(guò)程中出現(xiàn)干擾
class RecordDisturbance(Exception):passclass DataRecordUI(QWidget):receiveLogSignal = pyqtSignal(str)messagebox_signal = pyqtSignal(dict)# 日志隊(duì)列logQueue = queue.Queue()def __init__(self):super(DataRecordUI, self).__init__()loadUi('./ui/DataRecord.ui', self) # 讀取UI布局self.setWindowIcon(QIcon('./icons/icon.png'))self.setFixedSize(1528, 856)# OpenCV# 攝像頭self.cap = cv2.VideoCapture()# 分類器self.faceCascade = cv2.CascadeClassifier('./haarcascades/haarcascade_frontalface_default.xml')# 圖像捕獲self.isExternalCameraUsed = Falseself.useExternalCameraCheckBox.stateChanged.connect(lambda: self.useExternalCamera(self.useExternalCameraCheckBox))self.startWebcamButton.toggled.connect(self.startWebcam)self.startWebcamButton.setCheckable(True)# 定時(shí)器self.timer = QTimer(self)self.timer.timeout.connect(self.updateFrame)# 人臉檢測(cè)self.isFaceDetectEnabled = Falseself.enableFaceDetectButton.toggled.connect(self.enableFaceDetect)self.enableFaceDetectButton.setCheckable(True)# 數(shù)據(jù)庫(kù)# self.database = 'users'self.datasets = './datasets'self.isDbReady = Falseself.initDbButton.setIcon(QIcon('./icons/warning.png'))self.initDbButton.clicked.connect(self.initDb)# 用戶信息self.isUserInfoReady = Falseself.userInfo = {'stu_id': '','cn_name': '','en_name': '','stu_grade': '','stu_class': '','stu_sex': '','major': ''}self.addOrUpdateUserInfoButton.clicked.connect(self.addOrUpdateUserInfo)self.migrateToDbButton.clicked.connect(self.migrateToDb) # 插入新數(shù)據(jù)按鍵綁定# 人臉采集self.startFaceRecordButton.clicked.connect(lambda: self.startFaceRecord(self.startFaceRecordButton)) # 開(kāi)始人臉采集按鈕綁定,并傳入按鈕本身用于結(jié)束狀態(tài)控制# self.startFaceRecordButton.setCheckable(True)self.faceRecordCount = 0 # 已采集照片計(jì)數(shù)器self.minFaceRecordCount = 100 # 最少采集照片數(shù)量self.isFaceDataReady = Falseself.isFaceRecordEnabled = Falseself.enableFaceRecordButton.clicked.connect(self.enableFaceRecord) # 按鍵綁定錄入單幀圖像# 日志系統(tǒng)self.receiveLogSignal.connect(lambda log: self.logOutput(log)) # pyqtsignal信號(hào)綁定self.messagebox_signal.connect(lambda log: self.message_output(log))self.logOutputThread = threading.Thread(target=self.receiveLog, daemon=True)self.logOutputThread.start()# 批量導(dǎo)入self.isImage_path_ready = False# self.ImagepathButton.clicked.connect(self.import_images_data) # 使用同一線程會(huì)導(dǎo)致窗口無(wú)響應(yīng)self.ImagepathButton.clicked.connect(self.import_image_thread) # 使用多線程實(shí)現(xiàn)圖片導(dǎo)入self.isExcel_path_ready = Falseself.ExcelpathButton.clicked.connect(self.import_excel_data)self.ImportPersonButton.clicked.connect(self.person_import_thread)@staticmethoddef connect_to_sql():conn = pymysql.connect(host='localhost',user='root',password='******',db='mytest',port=3306,charset='utf8')cursor = conn.cursor()return conn, cursor# 單人導(dǎo)入圖片集【主線程】棄用def import_person_imageset(self):if self.isUserInfoReady: # 學(xué)生信息確認(rèn)stu_id = self.userInfo.get('stu_id')self.ImportPersonButton.setIcon(QIcon('./icons/success.png'))image_paths = QFileDialog.getOpenFileNames(self, '選擇圖片',"./",'JEPG files(*.jpg);;PNG files(*.PNG)')if not os.path.exists('{}/stu_{}'.format(self.datasets, stu_id)):os.makedirs('{}/stu_{}'.format(self.datasets, stu_id))image_paths = image_paths[0]for index, path in enumerate(image_paths):try:img = cv2.imread(path)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度圖faces = self.faceCascade.detectMultiScale(gray, 1.3, 5, minSize=(90, 90)) # 分類器偵測(cè)人臉if len(faces) == 0:self.logQueue.put('圖片{}中沒(méi)有檢測(cè)到人臉!'.format(path))continuefor (x, y, w, h) in faces:if len(faces) > 1:raise RecordDisturbancecv2.imwrite('{}/stu_{}/img.{}-{}.jpg'.format(self.datasets, stu_id, index, ''.join(random.sample(string.ascii_letters + string.digits, 4))),img[y - 20:y + h + 20, x - 20:x + w + 20]) # 灰度圖的人臉區(qū)域except RecordDisturbance:logging.error('檢測(cè)到多張人臉或環(huán)境干擾')self.logQueue.put('Warning:檢測(cè)到圖片{}存在多張人臉或環(huán)境干擾,已忽略。'.format(path))continueexcept Exception as e:logging.error('寫入人臉圖像文件到計(jì)算機(jī)過(guò)程中發(fā)生異常')self.logQueue.put('Error:無(wú)法保存人臉圖像,導(dǎo)入該圖片失敗')print(e)self.migrateToDbButton.setEnabled(True) # 允許提交至數(shù)據(jù)庫(kù)self.isFaceDataReady = Trueelse:self.ImportPersonButton.setIcon(QIcon('./icons/error.png'))self.ImportPersonButton.setChecked(False)self.logQueue.put('Error:操作失敗,系統(tǒng)未檢測(cè)到有效的用戶信息')# 表格導(dǎo)入學(xué)生信息def import_excel_data(self):excel_paths = QFileDialog.getOpenFileNames(self, '選擇表格',"./",'EXCEL 文件 (*.xlsx;*.xls;*.xlm;*.xlt;*.xlsm;*.xla)')excel_paths = excel_paths[0]conn, cursor = self.connect_to_sql()error_count = 0for path in excel_paths:sheets_file = xlrd.open_workbook(path)for index, sheet in enumerate(sheets_file.sheets()):self.logQueue.put("正在讀取文件:" + str(path) + "的第" + str(index) + "個(gè)sheet表的內(nèi)容...")for row in range(sheet.nrows):row_data = sheet.row_values(row)if row_data[1] == '姓名':continueself.userInfo['stu_id'] = row_data[4]self.userInfo['cn_name'] = row_data[1]self.userInfo['en_name'] = row_data[0]self.userInfo['stu_grade'] = '20' + self.userInfo['stu_id'][:2]self.userInfo['stu_class'] = row_data[3].rsplit('-', 1)[1]self.userInfo['stu_sex'] = row_data[5]self.userInfo['major'] = row_data[2]self.userInfo['province'] = row_data[-1]self.userInfo['nation'] = row_data[-2]# print(self.userInfo)try:stu_id = row_data[4]if not os.path.exists('{}/stu_{}'.format(self.datasets, stu_id)):os.makedirs('{}/stu_{}'.format(self.datasets, stu_id))db_user_count = self.commit_to_database(cursor)self.dbUserCountLcdNum.display(db_user_count) # 數(shù)據(jù)庫(kù)人數(shù)計(jì)數(shù)器except OperationCancel:passexcept Exception as e:print(e)logging.error('讀寫數(shù)據(jù)庫(kù)異常,無(wú)法向數(shù)據(jù)庫(kù)插入/更新記錄')self.logQueue.put('Error:讀寫數(shù)據(jù)庫(kù)異常,同步失敗')error_count += 1self.logQueue.put('導(dǎo)入完畢!其中導(dǎo)入失敗 {} 條信息'.format(error_count))cursor.close()conn.commit()conn.close()# 啟用新線程導(dǎo)入圖片,并添加進(jìn)度條def import_image_thread(self):self.image_paths = QFileDialog.getOpenFileNames(self, '選擇圖片',"./",'JEPG files(*.jpg);;PNG files(*.PNG)')self.image_paths = self.image_paths[0]if len(self.image_paths) != 0: # 點(diǎn)擊導(dǎo)入但是沒(méi)有選擇文件時(shí)不需啟動(dòng)線程progress_bar = ActionsImportImage(self)print('import success!')# 啟用新線程 單人圖片導(dǎo)入 使用進(jìn)度條def person_import_thread(self):if self.isUserInfoReady: # 學(xué)生信息確認(rèn)stu_id = self.userInfo.get('stu_id')self.ImportPersonButton.setIcon(QIcon('./icons/success.png'))image_paths = QFileDialog.getOpenFileNames(self, '選擇圖片',"./",'JEPG files(*.jpg);;PNG files(*.PNG)')self.image_paths = image_paths[0]if len(self.image_paths) != 0: # 點(diǎn)擊導(dǎo)入但是沒(méi)有選擇文件時(shí)不需啟動(dòng)線程if not os.path.exists('{}/stu_{}'.format(self.datasets, stu_id)):os.makedirs('{}/stu_{}'.format(self.datasets, stu_id))progress_bar = ActionsPersonImport(self)self.migrateToDbButton.setEnabled(True) # 允許提交至數(shù)據(jù)庫(kù)self.isFaceDataReady = Trueelse:self.ImportPersonButton.setIcon(QIcon('./icons/error.png'))self.ImportPersonButton.setChecked(False)self.logQueue.put('Error:操作失敗,系統(tǒng)未檢測(cè)到有效的用戶信息')# 圖片批量導(dǎo)入【主線程】棄用def import_images_data(self):image_paths = QFileDialog.getOpenFileNames(self, '選擇圖片',"./",'JEPG files(*.jpg);;PNG files(*.PNG)')image_paths = image_paths[0]error_count = 0self.logQueue.put('開(kāi)始讀取圖片數(shù)據(jù)...')for index, path in enumerate(image_paths):stu_id = os.path.split(path)[1].split('.')[0]# print(stu_id)if not os.path.exists('{}/stu_{}'.format(self.datasets, stu_id)):text = '命名錯(cuò)誤!'informativeText = '<b>文件 <font color=red>{}</font> 存在問(wèn)題,數(shù)據(jù)庫(kù)中沒(méi)有以該圖片名為學(xué)號(hào)的用戶。</b>'.format(path)DataRecordUI.callDialog(QMessageBox.Critical, text, informativeText, QMessageBox.Ok)error_count += 1continuedstpath = '{}/stu_{}/img.{}.jpg'.format(self.datasets, stu_id, stu_id + '-0')try:shutil.copy(path, dstpath)except:text = '命名格式錯(cuò)誤!'informativeText = '<b>文件 <font color=red>{}</font> 命名格式不正確。</b>'.format(path)DataRecordUI.callDialog(QMessageBox.Critical, text, informativeText, QMessageBox.Ok)error_count += 1self.logQueue.put('圖片批量導(dǎo)入完成!其中導(dǎo)入失敗 {} 張圖片'.format(error_count))# 是否使用外接攝像頭def useExternalCamera(self, useExternalCameraCheckBox):if useExternalCameraCheckBox.isChecked():self.isExternalCameraUsed = Trueelse:self.isExternalCameraUsed = False# 打開(kāi)/關(guān)閉攝像頭def startWebcam(self, status):if status:if not self.cap.isOpened():camID = 1 if self.isExternalCameraUsed else 0 + cv2.CAP_DSHOWself.cap.open(camID)self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)ret, frame = self.cap.read() # 獲取攝像頭調(diào)用結(jié)果if not ret:logging.error('無(wú)法調(diào)用電腦攝像頭{}'.format(camID))self.logQueue.put('Error:初始化攝像頭失敗')self.cap.release()self.startWebcamButton.setIcon(QIcon('./icons/error.png'))self.startWebcamButton.setChecked(False)else:self.timer.start(5)self.enableFaceDetectButton.setEnabled(True)self.startWebcamButton.setIcon(QIcon('./icons/success.png'))self.startWebcamButton.setText('關(guān)閉攝像頭')else:if self.cap.isOpened():if self.timer.isActive():self.timer.stop()self.cap.release()self.faceDetectCaptureLabel.clear()self.faceDetectCaptureLabel.setText('<font color=red>攝像頭未開(kāi)啟</font>')self.startWebcamButton.setText('打開(kāi)攝像頭')self.enableFaceDetectButton.setEnabled(False)self.startWebcamButton.setIcon(QIcon())# 開(kāi)啟/關(guān)閉人臉檢測(cè)def enableFaceDetect(self, status):if self.cap.isOpened():if status:self.enableFaceDetectButton.setText('關(guān)閉人臉檢測(cè)')self.isFaceDetectEnabled = Trueelse:self.enableFaceDetectButton.setText('開(kāi)啟人臉檢測(cè)')self.isFaceDetectEnabled = False# 采集當(dāng)前捕獲幀def enableFaceRecord(self):if not self.isFaceRecordEnabled:self.isFaceRecordEnabled = True# 開(kāi)始/結(jié)束采集人臉數(shù)據(jù)def startFaceRecord(self, startFaceRecordButton):if startFaceRecordButton.text() == '開(kāi)始采集人臉數(shù)據(jù)': # 只能用==判斷,不能用isif self.isFaceDetectEnabled:if self.isUserInfoReady: # 學(xué)生信息確認(rèn)self.addOrUpdateUserInfoButton.setEnabled(False) # 采集人臉數(shù)據(jù)時(shí)禁用修改學(xué)生信息if not self.enableFaceRecordButton.isEnabled(): # 啟用單幀采集按鈕self.enableFaceRecordButton.setEnabled(True)self.enableFaceRecordButton.setIcon(QIcon())self.startFaceRecordButton.setIcon(QIcon('./icons/success.png'))self.startFaceRecordButton.setText('結(jié)束當(dāng)前人臉采集') # 開(kāi)始采集按鈕狀態(tài)修改為結(jié)束采集else:self.startFaceRecordButton.setIcon(QIcon('./icons/error.png'))self.startFaceRecordButton.setChecked(False)self.logQueue.put('Error:操作失敗,系統(tǒng)未檢測(cè)到有效的用戶信息')else:self.startFaceRecordButton.setIcon(QIcon('./icons/error.png'))self.logQueue.put('Error:操作失敗,請(qǐng)開(kāi)啟人臉檢測(cè)')else: # 根據(jù)按鈕文本信息判斷是結(jié)束采集還是開(kāi)始采集if self.faceRecordCount < self.minFaceRecordCount:text = '系統(tǒng)當(dāng)前采集了 <font color=blue>{}</font> 幀圖像,采集數(shù)據(jù)過(guò)少會(huì)導(dǎo)致較大的識(shí)別誤差。'.format(self.faceRecordCount)informativeText = '<b>請(qǐng)至少采集 <font color=red>{}</font> 幀圖像。</b>'.format(self.minFaceRecordCount)DataRecordUI.callDialog(QMessageBox.Information, text, informativeText, QMessageBox.Ok)else:text = '系統(tǒng)當(dāng)前采集了 <font color=blue>{}</font> 幀圖像,繼續(xù)采集可以提高識(shí)別準(zhǔn)確率。'.format(self.faceRecordCount)informativeText = '<b>你確定結(jié)束當(dāng)前人臉采集嗎?</b>'ret = DataRecordUI.callDialog(QMessageBox.Question, text, informativeText,QMessageBox.Yes | QMessageBox.No,QMessageBox.No)if ret == QMessageBox.Yes:self.isFaceDataReady = True # 結(jié)束采集,人臉數(shù)據(jù)準(zhǔn)備完畢if self.isFaceRecordEnabled:self.isFaceRecordEnabled = Falseself.enableFaceRecordButton.setEnabled(False) # 結(jié)束采集,單幀采集按鈕禁用self.enableFaceRecordButton.setIcon(QIcon())self.startFaceRecordButton.setText('開(kāi)始采集人臉數(shù)據(jù)') # 修改按鈕文本為開(kāi)始狀態(tài)self.startFaceRecordButton.setEnabled(False) # 不可重新開(kāi)始采集self.startFaceRecordButton.setIcon(QIcon())self.migrateToDbButton.setEnabled(True) # 允許提交至數(shù)據(jù)庫(kù)# 定時(shí)器,實(shí)時(shí)更新畫面def updateFrame(self):ret, frame = self.cap.read()# frame = cv2.flip(frame, 1) # 水平翻轉(zhuǎn)圖片if ret:# self.displayImage(frame) # ?兩次輸出?if self.isFaceDetectEnabled: # 人臉檢測(cè)detected_frame = self.detectFace(frame)self.displayImage(detected_frame)else:self.displayImage(frame)# 檢測(cè)人臉def detectFace(self, frame):gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 灰度圖faces = self.faceCascade.detectMultiScale(gray, 1.3, 5, minSize=(90, 90)) # 分類器偵測(cè)人臉# 1.image為輸入的灰度圖像# 2.objects為得到被檢測(cè)物體的矩形框向量組# 3.scaleFactor為每一個(gè)圖像尺度中的尺度參數(shù),默認(rèn)值為1.1。scale_factor參數(shù)可以決定兩個(gè)不同大小的窗口掃描之間有多大的跳躍,# 這個(gè)參數(shù)設(shè)置的大,則意味著計(jì)算會(huì)變快,但如果窗口錯(cuò)過(guò)了某個(gè)大小的人臉,則可能丟失物體。# 4.minNeighbors參數(shù)為每一個(gè)級(jí)聯(lián)矩形應(yīng)該保留的鄰近個(gè)數(shù),默認(rèn)為3。# minNeighbors控制著誤檢測(cè),默認(rèn)值為3表明至少有3次重疊檢測(cè),我們才認(rèn)為人臉確實(shí)存。# 6.cvSize()指示尋找人臉的最小區(qū)域。設(shè)置這個(gè)參數(shù)過(guò)大,會(huì)以丟失小物體為代價(jià)減少計(jì)算量。stu_id = self.userInfo.get('stu_id')# 遍歷所有人臉,只允許有一個(gè)人的臉for (x, y, w, h) in faces:if self.isFaceRecordEnabled:try: # 創(chuàng)建學(xué)號(hào)對(duì)應(yīng)的圖片數(shù)據(jù)集if not os.path.exists('{}/stu_{}'.format(self.datasets, stu_id)):os.makedirs('{}/stu_{}'.format(self.datasets, stu_id))if len(faces) > 1:raise RecordDisturbancecv2.imwrite('{}/stu_{}/img.{}.jpg'.format(self.datasets, stu_id, self.faceRecordCount + 1),frame[y - 20:y + h + 20, x - 20:x + w + 20]) # 灰度圖的人臉區(qū)域except RecordDisturbance:self.isFaceRecordEnabled = Falselogging.error('檢測(cè)到多張人臉或環(huán)境干擾')self.logQueue.put('Warning:檢測(cè)到多張人臉或環(huán)境干擾,請(qǐng)解決問(wèn)題后繼續(xù)')self.enableFaceRecordButton.setIcon(QIcon('./icons/warning.png'))continueexcept Exception as e:logging.error('寫入人臉圖像文件到計(jì)算機(jī)過(guò)程中發(fā)生異常')self.enableFaceRecordButton.setIcon(QIcon('./icons/error.png'))self.logQueue.put('Error:無(wú)法保存人臉圖像,采集當(dāng)前捕獲幀失敗')else:self.enableFaceRecordButton.setIcon(QIcon('./icons/success.png'))self.faceRecordCount = self.faceRecordCount + 1self.isFaceRecordEnabled = False # 單幀拍攝完成后馬上關(guān)閉self.faceRecordCountLcdNum.display(self.faceRecordCount) # 更新采集數(shù)量cv2.rectangle(frame, (x - 5, y - 10), (x + w + 5, y + h + 10), (0, 0, 255), 2) # 紅色追蹤框return frame
源碼下載地址
源碼下載地址https://download.csdn.net/download/u013749113/87897481