最近在做一個人臉識別考勤系統(tǒng),已經(jīng)總結(jié)和記錄了大部分內(nèi)容,算是比較完善啦!后續(xù)把剩下的搞完,感興趣的同學(xué)可以關(guān)注一下哦~ 文末給出了代碼獲取方式,請自行獲取食用~
B站:馬上就更?。。bilibili
CSDN:使用python和pyqt5輕松上手人臉識別系統(tǒng)(含代碼)_百年后封筆-CSDN博客
公眾號:百年后封筆
一、 環(huán)境配置
這里我們使用的環(huán)境主要包括一下三個方面:
- 首先是開發(fā)所需要的
python環(huán)境、包管理工具和IDE
等。這里我們主要需要安裝的就是anaconda
和pycharm專業(yè)版
,以及pip鏡像源
。需要注意的是,pycharm專業(yè)版是付費(fèi)軟件,如果你不想后續(xù)查看數(shù)據(jù)庫的詳細(xì)信息等,那么社區(qū)版也足夠了,但專業(yè)版是可以通過學(xué)生作科研用途申請的。 - 本項目使用到了
sqlite3
數(shù)據(jù)庫,但我們最好也配置mysql
數(shù)據(jù)庫,然后方便在pycharm專業(yè)版中查看數(shù)據(jù)庫的表和信息。 - 最后是人臉識別和開發(fā)過程中依賴的python包,后面我們展示了幾種使用python進(jìn)行人臉識別的方法,除了
opencv-python
,其他的基本都需要dlib
這個庫,后面我會說具體所依賴的庫,不過對于No Module Error
的情況,直接pip install xxx
就也可以輕松解決。
1.1 python環(huán)境配置
這里我們只提供windows下python環(huán)境的配置方法,其實(shí)都大同小異。
1.1.1 安裝 anaconda
anaconda是一個python的包管理軟件,可以方便的管理你的虛擬環(huán)境
和依賴的包
-
首先去官網(wǎng)下載安裝包
-
按照要求一步步進(jìn)行安裝即可,一直next就行。如果不清楚怎么安裝的話,可以直接搜一下
anaconda的安裝方法
,參考其他博主的詳細(xì)安裝指導(dǎo),這里我們不再贅述。 -
檢查anaconda是否安裝成功
打開cmd,輸入conda -V
,一般會有兩種情況,下面這種就是安裝好了的。
如果報錯了,那么需要你將anaconda的安裝路徑先找到,比如,是在C:\Users\xxx\anaconda3
,那么接著你就需要在你的系統(tǒng)環(huán)境變量里面,把下面幾個路徑加入到path
里面,也就是把a(bǔ)naconda的bin
和Scripts
路徑加入到環(huán)境變量里面去,如下:
C:\Users\xxx\anaconda3\bin
C:\Users\xmhh\anaconda3\Scripts
C:\Users\xmhh\anaconda3
操作完之后,再檢驗(yàn)一下conda有沒有安裝好就行了,一般出問題就是環(huán)境變量沒加入進(jìn)去
,其他沒啥問題。
- 下面羅列一些常用的conda命令
# 建立新環(huán)境
conda create -n new env_name python=3.8
# conda初始化
conda init
# 激活虛擬環(huán)境
conda activate env_name 或者 activate env_name
# 查看虛擬環(huán)境
conda env list
# 刪除虛擬環(huán)境
conda remove -n env_name --all
1.1.2 安裝pycharm
- 去官網(wǎng)下載安裝包
- 需要注意的是,左邊專業(yè)版安裝包可以免費(fèi)用30天,或者可以認(rèn)證一下教育優(yōu)惠延期用,右邊社區(qū)版是免費(fèi)的。專業(yè)版可以查看數(shù)據(jù)庫,遠(yuǎn)程連接等,功能更加全面,社區(qū)版也勉強(qiáng)夠用吧。
- 安裝的話,就也是一直next就行了,沒什么好說的。好了并打開項目后的效果如下:
1.1.3 配置pip源
由于我們后續(xù)需要配置虛擬環(huán)境,安裝相關(guān)依賴包,國內(nèi)下載python非常慢,因此我們需要先配置一下pip鏡像源,具體做法如下:
- 在
C:\Users\xxx
中創(chuàng)建一個名為pip
的文件夾,然后在里面創(chuàng)建一個pip.ini
文件,注意后續(xù)需要修改這個文件內(nèi)容,因此可以先把名字改成pip.txt
,后續(xù)再改成pip.ini
- 修改
pip.ini
文件內(nèi)容,然后保存就可以了,如下:如果還不是很明白的,可以參考一下其他博主的,例如這個教程
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
- 后面pip install
1.2 mysql數(shù)據(jù)庫安裝
mysql數(shù)據(jù)庫很多時候會用到,所以最好是安裝配置一下,下面給一個大致的安裝教程
-
下載mysql數(shù)據(jù)庫的安裝包
如上圖,選第一個就行啦,解壓之后,就ok,如下圖,最開始是沒有這個my.ini
和data
的好像,不過不重要,官網(wǎng)下下來解壓就完事了,后面再配
。 -
配置環(huán)境變量,也就是mysql文件夾下的
bin
文件夾 -
在mysqll文件根目錄下創(chuàng)建并配置
my.ini
my.ini
的配置信息如下,注意把里面的路徑設(shè)置成你自己的就可以了,其他不用改。
[mysqld]
explicit_defaults_for_timestamp=true
character-set-server=utf8mb4
#綁定IPv4和3306端口
bind-address = 0.0.0.0
port = 3306
sql_mode="STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"
default_storage_engine=innodb
innodb_buffer_pool_size=1000M
innodb_log_file_size=50M
# 設(shè)置mysql的安裝目錄
basedir=G:\Program Files\mysql
# 設(shè)置mysql數(shù)據(jù)庫的數(shù)據(jù)的存放目錄
datadir=G:\Program Files\mysql\data
# 允許最大連接數(shù)
max_connections=200
# skip_grant_tables
[mysql]
default-character-set=utf8mb4
[mysql.server]
default-character-set=utf8mb4
[mysql_safe]
default-character-set=utf8mb4
[client]
port = 3306
default-character-set=utf8mb4
plugin-dir=G:\Program Files\mysql\lib\plugin
- 打開
cmd
,輸入mysqld --initialize
完成初始化。在根目錄就出現(xiàn)了data
文件夾,這下就齊活了。注意初始化的時候,會有一個初始密碼,記得記一下,后面登錄和修改密碼需要用到。 - 相關(guān)使用
# 安裝mysql服務(wù)
mysqld -install
# 啟動mysql服務(wù)
net start mysql
# 登錄數(shù)據(jù)庫
mysql -u root -p
# 修改 root 密碼為 root123
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root123';
1.3 相關(guān)依賴安裝
這里我們介紹一下本次項目所需要的依賴和環(huán)境吧。這里我們主要需要用到:首先,numpy
和pandas
是數(shù)據(jù)處理的基本庫;其次,一些圖像處理的python庫,cv2
和Pillow
;接著,gui的庫,pyqt5
;最后人臉識別的庫,cmake
, dlib
和face_recognition
?;旧暇褪沁@些吧,如果有問題的話,大家根據(jù)報錯再安裝吧。下面給出一般配置步驟:
- 首先在你的項目路徑下
cmd
中使用conda
命令創(chuàng)建虛擬環(huán)境,然后激活一下
# 創(chuàng)建虛擬環(huán)境 名字是face_login python版本是3.8
conda create -n new face_login python=3.8
# 激活虛擬環(huán)境
activate face_login 或者 conda activate face_login
- 在你的項目路徑下創(chuàng)建一個文件名叫做
requirements.txt
,在里面填寫如下信息,版本上似乎是沒有什么大的問題的,按順序裝就可以了,dlib
裝起來可能會有些慢如果嫌太慢的話,也可以最后單獨(dú)使用pip install dlib
去安裝,但是需要先pip
安裝cmake
包,不然會出錯的。
numpy
pandas
pyqt5
cmake
opencv-python
Pillow
dlib
face_recognition
- 運(yùn)行
pip install -r requirements.txt
安裝依賴
二、 人臉識別模塊測試
作為人臉識別系統(tǒng)中最重要的模塊,我們需要首先對人臉識別的功能進(jìn)行測試,然后將其作為一個模塊嵌入到我們的系統(tǒng)中即可。人臉識別功能按照流程主要分為兩步,首先是讀取圖片(本地文件夾或者攝像頭);其次是使用人臉識別算法(基于opencv的dnn或者其他深度學(xué)習(xí)框架的人臉識別模型)進(jìn)行人臉檢測和識別。下面分別就上述的兩部分來分別說明。
2.1 使用opencv從攝像頭中讀取圖片
從opencv中讀取圖片主要分為三種,下面分別給出三種方式的代碼:
2.1.1 opencv讀取圖片/攝像頭的視頻幀
- 讀取單張圖片
# 單張圖片讀取
import cv2
img_filename = 'demo.png'
img = cv2.imread(img_filename, cv2.IMREAD_COLOR) # 彩色圖像
# img = cv2.imread(img_filename, cv2.IMREAD_GRAYSCALE) # 灰度圖像
- 讀取文件夾中的圖片
# 從文件夾讀取
import glob
import cv2
img_dir = r'path/to/your/database'
for img_filename in glob.glob(img_dir + '/*.jpg' ) # 針對jpg圖片
img = cv2.imread(img_filename, cv2.IMREAD_COLOR)
- 讀取攝像頭中的視頻幀
import cv2
def get_cap():
cap = cv2.VideoCapture(0)
return cap
cap = get_cap() # 獲取攝像頭視頻流
id = 1
while True:
ret, frame = cap.read()
if cv2.waitKey(1) == ord('q'):#按Q退出
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (480, 480))
# img = np.array(img)
break
else:
cv2.imshow('win', frame)
cv2.waitKey(10) # 可以理解為控制幀率
2.1.2 opencv將圖像保存為gif和視頻
有的時候,我們甚至還需要展示,那么就需要一個視頻流保存為視頻文件、圖片轉(zhuǎn)gif或者圖像轉(zhuǎn)視頻的需求,下面同樣給出這幾種操作的示例代碼:
- 視頻流保存視頻文件參考
需要注意的是,盡量按照攝像頭的編碼來保存視頻,否則可能會出問題。
cv2.VideoWriter_fourcc('I','4','2','0'):YUV編碼,4:2:0色度子采樣。這種編碼廣泛兼容,但會產(chǎn)生大文件。文件擴(kuò)展名應(yīng)為.avi。
cv2.VideoWriter_fourcc('P','I','M','1'):MPEG-1編碼。文件擴(kuò)展名應(yīng)為.avi。
cv2.VideoWriter_fourcc('X','V','I','D'):MPEG-4編碼。如果要限制結(jié)果視頻的大小,這是一個很好的選擇。文件擴(kuò)展名應(yīng)為.avi。
cv2.VideoWriter_fourcc('m', 'p', '4', 'v'):較舊的MPEG-4編碼。如果要限制結(jié)果視頻的大小,這是一個很好的選擇。文件擴(kuò)展名應(yīng)為.m4v。
cv2.VideoWriter_fourcc('X','2','6','4'):較新的MPEG-4編碼。如果你想限制結(jié)果視頻的大小,這可能是最好的選擇。文件擴(kuò)展名應(yīng)為.mp4。
cv2.VideoWriter_fourcc('T','H','E','O'):這個選項是Ogg Vorbis。文件擴(kuò)展名應(yīng)為.ogv。
cv2.VideoWriter_fourcc('F','L','V','1'):此選項為Flash視頻。文件擴(kuò)展名應(yīng)為.flv。
def save_video(video_name):
cap = cv2.VideoCapture(video_name)
# setting
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 獲取原視頻的寬
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 獲取原視頻的搞
fps = int(cap.get(cv2.CAP_PROP_FPS)) # 幀率
fourcc = int(cap.get(cv2.CAP_PROP_FOURCC)) # 視頻的編碼
# 定義視頻保存的輸出屬性
out = cv2.VideoWriter('out.mp4', fourcc, fps, (width, height))
while cap.isOpened():
ret, frame = cap.read()
cv2.imshow('fame', frame)
key = cv2.waitKey(25)
out.write(frame)
if key == ord('q'):
break
cap.release()
out.release()
cv2.destroyAllWindows()
- 圖片轉(zhuǎn)gif
import cv2
import imageio
def save_gif(video_name):
cap = cv2.VideoCapture(video_name)
# 定義視頻保存的輸出屬性
out = []
while cap.isOpened():
ret, frame = cap.read()
cv2.imshow('fame', frame)
key = cv2.waitKey(15)
if not ret: continue
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
out.append(cv2.resize(frame, (w//2, h//2))) # 適當(dāng)縮放,防止gif過大
if key == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
imageio.mimsave('demo.gif', out, fps=30) # 調(diào)整保存的幀率
- 圖片轉(zhuǎn)視頻
import cv2
import os
def img2video(img_dir, save_name):
# setting
width = 1080 # 獲取原視頻的寬
height = 720 # 獲取原視頻的搞
fps = 30 # 幀率
fourcc = cv2.VideoWriter_fourcc(*'DIVX') # 視頻的編碼
# 定義視頻保存的輸出屬性
out = cv2.VideoWriter(save_name, fourcc, fps, (width, height))
for r, ds, fs in os.walk(img_dir):
for f in fs:
file_name = os.path.join(r, f)
img = cv2.imread(file_name)
out.write(img)
return
2.2 使用不同人臉識別算法進(jìn)行檢測和識別
人臉識別最簡單的就是使用dlib框架,其檢測流程如下圖所示:
2.2.1 模型加載
這里需要加載三個模型,分別是人臉檢測模型、人臉關(guān)鍵點(diǎn)預(yù)測模型、描述子特征計算模型,人臉檢測模型直接調(diào)用dlib的dlib.get_frontal_face_detector()
即可,另外兩個可以直接從dlib的官網(wǎng)下載對應(yīng)的兩個模型:
- shape_predictor_68_face_landmarks.dat
- dlib_face_recognition_resnet_model_v1.dat
2.2.2 讀取圖片
讀取圖片直接使用2.1所講的就可以了,不再贅述。
2.2.3 人臉檢測和關(guān)鍵點(diǎn)提取
import dlib
def detect_img():
cap = cv2.VideoCapture(0)
detector = dlib.get_frontal_face_detector() # 檢測模型
path_pre = "../models/shape_predictor_68_face_landmarks.dat" # 68點(diǎn)模型
pre = dlib.shape_predictor(path_pre)
# 定義視頻保存的輸出屬性
out = []
while cap.isOpened():
ret, frame = cap.read()
key = cv2.waitKey(15)
if not ret: continue
rects = detector(frame, 0)
for obj in rects: # 繪制所有的人臉
# print(dir(obj))
pt1, pt2 = rect2bb(obj)
cv2.rectangle(frame, pt1, pt2, (0, 255, 0), 2)
shapes = pre(frame, obj) # 對當(dāng)前的人臉框做68點(diǎn)特征點(diǎn)預(yù)測
shapes = shape_to_np(shapes)
for (x, y) in shapes:
cv2.circle(frame, (x, y), 1, (255, 0, 0), -1)
cv2.imshow('fame', frame)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, c = frame.shape
out.append(cv2.resize(frame, (w//2, h//2)))
if key == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
imageio.mimsave('demo.gif', out, fps=20)
可以發(fā)現(xiàn),上述代碼中有兩個函數(shù)沒有定義,rect2bb
和shape_to_np
。由于dlib的輸出是個自定義的類,雖然print可以獲取他的rect信息,但是我們不能直接傳給cv2.rectangle,因此我們需要對dlib推理得到的rects和shapes重新處理從而方便處理:
def rect2bb(rect):
x1 = rect.left()
y1 = rect.top()
x2 = rect.right()
y2 = rect.bottom()
return (x1, y1), (x2, y2)
def shape_to_np(shape):
coords = np.zeros((68, 2), dtype=int)
for i in range(0, 68):
coords[i] = (shape.part(i).x, shape.part(i).y)
return coords
通過這個demo,我們可以獲取到下面的檢測結(jié)果,看起來還可以hhh。
2.2.4 描述子匹配
通過上述例子,我們已經(jīng)完成了人臉檢測和關(guān)鍵點(diǎn)估計,那么如何進(jìn)行人臉的匹配,也就是說怎么將當(dāng)前人臉和我們數(shù)據(jù)庫里面的人臉進(jìn)行匹配呢?我認(rèn)為主要分為以下幾個步驟:
- 創(chuàng)建一個人臉數(shù)據(jù)集
這里我們可以按照學(xué)號或者按照人名將人臉數(shù)據(jù)分文件夾放置,如下圖所示:
每個人的文件夾下放置該人不同姿態(tài)下的人臉圖片,用來作為原始的圖片數(shù)據(jù)集,但其實(shí)匹配的過程中我們往往不太直接用數(shù)據(jù)庫中的原圖,因?yàn)?code>計算特征是耗時的,因此更為合適的做法是,將數(shù)據(jù)庫中的圖片提前進(jìn)行人臉識別、特征點(diǎn)檢測和特征提取,產(chǎn)生一個特征數(shù)據(jù)集替代這一原始圖片的數(shù)據(jù)集,如下圖所示:
-
分別計算未知人臉特征和數(shù)據(jù)庫中的人臉特征
顧名思義,我們需要在2.2.3中的代碼的基礎(chǔ)上,完成人臉特征提取,代碼如下:
def load_model():
detector = dlib.get_frontal_face_detector()
path_pre = "../models/shape_predictor_68_face_landmarks.dat" # 68點(diǎn)模型
pre = dlib.shape_predictor(path_pre)
path_model = "../models/dlib_face_recognition_resnet_model_v1.dat" # resent模型
model = dlib.face_recognition_model_v1(path_model)
return detector, pre, model
def get_describe_for_face(detector, pre, model, img):
det_img = detector(img, 0)
try:
shape = pre(img, det_img[0])
know_encode = model.compute_face_descriptor(img, shape)
except:
return -1
return know_encode
# 獲取待檢測圖片的encode
unknown_encod = get_describe_for_face(detector, pre, model, unknown_img)
上面我們給出了計算未知人臉特征的代碼,而數(shù)據(jù)庫中的已知人臉數(shù)據(jù)我們同樣需要進(jìn)行特征提取,正如前面2.2.4第一步所說,我們需要提前預(yù)處理數(shù)據(jù)集中的特征,保存為一個類似face_fea_database
的數(shù)據(jù)集備用。這里給出批處理獲取特征
的參考代碼:
import os
import pickle
import numpy as np
from PIL import Image
def Eu(a, b): # 距離函數(shù)
return np.linalg.norm(np.array(a) - np.array(b), ord=2)
face_dir = '../face_database'
face_feature_dir = '../face_fea_database'
unknown_img = np.array(Image.open('../face_database/20221000/1.jpg'))
detector, pre, model = load_model()
unknown_fea = get_describe_for_face(detector, pre, model, unknown_img)
for id in os.listdir(face_dir):
face_path = os.path.join(face_dir, id)
face_fea_path = os.path.join(face_feature_dir, id)
os.makedirs(face_fea_path, exist_ok=True)
for img_name in os.listdir(face_path):
img_path = os.path.join(face_path, img_name)
fea_path = os.path.join(face_fea_path, img_name.replace('.jpg', '.ft'))
img = np.array(Image.open(img_path))
img_feature = get_describe_for_face(detector, pre, model, img)
with open(fea_path, 'wb') as f:
pickle.dump(img_feature, f)
with open(fea_path, 'rb') as f:
data = pickle.load(f)
print("save and load result is same? ", Eu(img_feature, unknown_fea) == Eu(data, unknown_fea))
- 將未知人臉和數(shù)據(jù)庫中的人臉進(jìn)行匹配
匹配這部分我們需要定義我們自己的匹配規(guī)則,這里我使用的是將未知了雙閾值篩選,如果兩個人臉特征差距太大,那么就跳過;如果兩個人臉的差距很小,那么直接返回結(jié)果;介于倆個閾值之間的,就放在一個list中,最后按照距離排序,返回距離最小的人臉?biāo)鶎?yīng)的人員屬性。
下面給出macth部分的函數(shù)代碼:
def record_status(status, name=None):
f = open('../recog_status.txt', 'w', encoding='utf-8')
if status == 3: # 打卡成功
info = STATUS_DICT[status] + name
else:
info = STATUS_DICT[status]
f.write(info)
f.close()
def match_face_by_face_recognize(unknown_img, face_dir, stu_lists, thres=0.4, min_thres=0.55):
MODEL_STATUS = 1
record_status(MODEL_STATUS)
# detector, pre, model = load_model()
MODEL_STATUS = 2
record_status(MODEL_STATUS)
# 獲取待檢測圖片的encode
unknown_encod = face_recognition.face_encodings(unknown_img)
if unknown_encod == []:
record_status(4)
print('無法識別攝像頭中的人臉')
return -1, 'Unknown'
else:
unknown_encod = unknown_encod[0]
stus_conf = {stu[0]:[stu[1], 2] for stu in stu_lists}
for id, name in stu_lists:
# 獲取當(dāng)前學(xué)生的人臉地址
stu_face_dir = os.path.join(face_dir, str(id))
for img_name in os.listdir(stu_face_dir):
img_path = os.path.join(stu_face_dir, img_name)
# func1. use dlib to get encode if necessary
# img = np.array(Image.open(img_path))
# know_encode = get_describe_for_face(detector, pre, model, img)
# func2. use feature prepared by utils.prepare_feature.py
f = open(img_path, 'rb')
know_encode = pickle.load(f)
# func3. use face recognize
# img = face_recognition.load_image_file(img_path)
# know_encode = face_recognition.face_encodings(img)
if know_encode == []:
record_status(4)
print('無法識別人臉庫中的人臉')
return -1, 'Unknown' # 人臉庫一般不存在這種情況!??!必須保證高質(zhì)量人臉
else:
know_encode = know_encode[0]
# 如果檢測到差距小于閾值,認(rèn)為檢測成功, 退出
distance = Eu(know_encode, unknown_encod)
if distance > min_thres: break # 這個人必然不是
else:
if distance < thres: # 閾值很小了
MODEL_STATUS = 3
record_status(MODEL_STATUS, name)
return id, name
else: # 閾值一般
stus_conf[id][1] = min(stus_conf[id][1], distance)
# print(stus_conf)
res = sorted(stus_conf.items(), key=lambda x:x[1][1])[0]
print(res)
id, (name, conf) = res
if conf > min_thres:
MODEL_STATUS = 4
record_status(MODEL_STATUS)
return -1, 'Unknown'
else:
MODEL_STATUS = 3
record_status(MODEL_STATUS, name)
return id, name
if __name__ == '__main__':
cap = get_cap()
id = 1
while True:
ret, frame = cap.read()
if cv2.waitKey(1) == ord('q'):#按Q退出
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (480, 480))
# img = np.array(img)
break
else:
cv2.imshow('win', frame)
stu_lists = [(20221001, '錢全'), (20221002, '葛娟'), (20221003, '衛(wèi)雅'), (20221004, '王寧'), (20221005, '韓惠'),
(20221006, '鄒慧')]
detector, pre, model = load_model()
result = match_face(img, '../face_fea_database', stu_lists, detector, pre, model)
print(result)
其實(shí)從上面的代碼上可以看出,我們其實(shí)還有一種快速提取圖像特征的方式,那就是使用face_recognize
這個庫,代碼如下,但速度上肯定還是加載預(yù)處理得到的特征文件(第二種方法)更快。
func3. use face recognize
img = face_recognition.load_image_file(img_path)
know_encode = face_recognition.face_encodings(img)
三、 基于sqlite3的數(shù)據(jù)庫設(shè)計
數(shù)據(jù)庫本身不是這個項目的重點(diǎn),因此這里淺淺地使用一下python自帶的sqlite3數(shù)據(jù)庫,這里我只簡單地定義了一些必要的類,因?yàn)檫@是一個人臉打卡系統(tǒng),因此不免就需要有學(xué)生、課程、打卡信息以及選課信息表。
3.1 簡單定義數(shù)據(jù)庫類
先定義一個數(shù)據(jù)庫的類,用來管理我們的數(shù)據(jù),包括數(shù)據(jù)的導(dǎo)入、數(shù)據(jù)的讀取和數(shù)據(jù)的更新等等,通過一個類的方式來進(jìn)行數(shù)據(jù)庫的操作更為清晰和直觀一些。E-R圖這里就不畫了,這個任務(wù)也比較簡單吧,簡化起見就設(shè)計了四個表,具體內(nèi)容下面詳細(xì)敘述。
3.1.1 CardRecord 打卡信息表
3.1.2 Course 課程表
3.1.3 Relation 選課關(guān)系表
3.1.4 Student 學(xué)生表
對于選擇困難的人而言起名字真的很痛苦,為了快速生成不同學(xué)生名和課程名,我使用了隨機(jī)拼接的方式生成csv文件,后面在導(dǎo)入數(shù)據(jù)庫,這里只給出csv的生成代碼,大家可以根據(jù)需要選擇和修改:
def random_get_datas():
import random
start_id = 20221000
firstName = "趙錢孫李周吳鄭王馮陳褚衛(wèi)蔣沈韓陶姜戚謝鄒喻水云蘇潘葛奚范彭"
lastName = "秀娟英華慧巧美娜靜淑惠珠翠雅力明永健寧貴福生龍元全國勝學(xué)祥才"
stu_infos, class_infos = [], []
with open('student.csv', 'w', encoding='utf-8') as f:
f.writelines('id,name,age,details\n')
for i in range(10):
id, name, age, details = start_id, \
random.choice(firstName) + random.choice(lastName), \
random.randint(18, 24), \
"喜歡" + random.choice(['足球', '籃球', '排球'])
start_id += 1
f.writelines("{},{},{},{}\n".format(id, name, age, details))
stu_infos.append((id, name, age, details))
with open('class.csv', 'w', encoding='utf-8') as f:
f.writelines('id,name,status\n')
start_id = 19334
firstName = ['高等','基礎(chǔ)' ,'線性' ,'隨機(jī)' ]
lastName = ['數(shù)學(xué)', '物理', '信號分析']
for i in range(4):
id, name, status = start_id, \
random.choice(firstName) + random.choice(lastName), \
1
start_id += 1
f.writelines("{},{},{}\n".format(id, name, status))
class_infos.append((id, name, status))
with open('relation.csv', 'w', encoding='utf-8') as f:
id = 0
f.writelines("id,stu_id,course_id,create_time\n")
for classes in class_infos:
a, b = random.randint(0, 4), random.randint(5, len(stu_infos))
for stu in stu_infos[a:b]:
time = "2022-5-" + str(random.randint(1, 10))
f.writelines("{},{},{},{}\n".format(id, stu[0], classes[0], time))
id += 1
print('random init success.')
從csv導(dǎo)入sqlite還是挺容易的,示例代碼如下,其中conn = sqlite3.connect(self.db_path)
:
def get_class_from_csv(self, csv_file):
df = pd.read_csv(csv_file)
try:
df.to_sql('Course', self.conn, if_exists='append', index=False)
except Exception as e:
print(e)
finally:
print("導(dǎo)入成功")
3.2 封裝常用的sql查詢到數(shù)據(jù)庫類成員函數(shù)中
這個也比較基本,把一些常用的查詢函數(shù)集成到database類里
def get_courses(self):
# 返回課程編號和名稱
sql = '''
select * from Course where status == 1;
'''
curr = self.conn.cursor()
curr.execute(sql)
return [item[:2] for item in curr.fetchall()]
def get_stu_info(self):
sql = '''
select * from student;
'''
curr = self.conn.cursor()
curr.execute(sql)
return {item[0]:item[1:] for item in curr.fetchall()}
def get_stus_by_course(self, course_id):
# 返回課程編號和名稱
sql = '''
select Student.id, Student.name from Student, Relation where Relation.course_id == ? and Student.id == Relation.stu_id
'''
curr = self.conn.cursor()
curr.execute(sql, [course_id])
其實(shí)這里面我寫的也比較簡單,拋磚引玉,大家可以參照類似的方式把一些功能sql語句寫入函數(shù)中,方便調(diào)用。
四、 基于PyQt5的GUI設(shè)計
GUI設(shè)計這部分的話,主要就是一個交互的作用,大家根據(jù)自己的熟悉程度選擇不同的框架,python客戶端主要就是tkinter
和pyqt5
吧,熟悉前后端框架的也可以用html+css+js+django/flask
之類的解決。我之前做過pyqt5的客戶端,算是比較熟悉吧,這里也就用pyqt5為例來說明一下吧。
想使用pyqt5的同志們,處理要安裝基本的pyqt5的python庫,為了方便開展GUI設(shè)計,最好可以配置一下三件套
,也就是:
-
qt designer
:把GUI設(shè)計變得更簡單,可以理解為可以把組件拖來拖去,完成框架設(shè)計的軟件。 -
pyrcc
:把pyqt5界面所引用的多個資源轉(zhuǎn)換為一個二進(jìn)制文件,方便使用,不用在去一個個文件調(diào)用。 -
pyuic
:pyqt5的GUI文件是.ui文件,這個工具可以把ui文件轉(zhuǎn)py文件,供我們做類的復(fù)用。
這里我就不贅述這三個玩意的配置和用法啦,如果大家感興趣,我在專門搞一篇講解吧 ~
4.1 框架和文件夾結(jié)構(gòu)設(shè)計
下面我講一下gui文件的結(jié)構(gòu)吧,其實(shí)做任何pyqt5的應(yīng)用,都是大同小異的,在你的主目錄下,可以創(chuàng)建一個resource文件夾,里面分別創(chuàng)建一個UI
文件夾和一個images
文件夾;
-
UI
主要就存放了你的不同界面的ui源文件,以及你用pyuic
轉(zhuǎn)換好的py文件(花括號括起來的),轉(zhuǎn)換后的py文件其實(shí)就是你在qt designer中設(shè)計的那個面板對應(yīng)的py類定義和實(shí)現(xiàn),你后面可以直接繼承這個類然后再做一些邏輯,包括信號和槽函數(shù)的實(shí)現(xiàn)。 -
images
主要存一些你的圖片資源文件,資源文件最后保存在.qrc
中,后面通過pyrcc可以將資源文件轉(zhuǎn)為images_rc.py,方便后面使用。當(dāng)然,如果你有一些音頻數(shù)據(jù),也可以按照類似的方式創(chuàng)建。
剩下的文件夾和定義就隨意啦 ~ 最好是整一個main文件,然后在里面完成不同界面的跳轉(zhuǎn)和交互,這樣比較清晰直觀,界面之間盡量解耦,依賴低一些。
4.2 開始界面設(shè)計
如上圖所示,其實(shí)可以看出,開始界面的組成比較簡單,就是一些按鈕、label和組合框的堆疊,主要就是為了顯示我們的軟件名,增加選擇框?qū)φn程進(jìn)行選擇,考勤時間的選擇,界面切換按鈕和自定義的最小化,最大化按鈕(qt自帶的很丑hhh)
你只需要選擇合適的widget(布局),把這些組織在一起就好啦。對于初學(xué)的同學(xué)不需要關(guān)心太多,我覺得你只要把想要的組件放進(jìn)去,然后哪怕是固定他們的位置呢,只要能用,就是勝利;這里要說的話就有點(diǎn)多了哈哈哈。如果你要自己設(shè)計的話,那么就就點(diǎn)擊左上角的file,按如下的創(chuàng)建就可以啦,然后在左邊選則不同的組件,拖過去就ok啦。
大家回頭配置好了qt designer,可以打開我的ui文件,看看具體的布局設(shè)計等等,其實(shí)想要把qt弄得好看一些還是需要費(fèi)一些功夫的,qt的qss感覺相比于css還是差點(diǎn)意思,所以一些圓角設(shè)計、配色和鼠標(biāo)事件下,控件的變化想變得絲滑還是需要大家費(fèi)點(diǎn)心思。
4.3 驗(yàn)證界面設(shè)計
驗(yàn)證界面還是挺簡單的,其實(shí)就是為了防止同學(xué)們或者非管理員瞎玩開始界面和導(dǎo)出打開數(shù)據(jù),因此需要驗(yàn)證身份,也比較簡單吧,就只需要輸入密碼,然后驗(yàn)證或者返回就ok。
4.4 自定義的listwidget設(shè)計
這里設(shè)計這個listwidget的目的其實(shí)是為了顯示打卡人的頭像、姓名、學(xué)號和打卡信息,把他放在一個listwidget里面,但是qt并沒有一個基本組件可以滿足這一需求,因此我們可以設(shè)計一個form,然后把他嵌入到listwidget里面就ok啦,當(dāng)然后面如何對listwidget的元素進(jìn)行狀態(tài)更新和排序,也是有一些細(xì)節(jié)的,這里不贅述,大家感興趣可以從我的代碼中找到答案。
4.4 主界面設(shè)計
主界面應(yīng)該是最復(fù)雜的,但其實(shí)也只是相對復(fù)雜,我按照上述的8部分來敘述吧:
- 打卡面板:
- 狀態(tài)顯示欄
- 剩余時間顯示:
- 打卡進(jìn)度更新:
- 自定義打卡人物和狀態(tài)顯示:
- 攝像頭和打卡:
- 數(shù)據(jù)導(dǎo)出:
- 界面跳轉(zhuǎn):
4.5 如何有效組織不同的UI界面類
五、效果展示
5.1 檢測和識別效果展示
5.2 數(shù)據(jù)庫導(dǎo)出和查看
六、代碼獲取和下載
完整項目代碼放在了我的公眾號:百年后封筆,
大家回復(fù) “人臉打卡系統(tǒng)”,即可免費(fèi)獲取完整代碼的下載鏈接!
創(chuàng)作不易,也歡迎大家批評指正,動動小手,評論,收藏一哈!文章來源:http://www.zghlxwxcb.cn/news/detail-513845.html
我也會繼續(xù)不定期更新感興趣的項目~文章來源地址http://www.zghlxwxcb.cn/news/detail-513845.html
到了這里,關(guān)于使用python和pyqt5輕松上手人臉識別系統(tǒng)(含代碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!