基于Flask/Opencv/Dlib课堂人脸签到系统

简介

利用Python语言、Flask框架、Dlib库、MySQL数据库等工具设计并实现一套基于Web端的刷脸签到系统。
学生可以在闲暇时在系统录入人脸,等到上课签到时,只需在网页上刷脸即可完成签到,签到页实时显示签到人信息,整个过程简便流畅。同时,也实现了与考勤相关的一系列功能,满足用户需求。


一、系统角色和功能设计

  • 学生

    • 人脸录入
    • 退选课
    • 考勤查询
    • 选课
    • 修改个人信息
  • 老师

    • 新建课程
    • 开始或关闭选课
    • 导入选择记录
    • 课程刷脸签到
    • 考勤查询和统计
    • 考勤导出
    • 拍照权限设置
    • 批量导入账号

二、技术

	 - 前端
	 		- bootstrap 
	 - 后端
	 		- flask	 
	 - 语言
	        - python
	 - 数据库
	        - mysql 8.0.26
	 - 工具库
	 		- Dlib
	 		- Opencv 

三,系统源码截图

  1. 人脸识别
# 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



  1. 人脸录入
# 进行人脸录入

# 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"


  1. 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)
  1. 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)==</
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值