简介
利用Python语言、Flask框架、Dlib库、MySQL数据库等工具设计并实现一套基于Web端的刷脸签到系统。
学生可以在闲暇时在系统录入人脸,等到上课签到时,只需在网页上刷脸即可完成签到,签到页实时显示签到人信息,整个过程简便流畅。同时,也实现了与考勤相关的一系列功能,满足用户需求。
一、系统角色和功能设计
-
学生
- 人脸录入
- 退选课
- 考勤查询
- 选课
- 修改个人信息
-
老师
- 新建课程
- 开始或关闭选课
- 导入选择记录
- 课程刷脸签到
- 考勤查询和统计
- 考勤导出
- 拍照权限设置
- 批量导入账号
二、技术
- 前端
- bootstrap
- 后端
- flask
- 语言
- python
- 数据库
- mysql 8.0.26
- 工具库
- Dlib
- Opencv
三,系统源码截图
- 人脸识别
# SPDX-License-Identifier: MIT
# Author: IT-邢
# 微信: zhizhenkongjian001
# Mail: 2362721245@qq.com
# 从人脸图像文件中提取人脸特征存入 "features_all.csv" / Extract features from images and save into "features_all.csv"
import os
import dlib
from skimage import io
import csv
import numpy as np
# 要读取人脸图像文件的路径 / Path of cropped faces
path_images_from_camera = "static/data/data_faces_from_camera/"
# Dlib 正向人脸检测器 / Use frontal face detector of Dlib
detector = dlib.get_frontal_face_detector()
# Dlib 人脸 landmark 特征点检测器 / Get face landmarks
predictor = dlib.shape_predictor('app/static/data_dlib/shape_predictor_68_face_landmarks.dat')
# Dlib Resnet 人脸识别模型,提取 128D 的特征矢量 / Use Dlib resnet50 model to get 128D face descriptor
face_reco_model = dlib.face_recognition_model_v1("app/static/data_dlib/dlib_face_recognition_resnet_model_v1.dat")
# 返回单张图像的 128D 特征 / Return 128D features for single image
# Input: path_img <class 'str'>
# Output: face_descriptor <class 'dlib.vector'>
def return_128d_features(path_img):
img_rd = io.imread(path_img)
faces = detector(img_rd, 1)
print("%-40s %-20s" % (" >> 检测到人脸的图像 / Image with faces detected:", path_img), '\n')
# 因为有可能截下来的人脸再去检测,检测不出来人脸了, 所以要确保是 检测到人脸的人脸图像拿去算特征
# For photos of faces saved, we need to make sure that we can detect faces from the cropped images
if len(faces) != 0:
shape = predictor(img_rd, faces[0])
face_descriptor = face_reco_model.compute_face_descriptor(img_rd, shape)
else:
face_descriptor = 0
print("no face")
return face_descriptor
# 返回 personX 的 128D 特征均值 / Return the mean value of 128D face descriptor for person X
# Input: path_faces_personX <class 'str'>
# Output: features_mean_personX <class 'numpy.ndarray'>
def return_features_mean_personX(path_faces_personX):
features_list_personX = []
photos_list = os.listdir(path_faces_personX)
if photos_list:
for i in range(len(photos_list)):
# 调用 return_128d_features() 得到 128D 特征 / Get 128D features for single image of personX
print("%-40s %-20s" % (" >> 正在读的人脸图像 / Reading image:", path_faces_personX + "/" + photos_list[i]))
features_128d = return_128d_features(path_faces_personX + "/" + photos_list[i])
# 遇到没有检测出人脸的图片跳过 / Jump if no face detected from image
if features_128d == 0:
i += 1
else:
features_list_personX.append(features_128d)
else:
print(" >> 文件夹内图像文件为空 / Warning: No images in " + path_faces_personX + '/', '\n')
# 计算 128D 特征的均值 / Compute the mean
# personX 的 N 张图像 x 128D -> 1 x 128D
if features_list_personX:
features_mean_personX = np.array(features_list_personX).mean(axis=0)
else:
features_mean_personX = np.zeros(128, dtype=int, order='C')
return features_mean_personX
- 人脸录入
# 进行人脸录入
# import dlib
import numpy as np
import cv2
import os
from skimage import io
import dlib
# Dlib 正向人脸检测器
detector = dlib.get_frontal_face_detector()
class Face_Register:
def __init__(self):
pass
#将录入的图片进行人脸检测,分截取人脸部分
def process(self, path):
photos_list = os.listdir(path)
if photos_list:
for i in range(len(photos_list)):
# 调用 return_128d_features() 得到 128D 特征
current_face_path = path + photos_list[i]
print("%-40s %-20s" % (" >> 正在检测的人脸图像 / Reading image:", current_face_path))
img_rd = cv2.imread(current_face_path)
faces = detector(img_rd, 0) # detector = dlib.get_frontal_face_detector() & Dlib人脸检测器
# 遇到没有检测出人脸的图片跳过
if len(faces) == 0:
i += 1
else:
for k, d in enumerate(faces):
# 计算人脸区域矩形框大小
height = (d.bottom() - d.top())
width = (d.right() - d.left())
hh = int(height / 2)
ww = int(width / 2)
# 判断人脸矩形框是否超出 480x640
if (d.right() + ww) > 640 or (d.bottom() + hh > 480) or (d.left() - ww < 0) or (
d.top() - hh < 0):
print("%-40s %-20s" % (" >>超出范围,该图作废", current_face_path))
else:
img_blank = np.zeros((int(height * 2), width * 2, 3), np.uint8)
for ii in range(height * 2):
for jj in range(width * 2):
img_blank[ii][jj] = img_rd[d.top() - hh + ii][d.left() - ww + jj]
cv2.imwrite(path + str(i+1) + ".jpg", img_blank)
print("写入本地 / Save into:", str(path) + str(i+1) + ".jpg")
else:
print(" >> 文件夹内图像文件为空 / Warning: No images in " + path , '\n')
def single_pocess(self,path):
#读取人脸图像
img_rd = cv2.imread(path)
# Dlib的人脸检测器
faces = detector(img_rd, 0)
# 遇到没有检测出人脸的图片跳过
if len(faces) == 0:
return "none"
else:
for k, d in enumerate(faces):
# 计算人脸区域矩形框大小
height = (d.bottom() - d.top())
width = (d.right() - d.left())
hh = int(height / 2)
ww = int(width / 2)
# 6. 判断人脸矩形框是否超出 480x640 / If the size of ROI > 480x640
if (d.right() + ww) > 640 or (d.bottom() + hh > 480) or (d.left() - ww < 0) or (
d.top() - hh < 0):
print("%-40s %-20s" % (" >>超出范围,该图作废", path))
return "big"
else:
img_blank = np.zeros((int(height * 2), width * 2, 3), np.uint8)
for ii in range(height * 2):
for jj in range(width * 2):
img_blank[ii][jj] = img_rd[d.top() - hh + ii][d.left() - ww + jj]
cv2.imwrite(path , img_blank)
print("写入本地 / Save into:", path)
return "right"
- model层
from app import db #db是在app/__init__.py生成的关联后的SQLAlchemy实例
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(320), unique=True)
password = db.Column(db.String(32), nullable=False)
def __repr__(self):
return '<User %r>' % self.username
class Admin(db.Model):
__tablename__ = 'admins'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(320), unique=True)
password = db.Column(db.String(32), nullable=False)
def __repr__(self):
return '<User %r>' % self.username
class Student(db.Model):
__tablename__ = 'students'
s_id= db.Column(db.String(13), primary_key=True)
s_name = db.Column(db.String(80), nullable=False)
s_password = db.Column(db.String(32), nullable=False)
flag = db.Column(db.Integer, default=1)
before = db.Column(db.DateTime)
def __repr__(self):
return '<Student %r,%r>' % (self.s_id,self.s_name)
class Teacher(db.Model):
__tablename__ = 'teachers'
t_id= db.Column(db.String(8), primary_key=True)
t_name = db.Column(db.String(80), nullable=False)
t_password = db.Column(db.String(32), nullable=False)
before = db.Column(db.DateTime)
def __repr__(self):
return '<Teacher %r,%r>' % (self.t_id,self.t_name)
class Faces(db.Model):
__tablename__ = 'student_faces'
s_id = db.Column(db.String(13), primary_key=True)
feature = db.Column(db.Text,nullable=False)
def __repr__(self):
return '<Faces %r>' % self.s_id
class Course(db.Model):
__tablename__ = 'courses'
c_id = db.Column(db.String(6), primary_key=True)
t_id = db.Column(db.String(8),db.ForeignKey('teachers.t_id'),nullable=False)
c_name = db.Column(db.String(100),nullable=False)
times = db.Column(db.Text,default="0000-00-00 00:00")
flag = db.Column(db.String(50), default="不可选课")
def __repr__(self):
return '<Course %r,%r,%r>' % (self.c_id,self.t_id,self.c_name)
class SC(db.Model):
__tablename__ = 'student_course'
s_id = db.Column(db.String(13),db.ForeignKey('students.s_id'),primary_key=True)
c_id = db.Column(db.String(100),db.ForeignKey('courses.c_id') ,primary_key=True)
def __repr__(self):
return '<SC %r,%r> '%( self.s_id,self.c_id)
class Attendance(db.Model):
__tablename__ = 'attendance'
id = db.Column(db.Integer,primary_key=True)
s_id = db.Column(db.String(13), db.ForeignKey('students.s_id'))
c_id = db.Column(db.String(100), db.ForeignKey('courses.c_id'))
time = db.Column(db.DateTime)
result = db.Column(db.String(10),nullable=False)
def __repr__(self):
return '<Attendance %r,%r,%r,%r>' % (self.s_id,self.c_id,self.time,self.result)
class Time_id():
id = ''
time = ''
def __init__(self,id,time):
self.id = id
self.time = time
class choose_course():
__tablename___ = 'choose_course'
c_id = db.Column(db.String(6), primary_key=True)
t_id = db.Column(db.String(8), nullable=False)
c_name = db.Column(db.String(100), nullable=False)
def __repr__(self):
return '<Course %r,%r,%r>' % (self.c_id,self.t_id,self.c_name)
-
view层
student视图函数
from flask import Blueprint, render_template, request, session,flash,jsonify,redirect,url_for
from app import get_faces_from_camera as gf
import base64
import os
from .models import Faces,Student,SC,Course,Attendance,Teacher
from app import features_extraction_to_csv as fc
from app import db
from datetime import datetime
from sqlalchemy import extract
student = Blueprint('student',__name__,static_folder='static')
@student.route('/home')
def home():
records = {
}
student = Student.query.filter(Student.s_id==session['id']).first()
session['flag'] = student.flag
attendances = db.session.query(Attendance).filter(Attendance.s_id==session['id']).order_by(Attendance.time.desc()).limit(5).all()
for i in attendances:
course = db.session.query(Course).filter(Course.c_id==i.c_id).all()
records[i]=course
year = datetime.now().year
month = datetime.now().month
qj = db.session.query(Attendance).filter(Attendance.s_id == session['id'], extract('month', Attendance.time)==month,
extract('year', Attendance.time) == year, Attendance.result == '请假').count()
cd = db.session.query(Attendance).filter(Attendance.s_id == session['id'], extract('month', Attendance.time)==</