Python项目-基于深度学习的校园人脸识别考勤系统

引言

随着人工智能技术的快速发展,深度学习在计算机视觉领域的应用日益广泛。人脸识别作为其中的一个重要分支,已经在安防、金融、教育等多个领域展现出巨大的应用价值。本文将详细介绍如何使用Python和深度学习技术构建一个校园人脸识别考勤系统,该系统能够自动识别学生身份并记录考勤信息,大大提高了考勤效率,减轻了教师的工作负担。

系统概述

功能特点

  • 实时人脸检测与识别:能够从摄像头视频流中实时检测并识别人脸
  • 自动考勤记录:识别学生身份后自动记录考勤信息
  • 数据可视化:提供直观的考勤统计和数据分析功能
  • 管理员后台:方便教师和管理员查看和管理考勤记录
  • 用户友好界面:简洁直观的用户界面,易于操作

技术栈

  • 编程语言:Python 3.8+
  • 深度学习框架:TensorFlow/Keras、PyTorch
  • 人脸检测与识别:dlib、face_recognition、OpenCV
  • Web框架:Flask/Django
  • 数据库:SQLite/MySQL
  • 前端技术:HTML、CSS、JavaScript、Bootstrap

系统设计

系统架构

系统采用经典的三层架构设计:

  1. 表示层:用户界面,包括学生签到界面和管理员后台
  2. 业务逻辑层:核心算法实现,包括人脸检测、特征提取和身份识别
  3. 数据访问层:负责数据的存储和检索,包括学生信息和考勤记录

数据流程

  1. 摄像头捕获实时视频流
  2. 人脸检测模块从视频帧中检测人脸
  3. 特征提取模块提取人脸特征
  4. 身份识别模块将提取的特征与数据库中的特征进行比对
  5. 考勤记录模块记录识别结果和时间信息
  6. 数据分析模块生成考勤统计报表

核心技术实现

1. 人脸检测

人脸检测是整个系统的第一步,我们使用HOG(Histogram of Oriented Gradients)算法或基于深度学习的方法(如MTCNN、RetinaFace)来检测图像中的人脸。

import cv2
import dlib

# 使用dlib的人脸检测器
detector = dlib.get_frontal_face_detector()

def detect_faces(image):
    # 转换为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 检测人脸
    faces = detector(gray, 1)
    
    # 返回人脸位置列表
    face_locations = []
    for face in faces:
        x, y, w, h = face.left(), face.top(), face.width(), face.height()
        face_locations.append((y, x + w, y + h, x))
    
    return face_locations

2. 人脸特征提取

检测到人脸后,我们需要提取人脸的特征向量,这里使用深度学习模型(如FaceNet、ArcFace)来提取高维特征。

import face_recognition

def extract_face_features(image, face_locations):
    # 提取人脸特征
    face_encodings = face_recognition.face_encodings(image, face_locations)
    return face_encodings

3. 人脸识别

将提取的特征与数据库中已存储的特征进行比对,找出最匹配的身份。

def recognize_faces(face_encodings, known_face_encodings, known_face_names):
    recognized_names = []
    
    for face_encoding in face_encodings:
        # 比较人脸特征与已知特征的距离
        matches = face_recognition.compare_faces(known_face_encodings, face_encoding)
        name = "Unknown"
        
        # 找出距离最小的匹配
        face_distances = face_recognition.face_distance(known_face_encodings, face_encoding)
        best_match_index = np.argmin(face_distances)
        
        if matches[best_match_index]:
            name = known_face_names[best_match_index]
        
        recognized_names.append(name)
    
    return recognized_names

4. 考勤记录

识别到学生身份后,系统会自动记录考勤信息,包括学生ID、姓名、时间等。

import datetime
import sqlite3

def record_attendance(student_id, student_name):
    conn = sqlite3.connect('attendance.db')
    cursor = conn.cursor()
    
    # 获取当前时间
    now = datetime.datetime.now()
    date = now.strftime("%Y-%m-%d")
    time = now.strftime("%H:%M:%S")
    
    # 插入考勤记录
    cursor.execute("""
        INSERT INTO attendance (student_id, student_name, date, time)
        VALUES (?, ?, ?, ?)
    """, (student_id, student_name, date, time))
    
    conn.commit()
    conn.close()

系统集成

将上述模块集成到一个完整的系统中,下面是主程序的示例代码:

import cv2
import numpy as np
import face_recognition
import os
from datetime import datetime
import sqlite3

# 初始化数据库
def init_database():
    conn = sqlite3.connect('attendance.db')
    cursor = conn.cursor()
    
    # 创建学生表
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS students (
        id INTEGER PRIMARY KEY,
        student_id TEXT,
        name TEXT,
        face_encoding BLOB
    )
    ''')
    
    # 创建考勤记录表
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS attendance (
        id INTEGER PRIMARY KEY,
        student_id TEXT,
        student_name TEXT,
        date TEXT,
        time TEXT
    )
    ''')
    
    conn.commit()
    conn.close()

# 加载已知学生人脸特征
def load_known_faces():
    conn = sqlite3.connect('attendance.db')
    cursor = conn.cursor()
    
    cursor.execute("SELECT student_id, name, face_encoding FROM students")
    rows = cursor.fetchall()
    
    known_face_encodings = []
    known_face_ids = []
    known_face_names = []
    
    for row in rows:
        student_id, name, face_encoding_blob = row
        face_encoding = np.frombuffer(face_encoding_blob, dtype=np.float64)
        
        known_face_encodings.append(face_encoding)
        known_face_ids.append(student_id)
        known_face_names.append(name)
    
    conn.close()
    
    return known_face_encodings, known_face_ids, known_face_names

# 主程序
def main():
    # 初始化数据库
    init_database()
    
    # 加载已知人脸
    known_face_encodings, known_face_ids, known_face_names = load_known_faces()
    
    # 打开摄像头
    video_capture = cv2.VideoCapture(0)
    
    # 记录已识别的学生,避免重复记录
    recognized_students = set()
    
    while True:
        # 读取一帧视频
        ret, frame = video_capture.read()
        
        # 缩小图像以加快处理速度
        small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
        
        # 将BGR转换为RGB(face_recognition使用RGB)
        rgb_small_frame = small_frame[:, :, ::-1]
        
        # 检测人脸
        face_locations = face_recognition.face_locations(rgb_small_frame)
        face_encodings = face_recognition.face_encodings(rgb_small_frame, face_locations)
        
        face_names = []
        for face_encoding in face_encodings:
            # 比较人脸
            matches = face_recognition.compare_faces(known_face_encodings, face_encoding)
            name = "Unknown"
            student_id = "Unknown"
            
            # 找出最匹配的人脸
            face_distances = face_recognition.face_distance(known_face_encodings, face_encoding)
            best_match_index = np.argmin(face_distances)
            
            if matches[best_match_index]:
                name = known_face_names[best_match_index]
                student_id = known_face_ids[best_match_index]
                
                # 记录考勤
                if student_id not in recognized_students:
                    record_attendance(student_id, name)
                    recognized_students.add(student_id)
            
            face_names.append(name)
        
        # 显示结果
        for (top, right, bottom, left), name in zip(face_locations, face_names):
            # 放大回原始大小
            top *= 4
            right *= 4
            bottom *= 4
            left *= 4
            
            # 绘制人脸框
            cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)
            
            # 绘制名字标签
            cv2.rectangle(frame, (left, bottom - 35), (right, bottom), (0, 0, 255), cv2.FILLED)
            font = cv2.FONT_HERSHEY_DUPLEX
            cv2.putText(frame, name, (left + 6, bottom - 6), font, 1.0, (255, 255, 255), 1)
        
        # 显示结果图像
        cv2.imshow('Video', frame)
        
        # 按q退出
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    
    # 释放资源
    video_capture.release()
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()

Web界面实现

使用Flask框架构建Web界面,方便用户操作和查看考勤记录。

from flask import Flask, render_template, request, redirect, url_for
import sqlite3
import pandas as pd
import matplotlib.pyplot as plt
import io
import base64

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/attendance')
def attendance():
    conn = sqlite3.connect('attendance.db')
    
    # 获取考勤记录
    query = """
    SELECT student_id, student_name, date, time
    FROM attendance
    ORDER BY date DESC, time DESC
    """
    
    df = pd.read_sql_query(query, conn)
    conn.close()
    
    return render_template('attendance.html', records=df.to_dict('records'))

@app.route('/statistics')
def statistics():
    conn = sqlite3.connect('attendance.db')
    
    # 获取考勤统计
    query = """
    SELECT date, COUNT(DISTINCT student_id) as count
    FROM attendance
    GROUP BY date
    ORDER BY date
    """
    
    df = pd.read_sql_query(query, conn)
    conn.close()
    
    # 生成统计图表
    plt.figure(figsize=(10, 6))
    plt.bar(df['date'], df['count'])
    plt.xlabel('日期')
    plt.ylabel('出勤人数')
    plt.title('每日出勤统计')
    plt.xticks(rotation=45)
    
    # 将图表转换为base64编码
    img = io.BytesIO()
    plt.savefig(img, format='png')
    img.seek(0)
    plot_url = base64.b64encode(img.getvalue()).decode()
    
    return render_template('statistics.html', plot_url=plot_url)

if __name__ == '__main__':
    app.run(debug=True)

系统部署

环境配置

  1. 安装必要的Python库:
pip install opencv-python dlib face_recognition numpy flask pandas matplotlib
  1. 准备学生人脸数据库:
def register_new_student(student_id, name, image_path):
    # 加载图像
    image = face_recognition.load_image_file(image_path)
    
    # 检测人脸
    face_locations = face_recognition.face_locations(image)
    
    if len(face_locations) != 1:
        return False, "图像中没有检测到人脸或检测到多个人脸"
    
    # 提取人脸特征
    face_encoding = face_recognition.face_encodings(image, face_locations)[0]
    
    # 将特征存入数据库
    conn = sqlite3.connect('attendance.db')
    cursor = conn.cursor()
    
    cursor.execute("""
        INSERT INTO students (student_id, name, face_encoding)
        VALUES (?, ?, ?)
    """, (student_id, name, face_encoding.tobytes()))
    
    conn.commit()
    conn.close()
    
    return True, "学生注册成功"
  1. 启动系统:
python app.py

硬件要求

  • 摄像头:支持720p或更高分辨率
  • 处理器:建议Intel Core i5或更高性能
  • 内存:至少8GB RAM
  • 存储:至少100GB可用空间(用于存储学生数据和考勤记录)

系统优化与扩展

性能优化

  1. 模型压缩:使用模型量化和剪枝技术减小模型体积,提高推理速度
  2. GPU加速:利用GPU进行并行计算,加快人脸检测和识别过程
  3. 批处理:同时处理多个人脸,减少模型加载和初始化时间

功能扩展

  1. 活体检测:防止照片欺骗,提高系统安全性
  2. 表情识别:分析学生表情,评估课堂专注度
  3. 移动端应用:开发移动应用,支持远程考勤
  4. 多模态融合:结合声纹识别等多种生物特征,提高识别准确率

安全与隐私保护

在实施人脸识别系统时,必须高度重视用户隐私和数据安全:

  1. 数据加密:对存储的人脸特征和个人信息进行加密
  2. 权限控制:严格控制系统访问权限,防止未授权访问
  3. 数据最小化:只收集和存储必要的个人信息
  4. 透明度:向用户明确说明数据收集和使用方式
  5. 合规性:确保系统符合相关法律法规要求

结论

基于深度学习的校园人脸识别考勤系统是人工智能技术在教育领域的一个典型应用。通过整合计算机视觉、深度学习和Web开发技术,我们构建了一个高效、准确的自动考勤系统,不仅大大提高了考勤效率,还为教育管理提供了数据支持。

随着深度学习技术的不断发展,人脸识别系统的准确率和性能将进一步提升,应用场景也将更加广泛。同时,我们也需要关注系统在实际应用中可能面临的挑战,如隐私保护、环境适应性等问题,不断优化和完善系统功能。

源代码

Directory Content Summary

Source Directory: ./face_attendance_system

Directory Structure

face_attendance_system/
  app.py
  face_detection.py
  README.md
  requirements.txt
  database/
    db_setup.py
    init_db.py
    migrate.py
    models.py
  static/
    css/
      style.css
    js/
      main.js
    uploads/
  templates/
    attendance.html
    base.html
    dashboard.html
    edit_user.html
    face_recognition_attendance.html
    face_registration.html
    face_registration_admin.html
    index.html
    login.html
    register.html
    user_management.html
    webcam_registration.html

File Contents

app.py

import os
import numpy as np
import face_recognition
import cv2
from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify
from werkzeug.utils import secure_filename
import base64
from datetime import datetime
import json
import uuid
import shutil

# Import database models
from database.models import User, FaceEncoding, Attendance
from database.db_setup import init_database

# Initialize the Flask application
app = Flask(__name__)
app.secret_key = 'your_secret_key_here'  # Change this to a random secret key in production

# Initialize database
init_database()

# Configure upload folder
UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'uploads')
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16MB max upload size

# Allowed file extensions
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}

def allowed_file(filename):
    """Check if file has allowed extension"""
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/')
def index():
    """Home page route"""
    if 'user_id' in session:
        return redirect(url_for('dashboard'))
    return render_template('index.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    """Login route"""
    if request.method == 'POST':
        student_id = request.form.get('student_id')
        password = request.form.get('password')
        
        if not student_id or not password:
            flash('Please provide both student ID and password', 'danger')
            return render_template('login.html')
        
        user = User.authenticate(student_id, password)
        
        if user:
            session['user_id'] = user['id']
            session['student_id'] = user['student_id']
            session['name'] = user['name']
            flash(f'Welcome back, {user["name"]}!', 'success')
            return redirect(url_for('dashboard'))
        else:
            flash('Invalid student ID or password', 'danger')
    
    return render_template('login.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
    """User registration route"""
    if request.method == 'POST':
        student_id = request.form.get('student_id')
        name = request.form.get('name')
        email = request.form.get('email')
        password = request.form.get('password')
        confirm_password = request.form.get('confirm_password')
        
        # Validate input
        if not all([student_id, name, email, password, confirm_password]):
            flash('Please fill in all fields', 'danger')
            return render_template('register.html')
        
        if password != confirm_password:
            flash('Passwords do not match', 'danger')
            return render_template('register.html')
        
        # Check if student ID already exists
        existing_user = User.get_user_by_student_id(student_id)
        if existing_user:
            flash('Student ID already registered', 'danger')
            return render_template('register.html')
        
        # Create user
        user_id = User.create_user(student_id, name, email, password)
        
        if user_id:
            flash('Registration successful! Please login.', 'success')
            return redirect(url_for('login'))
        else:
            flash('Registration failed. Please try again.', 'danger')
    
    return render_template('register.html')

@app.route('/logout')
def logout():
    """Logout route"""
    session.clear()
    flash('You have been logged out', 'info')
    return redirect(url_for('index'))

@app.route('/dashboard')
def dashboard():
    """User dashboard route"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    user_id = session['user_id']
    user = User.get_user_by_id(user_id)
    
    # Get user's face encodings
    face_encodings = FaceEncoding.get_face_encodings_by_user_id(user_id)
    has_face_data = len(face_encodings) > 0
    
    # Get user's attendance records
    attendance_records = Attendance.get_attendance_by_user(user_id)
    
    return render_template('dashboard.html', 
                          user=user, 
                          has_face_data=has_face_data, 
                          attendance_records=attendance_records)

@app.route('/face-registration', methods=['GET', 'POST'])
def face_registration():
    """Face registration route"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    if request.method == 'POST':
        # Check if the post request has the file part
        if 'face_image' not in request.files:
            flash('No file part', 'danger')
            return redirect(request.url)
        
        file = request.files['face_image']
        
        # If user does not select file, browser also
        # submit an empty part without filename
        if file.filename == '':
            flash('No selected file', 'danger')
            return redirect(request.url)
        
        if file and allowed_file(file.filename):
            # Generate a unique filename
            filename = secure_filename(f"{session['student_id']}_{uuid.uuid4().hex}.jpg")
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
            file.save(filepath)
            
            # Process the image for face detection
            image = face_recognition.load_image_file(filepath)
            face_locations = face_recognition.face_locations(image)
            
            if not face_locations:
                os.remove(filepath)  # Remove the file if no face is detected
                flash('No face detected in the image. Please try again.', 'danger')
                return redirect(request.url)
            
            if len(face_locations) > 1:
                os.remove(filepath)  # Remove the file if multiple faces are detected
                flash('Multiple faces detected in the image. Please upload an image with only your face.', 'danger')
                return redirect(request.url)
            
            # Extract face encoding
            face_encoding = face_recognition.face_encodings(image, face_locations)[0]
            
            # Save face encoding to database
            encoding_id = FaceEncoding.save_face_encoding(session['user_id'], face_encoding)
            
            if encoding_id:
                flash('Face registered successfully!', 'success')
                return redirect(url_for('dashboard'))
            else:
                flash('Failed to register face. Please try again.', 'danger')
        else:
            flash('Invalid file type. Please upload a JPG, JPEG or PNG image.', 'danger')
    
    return render_template('face_registration.html')

@app.route('/webcam-registration', methods=['GET', 'POST'])
def webcam_registration():
    """Face registration using webcam"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    if request.method == 'POST':
        # Get the base64 encoded image from the request
        image_data = request.form.get('image_data')
        
        if not image_data:
            return jsonify({'success': False, 'message': 'No image data received'})
        
        # Remove the data URL prefix
        image_data = image_data.split(',')[1]
        
        # Decode the base64 image
        image_bytes = base64.b64decode(image_data)
        
        # Generate a unique filename
        filename = f"{session['student_id']}_{uuid.uuid4().hex}.jpg"
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        
        # Save the image
        with open(filepath, 'wb') as f:
            f.write(image_bytes)
        
        # Process the image for face detection
        image = face_recognition.load_image_file(filepath)
        face_locations = face_recognition.face_locations(image)
        
        if not face_locations:
            os.remove(filepath)  # Remove the file if no face is detected
            return jsonify({'success': False, 'message': 'No face detected in the image. Please try again.'})
        
        if len(face_locations) > 1:
            os.remove(filepath)  # Remove the file if multiple faces are detected
            return jsonify({'success': False, 'message': 'Multiple faces detected in the image. Please ensure only your face is visible.'})
        
        # Extract face encoding
        face_encoding = face_recognition.face_encodings(image, face_locations)[0]
        
        # Save face encoding to database
        encoding_id = FaceEncoding.save_face_encoding(session['user_id'], face_encoding)
        
        if encoding_id:
            return jsonify({'success': True, 'message': 'Face registered successfully!'})
        else:
            os.remove(filepath)
            return jsonify({'success': False, 'message': 'Failed to register face. Please try again.'})
    
    return render_template('webcam_registration.html')

@app.route('/webcam-registration-admin', methods=['POST'])
def webcam_registration_admin():
    """Process webcam registration for face data"""
    if 'user_id' not in session:
        return jsonify({'success': False, 'message': 'Please login first'})
    
    # Get image data from form
    image_data = request.form.get('image_data')
    user_id = request.form.get('user_id')
    
    if not image_data:
        return jsonify({'success': False, 'message': 'No image data provided'})
    
    # Check if user_id is provided (for admin registration)
    if not user_id:
        user_id = session['user_id']
    
    # Get user data
    user = User.get_user_by_id(user_id)
    if not user:
        return jsonify({'success': False, 'message': 'User not found'})
    
    try:
        # Remove header from the base64 string
        image_data = image_data.split(',')[1]
        
        # Decode base64 string to image
        image_bytes = base64.b64decode(image_data)
        
        # Create a temporary file to save the image
        temp_filepath = os.path.join(app.config['UPLOAD_FOLDER'], f"temp_{uuid.uuid4().hex}.jpg")
        with open(temp_filepath, 'wb') as f:
            f.write(image_bytes)
        
        # Process the image for face detection
        image = face_recognition.load_image_file(temp_filepath)
        face_locations = face_recognition.face_locations(image)
        
        if not face_locations:
            os.remove(temp_filepath)
            return jsonify({'success': False, 'message': 'No face detected in the image. Please try again.'})
        
        if len(face_locations) > 1:
            os.remove(temp_filepath)
            return jsonify({'success': False, 'message': 'Multiple faces detected in the image. Please ensure only one face is visible.'})
        
        # Extract face encoding
        face_encoding = face_recognition.face_encodings(image, face_locations)[0]
        
        # Save face encoding to database
        encoding_id = FaceEncoding.save_face_encoding(user_id, face_encoding)
        
        if encoding_id:
            # Save the processed image with a proper filename
            final_filename = secure_filename(f"{user['student_id']}_{uuid.uuid4().hex}.jpg")
            final_filepath = os.path.join(app.config['UPLOAD_FOLDER'], final_filename)
            shutil.copy(temp_filepath, final_filepath)
            
            # Remove temporary file
            os.remove(temp_filepath)
            
            return jsonify({'success': True, 'message': 'Face registered successfully!'})
        else:
            os.remove(temp_filepath)
            return jsonify({'success': False, 'message': 'Failed to register face. Please try again.'})
    
    except Exception as e:
        # Clean up if there was an error
        if os.path.exists(temp_filepath):
            os.remove(temp_filepath)
        return jsonify({'success': False, 'message': f'An error occurred: {str(e)}'})

@app.route('/attendance', methods=['GET'])
def attendance():
    """View attendance records"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    date = request.args.get('date', datetime.now().strftime('%Y-%m-%d'))
    
    attendance_records = Attendance.get_attendance_by_date(date)
    
    return render_template('attendance.html', 
                          attendance_records=attendance_records, 
                          selected_date=date)

@app.route('/check-in', methods=['GET'])
def check_in():
    """Manual check-in page"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    return render_template('check_in.html')

@app.route('/process-check-in', methods=['POST'])
def process_check_in():
    """Process manual check-in"""
    if 'user_id' not in session:
        return jsonify({'success': False, 'message': 'Please login first'})
    
    user_id = session['user_id']
    
    # Record check-in
    attendance_id = Attendance.record_check_in(user_id)
    
    if attendance_id:
        return jsonify({'success': True, 'message': 'Check-in successful!'})
    else:
        return jsonify({'success': False, 'message': 'You have already checked in today'})

@app.route('/check-out', methods=['POST'])
def check_out():
    """Process check-out"""
    if 'user_id' not in session:
        return jsonify({'success': False, 'message': 'Please login first'})
    
    user_id = session['user_id']
    
    # Record check-out
    success = Attendance.record_check_out(user_id)
    
    if success:
        return jsonify({'success': True, 'message': 'Check-out successful!'})
    else:
        return jsonify({'success': False, 'message': 'No active check-in found for today'})

@app.route('/face-recognition-attendance', methods=['GET'])
def face_recognition_attendance():
    """Face recognition attendance page"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    return render_template('face_recognition_attendance.html')

@app.route('/process-face-attendance', methods=['POST'])
def process_face_attendance():
    """Process face recognition attendance"""
    # Get the base64 encoded image from the request
    image_data = request.form.get('image_data')
    
    if not image_data:
        return jsonify({'success': False, 'message': 'No image data received'})
    
    # Remove the data URL prefix
    image_data = image_data.split(',')[1]
    
    # Decode the base64 image
    image_bytes = base64.b64decode(image_data)
    
    # Generate a temporary filename
    temp_filename = f"temp_{uuid.uuid4().hex}.jpg"
    temp_filepath = os.path.join(app.config['UPLOAD_FOLDER'], temp_filename)
    
    # Save the image
    with open(temp_filepath, 'wb') as f:
        f.write(image_bytes)
    
    try:
        # Process the image for face detection
        image = face_recognition.load_image_file(temp_filepath)
        face_locations = face_recognition.face_locations(image)
        
        if not face_locations:
            return jsonify({'success': False, 'message': 'No face detected in the image. Please try again.'})
        
        if len(face_locations) > 1:
            return jsonify({'success': False, 'message': 'Multiple faces detected. Please ensure only one person is in the frame.'})
        
        # Extract face encoding
        face_encoding = face_recognition.face_encodings(image, face_locations)[0]
        
        # Get all face encodings from database
        all_encodings = FaceEncoding.get_all_face_encodings()
        
        if not all_encodings:
            return jsonify({'success': False, 'message': 'No registered faces found in the database.'})
        
        # Compare with known face encodings
        known_encodings = [enc['encoding'] for enc in all_encodings]
        matches = face_recognition.compare_faces(known_encodings, face_encoding)
        
        if True in matches:
            # Find the matching index
            match_index = matches.index(True)
            matched_user = all_encodings[match_index]
            
            # Record attendance
            attendance_id = Attendance.record_check_in(matched_user['user_id'])
            
            if attendance_id:
                return jsonify({
                    'success': True, 
                    'message': f'Welcome, {matched_user["name"]}! Your attendance has been recorded.',
                    'user': {
                        'name': matched_user['name'],
                        'student_id': matched_user['student_id']
                    }
                })
            else:
                return jsonify({
                    'success': True, 
                    'message': f'Welcome back, {matched_user["name"]}! You have already checked in today.',
                    'user': {
                        'name': matched_user['name'],
                        'student_id': matched_user['student_id']
                    }
                })
        else:
            return jsonify({'success': False, 'message': 'Face not recognized. Please register your face or try again.'})
    
    finally:
        # Clean up the temporary file
        if os.path.exists(temp_filepath):
            os.remove(temp_filepath)

@app.route('/user-management', methods=['GET'])
def user_management():
    """User management route for admins"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    # Check if user is admin (in a real app, you would check user role)
    # For demo purposes, we'll allow all logged-in users to access this page
    
    # Get search query and pagination parameters
    search_query = request.args.get('search', '')
    page = int(request.args.get('page', 1))
    per_page = 10
    
    # Get users based on search query
    if search_query:
        users = User.search_users(search_query, page, per_page)
        total_users = User.count_search_results(search_query)
    else:
        users = User.get_all_users(page, per_page)
        total_users = User.count_all_users()
    
    # Calculate total pages
    total_pages = (total_users + per_page - 1) // per_page
    
    # Check if each user has face data
    for user in users:
        face_encodings = FaceEncoding.get_face_encodings_by_user_id(user['id'])
        user['has_face_data'] = len(face_encodings) > 0
    
    return render_template('user_management.html',
                          users=users,
                          search_query=search_query,
                          current_page=page,
                          total_pages=total_pages)

@app.route('/edit-user/', methods=['GET', 'POST'])
def edit_user(user_id):
    """Edit user route"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    # Check if user is admin (in a real app, you would check user role)
    # For demo purposes, we'll allow all logged-in users to access this page
    
    # Get user data
    user = User.get_user_by_id(user_id)
    if not user:
        flash('User not found', 'danger')
        return redirect(url_for('user_management'))
    
    # Check if user has face data
    face_encodings = FaceEncoding.get_face_encodings_by_user_id(user_id)
    user['has_face_data'] = len(face_encodings) > 0
    
    if request.method == 'POST':
        student_id = request.form.get('student_id')
        name = request.form.get('name')
        email = request.form.get('email')
        password = request.form.get('password')
        role = request.form.get('role')
        is_active = 'is_active' in request.form
        
        # Update user
        success = User.update_user(user_id, student_id, name, email, password, role, is_active)
        
        if success:
            flash('User updated successfully', 'success')
            return redirect(url_for('user_management'))
        else:
            flash('Failed to update user', 'danger')
    
    return render_template('edit_user.html', user=user)

@app.route('/delete-user/', methods=['POST'])
def delete_user(user_id):
    """Delete user route"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    # Check if user is admin (in a real app, you would check user role)
    # For demo purposes, we'll allow all logged-in users to access this page
    
    # Delete user
    success = User.delete_user(user_id)
    
    if success:
        flash('User deleted successfully', 'success')
    else:
        flash('Failed to delete user', 'danger')
    
    return redirect(url_for('user_management'))

@app.route('/reset-face-data/', methods=['POST'])
def reset_face_data(user_id):
    """Reset user's face data"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    # Check if user is admin (in a real app, you would check user role)
    # For demo purposes, we'll allow all logged-in users to access this page
    
    # Delete face encodings
    success = FaceEncoding.delete_face_encodings_by_user_id(user_id)
    
    if success:
        flash('Face data reset successfully', 'success')
    else:
        flash('Failed to reset face data', 'danger')
    
    return redirect(url_for('edit_user', user_id=user_id))

@app.route('/face-registration-admin/', methods=['GET', 'POST'])
def face_registration_admin(user_id):
    """Face registration for admin to register user's face"""
    if 'user_id' not in session:
        flash('Please login first', 'warning')
        return redirect(url_for('login'))
    
    # Check if user is admin (in a real app, you would check user role)
    # For demo purposes, we'll allow all logged-in users to access this page
    
    # Get user data
    user = User.get_user_by_id(user_id)
    if not user:
        flash('User not found', 'danger')
        return redirect(url_for('user_management'))
    
    if request.method == 'POST':
        # Check if the post request has the file part
        if 'face_image' not in request.files:
            flash('No file part', 'danger')
            return redirect(request.url)
        
        file = request.files['face_image']
        
        # If user does not select file, browser also
        # submit an empty part without filename
        if file.filename == '':
            flash('No selected file', 'danger')
            return redirect(request.url)
        
        if file and allowed_file(file.filename):
            # Generate a unique filename
            filename = secure_filename(f"{user['student_id']}_{uuid.uuid4().hex}.jpg")
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
            file.save(filepath)
            
            # Process the image for face detection
            image = face_recognition.load_image_file(filepath)
            face_locations = face_recognition.face_locations(image)
            
            if not face_locations:
                os.remove(filepath)  # Remove the file if no face is detected
                flash('No face detected in the image. Please try again.', 'danger')
                return redirect(request.url)
            
            if len(face_locations) > 1:
                os.remove(filepath)  # Remove the file if multiple faces are detected
                flash('Multiple faces detected in the image. Please upload an image with only one face.', 'danger')
                return redirect(request.url)
            
            # Extract face encoding
            face_encoding = face_recognition.face_encodings(image, face_locations)[0]
            
            # Save face encoding to database
            encoding_id = FaceEncoding.save_face_encoding(user_id, face_encoding)
            
            if encoding_id:
                flash('Face registered successfully!', 'success')
                return redirect(url_for('edit_user', user_id=user_id))
            else:
                flash('Failed to register face. Please try again.', 'danger')
        else:
            flash('Invalid file type. Please upload a JPG, JPEG or PNG image.', 'danger')
    
    return render_template('face_registration_admin.html', user=user)

@app.route('/detect-face', methods=['POST'])
def detect_face():
    """检测人脸API"""
    if 'image_data' not in request.form:
        return jsonify({'success': False, 'message': '未提供图像数据'})
    
    # 获取图像数据
    image_data = request.form.get('image_data')
    
    try:
        # 移除base64头部
        if ',' in image_data:
            image_data = image_data.split(',')[1]
        
        # 解码base64图像
        image_bytes = base64.b64decode(image_data)
        nparr = np.frombuffer(image_bytes, np.uint8)
        image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        
        # 转换为RGB(OpenCV使用BGR)
        rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        
        # 检测人脸
        face_locations = face_recognition.face_locations(rgb_image)
        
        return jsonify({
            'success': True,
            'message': '人脸检测完成',
            'face_count': len(face_locations)
        })
    
    except Exception as e:
        app.logger.error(f"人脸检测错误: {str(e)}")
        return jsonify({'success': False, 'message': f'处理图像时出错: {str(e)}'})

@app.route('/recognize-face', methods=['POST'])
def recognize_face():
    """识别人脸API"""
    if 'image_data' not in request.form:
        return jsonify({'success': False, 'message': '未提供图像数据'})
    
    # 获取图像数据
    image_data = request.form.get('image_data')
    
    try:
        # 移除base64头部
        if ',' in image_data:
            image_data = image_data.split(',')[1]
        
        # 解码base64图像
        image_bytes = base64.b64decode(image_data)
        nparr = np.frombuffer(image_bytes, np.uint8)
        image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        
        # 转换为RGB(OpenCV使用BGR)
        rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        
        # 检测人脸
        face_locations = face_recognition.face_locations(rgb_image)
        
        if not face_locations:
            return jsonify({'success': False, 'message': '未检测到人脸,请确保脸部清晰可见'})
        
        if len(face_locations) > 1:
            return jsonify({'success': False, 'message': '检测到多个人脸,请确保画面中只有一个人脸'})
        
        # 提取人脸特征
        face_encoding = face_recognition.face_encodings(rgb_image, face_locations)[0]
        
        # 加载所有已知人脸编码
        known_faces = FaceEncoding.get_all_face_encodings()
        
        if not known_faces:
            return jsonify({'success': False, 'message': '数据库中没有注册的人脸'})
        
        # 比较人脸
        known_encodings = [face['encoding'] for face in known_faces]
        matches = face_recognition.compare_faces(known_encodings, face_encoding)
        face_distances = face_recognition.face_distance(known_encodings, face_encoding)
        
        if True in matches:
            # 找到最佳匹配
            best_match_index = np.argmin(face_distances)
            confidence = 1 - face_distances[best_match_index]
            
            if confidence >= 0.6:  # 置信度阈值
                matched_user = known_faces[best_match_index]
                
                # 返回识别结果
                return jsonify({
                    'success': True,
                    'message': f'成功识别为 {matched_user["name"]}',
                    'user': {
                        'user_id': matched_user['user_id'],
                        'student_id': matched_user['student_id'],
                        'name': matched_user['name']
                    },
                    'confidence': float(confidence)
                })
            else:
                return jsonify({'success': False, 'message': '识别置信度过低,请重新尝试'})
        else:
            return jsonify({'success': False, 'message': '无法识别您的身份,请确保您已注册人脸数据'})
    
    except Exception as e:
        app.logger.error(f"人脸识别错误: {str(e)}")
        return jsonify({'success': False, 'message': f'处理图像时出错: {str(e)}'})

@app.route('/record-attendance', methods=['POST'])
def record_attendance():
    """记录考勤API"""
    if 'user_id' not in session:
        return jsonify({'success': False, 'message': '请先登录'})
    
    # 获取请求数据
    data = request.get_json()
    
    if not data or 'user_id' not in data:
        return jsonify({'success': False, 'message': '无效的请求数据'})
    
    user_id = data.get('user_id')
    confidence = data.get('confidence', 0)
    
    # 验证用户身份(确保当前登录用户只能为自己签到)
    if int(session['user_id']) != int(user_id) and session.get('role') != 'admin':
        return jsonify({'success': False, 'message': '无权为其他用户签到'})
    
    # 检查是否已经签到
    today_attendance = Attendance.get_today_attendance(user_id)
    if today_attendance:
        return jsonify({'success': False, 'message': '今天已经签到,无需重复签到'})
    
    # 记录考勤
    attendance_id = Attendance.record_check_in(user_id)
    
    if attendance_id:
        # 获取用户信息
        user = User.get_user_by_id(user_id)
        
        return jsonify({
            'success': True,
            'message': f'签到成功!欢迎 {user["name"]}',
            'attendance_id': attendance_id,
            'check_in_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        })
    else:
        return jsonify({'success': False, 'message': '签到失败,请稍后重试'})

@app.route('/get-recent-attendance', methods=['GET'])
def get_recent_attendance():
    """获取最近考勤记录API"""
    if 'user_id' not in session:
        return jsonify({'success': False, 'message': '请先登录'})
    
    # 获取最近的考勤记录(默认10条)
    limit = request.args.get('limit', 10, type=int)
    records = Attendance.get_recent_attendance(limit)
    
    return jsonify({
        'success': True,
        'records': records
    })

if __name__ == '__main__':
    app.run(debug=True)

face_detection.py

import cv2
import face_recognition
import numpy as np
import os
import pickle
from datetime import datetime
import time
import logging

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class FaceDetector:
    """人脸检测与识别类"""
    
    def __init__(self, model_type='hog', tolerance=0.6, known_faces=None):
        """
        初始化人脸检测器
        
        参数:
            model_type (str): 使用的模型类型,'hog'(CPU)或'cnn'(GPU)
            tolerance (float): 人脸匹配的容差值,越小越严格
            known_faces (list): 已知人脸编码和对应用户信息的列表
        """
        self.model_type = model_type
        self.tolerance = tolerance
        self.known_faces = known_faces or []
        logger.info(f"人脸检测器初始化完成,使用{model_type}模型,容差值为{tolerance}")
    
    def load_known_faces(self, known_faces):
        """
        加载已知人脸数据
        
        参数:
            known_faces (list): 包含人脸编码和用户信息的列表
        """
        self.known_faces = known_faces
        logger.info(f"已加载{len(known_faces)}个已知人脸")
    
    def detect_faces(self, image):
        """
        检测图像中的人脸位置
        
        参数:
            image: 图像数据,可以是文件路径或图像数组
            
        返回:
            list: 人脸位置列表,每个位置为(top, right, bottom, left)
        """
        # 如果是文件路径,加载图像
        if isinstance(image, str):
            if not os.path.exists(image):
                logger.error(f"图像文件不存在: {image}")
                return []
            image = face_recognition.load_image_file(image)
        
        # 检测人脸位置
        start_time = time.time()
        face_locations = face_recognition.face_locations(image, model=self.model_type)
        detection_time = time.time() - start_time
        
        logger.info(f"检测到{len(face_locations)}个人脸,耗时{detection_time:.4f}秒")
        return face_locations
    
    def encode_faces(self, image, face_locations=None):
        """
        提取图像中人脸的编码特征
        
        参数:
            image: 图像数据,可以是文件路径或图像数组
            face_locations: 可选,人脸位置列表
            
        返回:
            list: 人脸编码特征列表
        """
        # 如果是文件路径,加载图像
        if isinstance(image, str):
            if not os.path.exists(image):
                logger.error(f"图像文件不存在: {image}")
                return []
            image = face_recognition.load_image_file(image)
        
        # 如果没有提供人脸位置,先检测人脸
        if face_locations is None:
            face_locations = self.detect_faces(image)
        
        if not face_locations:
            logger.warning("未检测到人脸,无法提取特征")
            return []
        
        # 提取人脸编码特征
        start_time = time.time()
        face_encodings = face_recognition.face_encodings(image, face_locations)
        encoding_time = time.time() - start_time
        
        logger.info(f"提取了{len(face_encodings)}个人脸特征,耗时{encoding_time:.4f}秒")
        return face_encodings
    
    def recognize_faces(self, face_encodings):
        """
        识别人脸,匹配已知人脸
        
        参数:
            face_encodings: 待识别的人脸编码特征列表
            
        返回:
            list: 识别结果列表,每个结果为(user_info, confidence)或(None, 0)
        """
        if not self.known_faces:
            logger.warning("没有已知人脸数据,无法进行识别")
            return [(None, 0) for _ in face_encodings]
        
        if not face_encodings:
            logger.warning("没有提供人脸特征,无法进行识别")
            return []
        
        results = []
        
        # 提取已知人脸的编码和用户信息
        known_encodings = [face['encoding'] for face in self.known_faces]
        
        for face_encoding in face_encodings:
            # 计算与已知人脸的距离
            face_distances = face_recognition.face_distance(known_encodings, face_encoding)
            
            if len(face_distances) > 0:
                # 找到最小距离及其索引
                best_match_index = np.argmin(face_distances)
                best_match_distance = face_distances[best_match_index]
                
                # 计算置信度(1 - 距离)
                confidence = 1 - best_match_distance
                
                # 如果距离小于容差,认为匹配成功
                if best_match_distance <= self.tolerance:
                    user_info = {
                        'user_id': self.known_faces[best_match_index]['user_id'],
                        'student_id': self.known_faces[best_match_index]['student_id'],
                        'name': self.known_faces[best_match_index]['name']
                    }
                    results.append((user_info, confidence))
                    logger.info(f"识别到用户: {user_info['name']},置信度: {confidence:.4f}")
                else:
                    results.append((None, confidence))
                    logger.info(f"未能识别人脸,最佳匹配置信度: {confidence:.4f},低于阈值")
            else:
                results.append((None, 0))
                logger.warning("没有已知人脸数据进行比较")
        
        return results
    
    def process_image(self, image):
        """
        处理图像,检测、编码并识别人脸
        
        参数:
            image: 图像数据,可以是文件路径或图像数组
            
        返回:
            tuple: (face_locations, recognition_results)
        """
        # 检测人脸
        face_locations = self.detect_faces(image)
        
        if not face_locations:
            return [], []
        
        # 提取人脸编码
        face_encodings = self.encode_faces(image, face_locations)
        
        # 识别人脸
        recognition_results = self.recognize_faces(face_encodings)
        
        return face_locations, recognition_results
    
    def process_video_frame(self, frame):
        """
        处理视频帧,检测、编码并识别人脸
        
        参数:
            frame: 视频帧图像数组
            
        返回:
            tuple: (face_locations, recognition_results)
        """
        # 将BGR格式转换为RGB格式(OpenCV使用BGR,face_recognition使用RGB)
        rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        
        # 为提高性能,可以缩小图像
        small_frame = cv2.resize(rgb_frame, (0, 0), fx=0.25, fy=0.25)
        
        # 检测人脸
        face_locations = self.detect_faces(small_frame)
        
        # 调整人脸位置坐标到原始尺寸
        original_face_locations = []
        for top, right, bottom, left in face_locations:
            original_face_locations.append(
                (top * 4, right * 4, bottom * 4, left * 4)
            )
        
        if not original_face_locations:
            return [], []
        
        # 提取人脸编码(使用原始尺寸的图像)
        face_encodings = self.encode_faces(rgb_frame, original_face_locations)
        
        # 识别人脸
        recognition_results = self.recognize_faces(face_encodings)
        
        return original_face_locations, recognition_results
    
    def draw_results(self, image, face_locations, recognition_results):
        """
        在图像上绘制人脸检测和识别结果
        
        参数:
            image: 图像数组
            face_locations: 人脸位置列表
            recognition_results: 识别结果列表
            
        返回:
            image: 绘制结果后的图像
        """
        # 复制图像,避免修改原图
        result_image = image.copy()
        
        # 遍历每个人脸
        for i, (top, right, bottom, left) in enumerate(face_locations):
            if i < len(recognition_results):
                user_info, confidence = recognition_results[i]
                
                # 绘制人脸框
                if user_info:  # 识别成功
                    color = (0, 255, 0)  # 绿色
                else:  # 识别失败
                    color = (0, 0, 255)  # 红色
                
                cv2.rectangle(result_image, (left, top), (right, bottom), color, 2)
                
                # 绘制文本背景
                cv2.rectangle(result_image, (left, bottom - 35), (right, bottom), color, cv2.FILLED)
                
                # 绘制文本
                if user_info:
                    text = f"{user_info['name']} ({confidence:.2f})"
                else:
                    text = f"Unknown ({confidence:.2f})"
                
                cv2.putText(result_image, text, (left + 6, bottom - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)
        
        return result_image
    
    @staticmethod
    def save_face_image(image, face_location, output_path):
        """
        保存人脸图像
        
        参数:
            image: 图像数组
            face_location: 人脸位置 (top, right, bottom, left)
            output_path: 输出文件路径
            
        返回:
            bool: 是否保存成功
        """
        try:
            top, right, bottom, left = face_location
            
            # 扩大人脸区域,包含更多背景
            height, width = image.shape[:2]
            margin = int((bottom - top) * 0.5)  # 使用人脸高度的50%作为边距
            
            # 确保不超出图像边界
            top = max(0, top - margin)
            bottom = min(height, bottom + margin)
            left = max(0, left - margin)
            right = min(width, right + margin)
            
            # 裁剪人脸区域
            face_image = image[top:bottom, left:right]
            
            # 保存图像
            cv2.imwrite(output_path, face_image)
            logger.info(f"人脸图像已保存到: {output_path}")
            return True
        except Exception as e:
            logger.error(f"保存人脸图像失败: {e}")
            return False


def test_face_detector():
    """测试人脸检测器功能"""
    # 创建人脸检测器
    detector = FaceDetector()
    
    # 测试图像路径
    test_image_path = "test_image.jpg"
    
    # 检测人脸
    face_locations = detector.detect_faces(test_image_path)
    print(f"检测到 {len(face_locations)} 个人脸")
    
    # 提取人脸编码
    face_encodings = detector.encode_faces(test_image_path, face_locations)
    print(f"提取了 {len(face_encodings)} 个人脸特征")
    
    # 加载图像并绘制结果
    image = cv2.imread(test_image_path)
    result_image = detector.draw_results(image, face_locations, [(None, 0.5) for _ in face_locations])
    
    # 显示结果
    cv2.imshow("Face Detection Results", result_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == "__main__":
    test_face_detector()

README.md

# 校园人脸识别考勤系统

基于深度学习的校园人脸识别考勤系统,使用Python、Flask、OpenCV和face_recognition库开发。

## 功能特点

- 用户管理:注册、登录、编辑和删除用户
- 人脸识别:通过摄像头或上传图片进行人脸识别
- 考勤管理:记录和查询考勤信息
- 课程管理:创建课程和管理课程考勤
- 权限控制:区分管理员和普通用户权限

## 技术栈

- **后端**:Python、Flask
- **前端**:HTML、CSS、JavaScript、Bootstrap 5
- **数据库**:SQLite
- **人脸识别**:face_recognition、OpenCV
- **其他**:NumPy、Pickle

## 安装指南

1. 克隆仓库
```bash
git clone https://github.com/yourusername/face-attendance-system.git
cd face-attendance-system
  1. 创建虚拟环境
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate
  1. 安装依赖
pip install -r requirements.txt
  1. 初始化数据库
python database/init_db.py
  1. 运行应用
python app.py
  1. 访问应用
    在浏览器中访问 http://localhost:5000

系统要求

  • Python 3.7+
  • 摄像头(用于人脸识别)
  • 现代浏览器(Chrome、Firefox、Edge等)

默认管理员账户

  • 学号:admin
  • 密码:admin123

项目结构

face_attendance_system/
├── app.py                  # 主应用入口
├── face_detection.py       # 人脸检测和识别模块
├── requirements.txt        # 项目依赖
├── README.md               # 项目说明
├── database/               # 数据库相关
│   ├── init_db.py          # 数据库初始化
│   ├── migrate.py          # 数据库迁移
│   └── models.py           # 数据模型
├── static/                 # 静态资源
│   ├── css/                # CSS样式
│   ├── js/                 # JavaScript脚本
│   └── uploads/            # 上传文件存储
│       └── faces/          # 人脸图像存储
└── templates/              # HTML模板
    ├── base.html           # 基础模板
    ├── login.html          # 登录页面
    ├── register.html       # 注册页面
    ├── user_management.html # 用户管理页面
    ├── edit_user.html      # 编辑用户页面
    ├── face_registration_admin.html # 管理员人脸注册页面
    ├── webcam_registration.html # 摄像头人脸注册页面
    └── face_recognition_attendance.html # 人脸识别考勤页面

许可证

MIT License


### requirements.txt

```text/plain
Flask==2.0.1
Werkzeug==2.0.1
Jinja2==3.0.1
itsdangerous==2.0.1
MarkupSafe==2.0.1
numpy==1.21.0
opencv-python==4.5.3.56
face-recognition==1.3.0
face-recognition-models==0.3.0
dlib==19.22.1
Pillow==8.3.1

database\db_setup.py

import sqlite3
import os

# Database directory
DB_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.path.join(DB_DIR, 'attendance.db')

def init_database():
    """Initialize the database with necessary tables"""
    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()
    
    # Create users table
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        student_id TEXT UNIQUE NOT NULL,
        name TEXT NOT NULL,
        email TEXT UNIQUE,
        password TEXT NOT NULL,
        registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
    ''')
    
    # Create face_encodings table
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS face_encodings (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        encoding BLOB NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (user_id) REFERENCES users (id)
    )
    ''')
    
    # Create attendance table
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS attendance (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        check_in_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        check_out_time TIMESTAMP,
        date TEXT,
        FOREIGN KEY (user_id) REFERENCES users (id)
    )
    ''')
    
    conn.commit()
    conn.close()
    
    print("Database initialized successfully!")

if __name__ == "__main__":
    init_database()

database\init_db.py

import sqlite3
import os

# Database path
DB_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.path.join(DB_DIR, 'attendance.db')

def init_database():
    """Initialize database with required tables"""
    print("Initializing database...")
    
    # Connect to database
    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()
    
    try:
        # Create users table
        cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            student_id TEXT UNIQUE NOT NULL,
            name TEXT NOT NULL,
            email TEXT NOT NULL,
            password TEXT NOT NULL,
            registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            role TEXT DEFAULT 'student',
            is_active INTEGER DEFAULT 1
        )
        ''')
        
        # Create face_encodings table
        cursor.execute('''
        CREATE TABLE IF NOT EXISTS face_encodings (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            encoding BLOB NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
        )
        ''')
        
        # Create attendance table
        cursor.execute('''
        CREATE TABLE IF NOT EXISTS attendance (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            check_in_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            check_out_time TIMESTAMP,
            status TEXT DEFAULT 'present',
            FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
        )
        ''')
        
        # Create courses table
        cursor.execute('''
        CREATE TABLE IF NOT EXISTS courses (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            course_code TEXT UNIQUE NOT NULL,
            course_name TEXT NOT NULL,
            instructor TEXT NOT NULL,
            schedule TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        ''')
        
        # Create course_enrollments table
        cursor.execute('''
        CREATE TABLE IF NOT EXISTS course_enrollments (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            course_id INTEGER NOT NULL,
            user_id INTEGER NOT NULL,
            enrollment_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (course_id) REFERENCES courses (id) ON DELETE CASCADE,
            FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
            UNIQUE(course_id, user_id)
        )
        ''')
        
        # Create course_attendance table
        cursor.execute('''
        CREATE TABLE IF NOT EXISTS course_attendance (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            course_id INTEGER NOT NULL,
            user_id INTEGER NOT NULL,
            attendance_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            status TEXT DEFAULT 'present',
            FOREIGN KEY (course_id) REFERENCES courses (id) ON DELETE CASCADE,
            FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
        )
        ''')
        
        # Create admin user if not exists
        cursor.execute("SELECT id FROM users WHERE role = 'admin' LIMIT 1")
        if not cursor.fetchone():
            import hashlib
            admin_password = hashlib.sha256('admin123'.encode()).hexdigest()
            cursor.execute('''
            INSERT INTO users (student_id, name, email, password, role)
            VALUES (?, ?, ?, ?, ?)
            ''', ('admin', 'System Administrator', '[email protected]', admin_password, 'admin'))
            print("Created default admin user (student_id: admin, password: admin123)")
        
        conn.commit()
        print("Database initialized successfully.")
    
    except Exception as e:
        print(f"Error during initialization: {e}")
        conn.rollback()
    
    finally:
        conn.close()

if __name__ == '__main__':
    init_database()

database\migrate.py

import sqlite3
import os
import sys

# Database path
DB_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.path.join(DB_DIR, 'attendance.db')

def check_column_exists(cursor, table_name, column_name):
    """Check if a column exists in a table"""
    cursor.execute(f"PRAGMA table_info({table_name})")
    columns = cursor.fetchall()
    for column in columns:
        if column[1] == column_name:
            return True
    return False

def migrate_database():
    """Migrate database to latest schema"""
    print("Starting database migration...")
    
    # Connect to database
    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()
    
    try:
        # Check if database exists
        cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
        if not cursor.fetchone():
            print("Database not initialized. Please run init_db.py first.")
            conn.close()
            sys.exit(1)
        
        # Add role column to users table if it doesn't exist
        if not check_column_exists(cursor, 'users', 'role'):
            print("Adding 'role' column to users table...")
            cursor.execute("ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'student'")
            conn.commit()
            print("Added 'role' column to users table.")
        
        # Add is_active column to users table if it doesn't exist
        if not check_column_exists(cursor, 'users', 'is_active'):
            print("Adding 'is_active' column to users table...")
            cursor.execute("ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1")
            conn.commit()
            print("Added 'is_active' column to users table.")
        
        # Check if face_encodings table has the correct schema
        cursor.execute("PRAGMA table_info(face_encodings)")
        columns = cursor.fetchall()
        encoding_column_type = None
        for column in columns:
            if column[1] == 'encoding':
                encoding_column_type = column[2]
                break
        
        # If encoding column is not BLOB, we need to recreate the table
        if encoding_column_type != 'BLOB':
            print("Updating face_encodings table schema...")
            
            # Create a backup of the face_encodings table
            cursor.execute("CREATE TABLE IF NOT EXISTS face_encodings_backup AS SELECT * FROM face_encodings")
            
            # Drop the original table
            cursor.execute("DROP TABLE face_encodings")
            
            # Create the table with the correct schema
            cursor.execute('''
            CREATE TABLE face_encodings (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                encoding BLOB NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
            )
            ''')
            
            # Note: We can't restore the data because the encoding format has changed
            # from numpy array bytes to pickle serialized data
            
            print("Updated face_encodings table schema. Note: Previous face encodings have been backed up but not restored.")
            print("Users will need to re-register their faces.")
        
        print("Database migration completed successfully.")
    
    except Exception as e:
        print(f"Error during migration: {e}")
        conn.rollback()
    
    finally:
        conn.close()

if __name__ == '__main__':
    migrate_database()

database\models.py

import sqlite3
import os
import numpy as np
import hashlib
import pickle
from datetime import datetime

# Database path
DB_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.path.join(DB_DIR, 'attendance.db')

class User:
    """User model for handling user-related database operations"""
    
    @staticmethod
    def create_user(student_id, name, email, password):
        """Create a new user"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        # Hash the password
        hashed_password = hashlib.sha256(password.encode()).hexdigest()
        
        try:
            cursor.execute('''
            INSERT INTO users (student_id, name, email, password)
            VALUES (?, ?, ?, ?)
            ''', (student_id, name, email, hashed_password))
            
            conn.commit()
            user_id = cursor.lastrowid
            conn.close()
            
            return user_id
        except sqlite3.IntegrityError:
            conn.close()
            return None
    
    @staticmethod
    def get_user_by_id(user_id):
        """Get user by ID"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        cursor.execute('''
        SELECT id, student_id, name, email, registration_date, role, is_active
        FROM users
        WHERE id = ?
        ''', (user_id,))
        
        user = cursor.fetchone()
        conn.close()
        
        if user:
            return {
                'id': user[0],
                'student_id': user[1],
                'name': user[2],
                'email': user[3],
                'registration_date': user[4],
                'role': user[5] if len(user) > 5 else 'student',
                'is_active': bool(user[6]) if len(user) > 6 else True
            }
        return None
    
    @staticmethod
    def get_user_by_student_id(student_id):
        """Get user by student ID"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        cursor.execute('''
        SELECT id, student_id, name, email, registration_date, role, is_active
        FROM users
        WHERE student_id = ?
        ''', (student_id,))
        
        user = cursor.fetchone()
        conn.close()
        
        if user:
            return {
                'id': user[0],
                'student_id': user[1],
                'name': user[2],
                'email': user[3],
                'registration_date': user[4],
                'role': user[5] if len(user) > 5 else 'student',
                'is_active': bool(user[6]) if len(user) > 6 else True
            }
        return None
    
    @staticmethod
    def authenticate(student_id, password):
        """Authenticate a user"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        # Hash the password
        hashed_password = hashlib.sha256(password.encode()).hexdigest()
        
        cursor.execute('''
        SELECT id, student_id, name, email, registration_date, role, is_active
        FROM users
        WHERE student_id = ? AND password = ?
        ''', (student_id, hashed_password))
        
        user = cursor.fetchone()
        conn.close()
        
        if user:
            return {
                'id': user[0],
                'student_id': user[1],
                'name': user[2],
                'email': user[3],
                'registration_date': user[4],
                'role': user[5] if len(user) > 5 else 'student',
                'is_active': bool(user[6]) if len(user) > 6 else True
            }
        return None
    
    @staticmethod
    def get_all_users(page=1, per_page=10):
        """Get all users"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        offset = (page - 1) * per_page
        cursor.execute('''
        SELECT id, student_id, name, email, registration_date, role, is_active
        FROM users
        ORDER BY id DESC
        LIMIT ? OFFSET ?
        ''', (per_page, offset))
        
        users = cursor.fetchall()
        conn.close()
        
        result = []
        for user in users:
            result.append({
                'id': user[0],
                'student_id': user[1],
                'name': user[2],
                'email': user[3],
                'registration_date': user[4],
                'role': user[5] if len(user) > 5 else 'student',
                'is_active': bool(user[6]) if len(user) > 6 else True
            })
        
        return result
    
    @staticmethod
    def count_all_users():
        """Count all users"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        cursor.execute('''
        SELECT COUNT(*)
        FROM users
        ''')
        
        count = cursor.fetchone()[0]
        conn.close()
        
        return count
    
    @staticmethod
    def search_users(query, page=1, per_page=10):
        """Search users"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        offset = (page - 1) * per_page
        search_query = f"%{query}%"
        cursor.execute('''
        SELECT id, student_id, name, email, registration_date, role, is_active
        FROM users
        WHERE student_id LIKE ? OR name LIKE ?
        ORDER BY id DESC
        LIMIT ? OFFSET ?
        ''', (search_query, search_query, per_page, offset))
        
        users = cursor.fetchall()
        conn.close()
        
        result = []
        for user in users:
            result.append({
                'id': user[0],
                'student_id': user[1],
                'name': user[2],
                'email': user[3],
                'registration_date': user[4],
                'role': user[5] if len(user) > 5 else 'student',
                'is_active': bool(user[6]) if len(user) > 6 else True
            })
        
        return result
    
    @staticmethod
    def count_search_results(query):
        """Count search results"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        search_query = f"%{query}%"
        cursor.execute('''
        SELECT COUNT(*)
        FROM users
        WHERE student_id LIKE ? OR name LIKE ?
        ''', (search_query, search_query))
        
        count = cursor.fetchone()[0]
        conn.close()
        
        return count
    
    @staticmethod
    def update_user(user_id, student_id, name, email, password=None, role='student', is_active=True):
        """Update user"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        try:
            if password:
                hashed_password = hashlib.sha256(password.encode()).hexdigest()
                cursor.execute('''
                UPDATE users
                SET student_id = ?, name = ?, email = ?, password = ?, role = ?, is_active = ?
                WHERE id = ?
                ''', (student_id, name, email, hashed_password, role, is_active, user_id))
            else:
                cursor.execute('''
                UPDATE users
                SET student_id = ?, name = ?, email = ?, role = ?, is_active = ?
                WHERE id = ?
                ''', (student_id, name, email, role, is_active, user_id))
            
            conn.commit()
            return True
        except Exception as e:
            print(f"Error updating user: {e}")
            return False
    
    @staticmethod
    def delete_user(user_id):
        """Delete user"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        try:
            # Delete user's face encodings
            cursor.execute('''
            DELETE FROM face_encodings
            WHERE user_id = ?
            ''', (user_id,))
            
            # Delete user's attendance records
            cursor.execute('''
            DELETE FROM attendance
            WHERE user_id = ?
            ''', (user_id,))
            
            # Delete user
            cursor.execute('''
            DELETE FROM users
            WHERE id = ?
            ''', (user_id,))
            
            conn.commit()
            return True
        except Exception as e:
            print(f"Error deleting user: {e}")
            return False

class FaceEncoding:
    """Face encoding model for handling face-related database operations"""
    
    @staticmethod
    def save_face_encoding(user_id, face_encoding):
        """Save a face encoding for a user"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        # Convert numpy array to bytes for storage
        encoding_bytes = pickle.dumps(face_encoding)
        
        cursor.execute('''
        INSERT INTO face_encodings (user_id, encoding)
        VALUES (?, ?)
        ''', (user_id, encoding_bytes))
        
        conn.commit()
        encoding_id = cursor.lastrowid
        conn.close()
        
        return encoding_id
    
    @staticmethod
    def get_face_encodings_by_user_id(user_id):
        """Get face encodings for a specific user"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        cursor.execute('''
        SELECT id, user_id, encoding
        FROM face_encodings
        WHERE user_id = ?
        ''', (user_id,))
        
        encodings = cursor.fetchall()
        conn.close()
        
        result = []
        for encoding in encodings:
            # Convert bytes back to numpy array
            face_encoding = pickle.loads(encoding[2])
            result.append({
                'id': encoding[0],
                'user_id': encoding[1],
                'encoding': face_encoding
            })
        
        return result
    
    @staticmethod
    def get_all_face_encodings():
        """Get all face encodings with user information"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        cursor.execute('''
        SELECT f.id, f.user_id, f.encoding, u.student_id, u.name
        FROM face_encodings f
        JOIN users u ON f.user_id = u.id
        ''')
        
        encodings = cursor.fetchall()
        conn.close()
        
        result = []
        for encoding in encodings:
            # Convert bytes back to numpy array
            face_encoding = pickle.loads(encoding[2])
            result.append({
                'id': encoding[0],
                'user_id': encoding[1],
                'encoding': face_encoding,
                'student_id': encoding[3],
                'name': encoding[4]
            })
        
        return result
    
    @staticmethod
    def delete_face_encodings_by_user_id(user_id):
        """Delete face encodings for a specific user"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        try:
            cursor.execute('''
            DELETE FROM face_encodings
            WHERE user_id = ?
            ''', (user_id,))
            
            conn.commit()
            return True
        except Exception as e:
            print(f"Error deleting face encodings: {e}")
            return False

class Attendance:
    """Attendance model for handling attendance-related database operations"""
    
    @staticmethod
    def record_check_in(user_id):
        """Record attendance check-in"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        today = datetime.now().strftime('%Y-%m-%d')
        
        # Check if user already checked in today
        cursor.execute('''
        SELECT id FROM attendance
        WHERE user_id = ? AND date = ? AND check_out_time IS NULL
        ''', (user_id, today))
        
        existing = cursor.fetchone()
        
        if existing:
            conn.close()
            return False
        
        cursor.execute('''
        INSERT INTO attendance (user_id, date)
        VALUES (?, ?)
        ''', (user_id, today))
        
        conn.commit()
        attendance_id = cursor.lastrowid
        conn.close()
        
        return attendance_id
    
    @staticmethod
    def record_check_out(user_id):
        """Record attendance check-out"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        today = datetime.now().strftime('%Y-%m-%d')
        now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        
        cursor.execute('''
        UPDATE attendance
        SET check_out_time = ?
        WHERE user_id = ? AND date = ? AND check_out_time IS NULL
        ''', (now, user_id, today))
        
        affected = cursor.rowcount
        conn.commit()
        conn.close()
        
        return affected > 0
    
    @staticmethod
    def get_attendance_by_date(date):
        """Get attendance records for a specific date"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        cursor.execute('''
        SELECT a.id, a.user_id, u.student_id, u.name, a.check_in_time, a.check_out_time
        FROM attendance a
        JOIN users u ON a.user_id = u.id
        WHERE a.date = ?
        ORDER BY a.check_in_time DESC
        ''', (date,))
        
        records = cursor.fetchall()
        conn.close()
        
        result = []
        for record in records:
            result.append({
                'id': record[0],
                'user_id': record[1],
                'student_id': record[2],
                'name': record[3],
                'check_in_time': record[4],
                'check_out_time': record[5]
            })
        
        return result
    
    @staticmethod
    def get_attendance_by_user(user_id):
        """Get attendance records for a specific user"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        cursor.execute('''
        SELECT id, date, check_in_time, check_out_time
        FROM attendance
        WHERE user_id = ?
        ORDER BY date DESC, check_in_time DESC
        ''', (user_id,))
        
        records = cursor.fetchall()
        conn.close()
        
        result = []
        for record in records:
            result.append({
                'id': record[0],
                'date': record[1],
                'check_in_time': record[2],
                'check_out_time': record[3]
            })
        
        return result
    
    @staticmethod
    def get_today_attendance(user_id):
        """Get user's attendance for today"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        # Get today's date (format: YYYY-MM-DD)
        today = datetime.now().strftime('%Y-%m-%d')
        
        cursor.execute('''
        SELECT id, user_id, check_in_time, check_out_time, status
        FROM attendance
        WHERE user_id = ? AND date(check_in_time) = ?
        ''', (user_id, today))
        
        attendance = cursor.fetchone()
        conn.close()
        
        if attendance:
            return {
                'id': attendance[0],
                'user_id': attendance[1],
                'check_in_time': attendance[2],
                'check_out_time': attendance[3],
                'status': attendance[4]
            }
        return None
    
    @staticmethod
    def get_recent_attendance(limit=10):
        """Get recent attendance records"""
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        cursor.execute('''
        SELECT a.id, a.user_id, a.check_in_time, a.status, u.student_id, u.name
        FROM attendance a
        JOIN users u ON a.user_id = u.id
        ORDER BY a.check_in_time DESC
        LIMIT ?
        ''', (limit,))
        
        attendances = cursor.fetchall()
        conn.close()
        
        result = []
        for attendance in attendances:
            result.append({
                'id': attendance[0],
                'user_id': attendance[1],
                'check_in_time': attendance[2],
                'status': attendance[3],
                'student_id': attendance[4],
                'name': attendance[5]
            })
        
        return result

static\css\style.css

/* 全局样式 */
body {
    background-color: #f8f9fa;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

/* 导航栏样式 */
.navbar {
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.navbar-brand {
    font-weight: 600;
}

/* 卡片样式 */
.card {
    border: none;
    border-radius: 10px;
    overflow: hidden;
    margin-bottom: 20px;
    transition: transform 0.3s, box-shadow 0.3s;
}

.card:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}

.card-header {
    font-weight: 600;
    border-bottom: none;
}

/* 按钮样式 */
.btn {
    border-radius: 5px;
    font-weight: 500;
    padding: 8px 16px;
    transition: all 0.3s;
}

.btn-primary {
    background-color: #4e73df;
    border-color: #4e73df;
}

.btn-primary:hover {
    background-color: #2e59d9;
    border-color: #2e59d9;
}

.btn-success {
    background-color: #1cc88a;
    border-color: #1cc88a;
}

.btn-success:hover {
    background-color: #17a673;
    border-color: #17a673;
}

.btn-info {
    background-color: #36b9cc;
    border-color: #36b9cc;
}

.btn-info:hover {
    background-color: #2c9faf;
    border-color: #2c9faf;
}

/* 表单样式 */
.form-control {
    border-radius: 5px;
    padding: 10px 15px;
    border: 1px solid #d1d3e2;
}

.form-control:focus {
    border-color: #4e73df;
    box-shadow: 0 0 0 0.25rem rgba(78, 115, 223, 0.25);
}

.input-group-text {
    background-color: #f8f9fc;
    border: 1px solid #d1d3e2;
}

/* 摄像头容器 */
#camera-container, #captured-container {
    position: relative;
    width: 100%;
    max-width: 640px;
    margin: 0 auto;
    border-radius: 10px;
    overflow: hidden;
}

#webcam, #captured-image {
    width: 100%;
    height: auto;
    border-radius: 10px;
}

/* 考勤信息样式 */
#attendance-info, #recognition-result {
    transition: all 0.3s ease;
}

/* 动画效果 */
.fade-in {
    animation: fadeIn 0.5s;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* 响应式调整 */
@media (max-width: 768px) {
    .card-body {
        padding: 1rem;
    }
    
    .btn {
        padding: 6px 12px;
    }
}

/* 页脚样式 */
footer {
    margin-top: 3rem;
    padding: 1.5rem 0;
    color: #6c757d;
    border-top: 1px solid #e3e6f0;
}

static\js\main.js

// 全局工具函数

// 格式化日期时间
function formatDateTime(dateString) {
    const date = new Date(dateString);
    return date.toLocaleString();
}

// 格式化日期
function formatDate(dateString) {
    const date = new Date(dateString);
    return date.toLocaleDateString();
}

// 格式化时间
function formatTime(dateString) {
    const date = new Date(dateString);
    return date.toLocaleTimeString();
}

// 显示加载中状态
function showLoading(element, message = '加载中...') {
    element.innerHTML = `
        
Loading...

${message}

`; } // 显示错误消息 function showError(element, message) { element.innerHTML = ` `; } // 显示成功消息 function showSuccess(element, message) { element.innerHTML = ` `; } // 显示警告消息 function showWarning(element, message) { element.innerHTML = ` `; } // 显示信息消息 function showInfo(element, message) { element.innerHTML = ` `; } // 复制文本到剪贴板 function copyToClipboard(text) { const textarea = document.createElement('textarea'); textarea.value = text; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); } // 防抖函数 function debounce(func, wait) { let timeout; return function(...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; } // 节流函数 function throttle(func, limit) { let inThrottle; return function(...args) { const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // 文档就绪事件 document.addEventListener('DOMContentLoaded', function() { // 初始化工具提示 const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function(tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); // 初始化弹出框 const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); popoverTriggerList.map(function(popoverTriggerEl) { return new bootstrap.Popover(popoverTriggerEl); }); // 处理闪现消息自动消失 const flashMessages = document.querySelectorAll('.alert-dismissible'); flashMessages.forEach(function(message) { setTimeout(function() { const alert = bootstrap.Alert.getInstance(message); if (alert) { alert.close(); } else { message.classList.add('fade'); setTimeout(() => message.remove(), 500); } }, 5000); }); // 处理表单验证 const forms = document.querySelectorAll('.needs-validation'); Array.from(forms).forEach(function(form) { form.addEventListener('submit', function(event) { if (!form.checkValidity()) { event.preventDefault(); event.stopPropagation(); } form.classList.add('was-validated'); }, false); }); // 处理返回顶部按钮 const backToTopButton = document.getElementById('back-to-top'); if (backToTopButton) { window.addEventListener('scroll', function() { if (window.pageYOffset > 300) { backToTopButton.classList.add('show'); } else { backToTopButton.classList.remove('show'); } }); backToTopButton.addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }); }); } // 处理侧边栏切换 const sidebarToggle = document.getElementById('sidebar-toggle'); if (sidebarToggle) { sidebarToggle.addEventListener('click', function() { document.body.classList.toggle('sidebar-collapsed'); localStorage.setItem('sidebar-collapsed', document.body.classList.contains('sidebar-collapsed')); }); // 从本地存储恢复侧边栏状态 if (localStorage.getItem('sidebar-collapsed') === 'true') { document.body.classList.add('sidebar-collapsed'); } } // 处理暗黑模式切换 const darkModeToggle = document.getElementById('dark-mode-toggle'); if (darkModeToggle) { darkModeToggle.addEventListener('click', function() { document.body.classList.toggle('dark-mode'); localStorage.setItem('dark-mode', document.body.classList.contains('dark-mode')); }); // 从本地存储恢复暗黑模式状态 if (localStorage.getItem('dark-mode') === 'true') { document.body.classList.add('dark-mode'); } } });

templates\attendance.html

{% extends 'base.html' %}

{% block title %}考勤记录 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

考勤记录

{% if attendance_records %}
{% for record in attendance_records %} {% endfor %}
学号 姓名 签到时间 签退时间 状态 时长
{{ record.student_id }} {{ record.name }} {{ record.check_in_time }} {{ record.check_out_time if record.check_out_time else '未签退' }} {% if record.check_out_time %} 已完成 {% else %} 进行中 {% endif %} {% if record.check_out_time %} {% set check_in = record.check_in_time.split(' ')[1] %} {% set check_out = record.check_out_time.split(' ')[1] %} {% set hours = (check_out.split(':')[0]|int - check_in.split(':')[0]|int) %} {% set minutes = (check_out.split(':')[1]|int - check_in.split(':')[1]|int) %} {% if minutes < 0 %} {% set hours = hours - 1 %} {% set minutes = minutes + 60 %} {% endif %} {{ hours }}小时{{ minutes }}分钟 {% else %} - {% endif %}
考勤统计

{{ attendance_records|length }}

总人数

{% set completed = 0 %} {% for record in attendance_records %} {% if record.check_out_time %} {% set completed = completed + 1 %} {% endif %} {% endfor %} {{ completed }}

已完成

{% set in_progress = 0 %} {% for record in attendance_records %} {% if not record.check_out_time %} {% set in_progress = in_progress + 1 %} {% endif %} {% endfor %} {{ in_progress }}

进行中

图表统计
{% else %}
{{ selected_date }} 没有考勤记录
{% endif %}
{% endblock %} {% block extra_js %} {% endblock %}

templates\base.html




    
    
    {% block title %}校园人脸识别考勤系统{% endblock %}
    
    
    
    
    
    
    {% block extra_css %}{% endblock %}


    
    

    
    
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} {% endfor %} {% endif %} {% endwith %}
{% block content %}{% endblock %}

© {{ now.year }} 校园人脸识别考勤系统 | 基于深度学习的智能考勤解决方案

{% block extra_js %}{% endblock %}

templates\dashboard.html

{% extends 'base.html' %}

{% block title %}控制面板 - 校园人脸识别考勤系统{% endblock %}

{% block content %}
个人信息
{% if has_face_data %}

人脸数据已注册

{% else %}

尚未注册人脸数据

立即注册 {% endif %}
学号 {{ user.student_id }}
姓名 {{ user.name }}
邮箱 {{ user.email }}
注册日期 {{ user.registration_date }}
快速考勤
考勤记录
{% if attendance_records %}
{% for record in attendance_records %} {% endfor %}
日期 签到时间 签退时间 状态
{{ record.date }} {{ record.check_in_time }} {{ record.check_out_time if record.check_out_time else '未签退' }} {% if record.check_out_time %} 已完成 {% else %} 进行中 {% endif %}
{% else %}
暂无考勤记录
{% endif %}
{% endblock %} {% block extra_css %} {% endblock %} {% block extra_js %} {% endblock %}

templates\edit_user.html

{% extends 'base.html' %}

{% block title %}编辑用户 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

编辑用户

如需重置密码,请在此输入新密码
{% endblock %}

templates\face_recognition_attendance.html

{% extends 'base.html' %}

{% block title %}人脸识别考勤 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

人脸识别考勤

请面向摄像头,系统将自动识别您的身份
请确保光线充足,面部无遮挡
准备中...

{% endblock %} {% block extra_css %} {% endblock %} {% block extra_js %} {% endblock %}

templates\face_registration.html

{% extends 'base.html' %}

{% block title %}人脸注册 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

人脸注册

上传照片
请上传清晰的正面照片,确保光线充足,面部无遮挡
预览图
使用摄像头
已拍摄照片
{% endblock %} {% block extra_js %} {% endblock %}

templates\face_registration_admin.html

{% extends 'base.html' %}

{% block title %}管理员人脸注册 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

为用户注册人脸数据

用户信息

学号: {{ user.student_id }}

姓名: {{ user.name }}

邮箱: {{ user.email }}

注册日期: {{ user.registration_date }}

上传照片
请上传清晰的正面照片,确保光线充足,面部无遮挡
预览图
使用摄像头
已拍摄照片
{% endblock %} {% block extra_js %} {% endblock %}

templates\index.html

{% extends 'base.html' %}

{% block title %}首页 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

智能校园考勤系统

基于深度学习的人脸识别技术,为校园考勤带来全新体验。告别传统签到方式,实现快速、准确、高效的智能考勤管理。

{% if session.get('user_id') %} 开始考勤 控制面板 {% else %} 登录系统 注册账号 {% endif %}
人脸识别技术

系统特点

快速识别

采用先进的深度学习算法,实现毫秒级人脸识别,大幅提高考勤效率。

安全可靠

人脸特征加密存储,确保用户隐私安全,防止冒名顶替,提高考勤准确性。

数据分析

自动生成考勤统计报表,提供直观的数据可视化,辅助教学管理决策。

使用流程

1

注册账号

创建个人账号,填写基本信息

2

人脸录入

上传照片或使用摄像头采集人脸数据

3

日常考勤

通过人脸识别快速完成签到签退

4

查看记录

随时查看个人考勤记录和统计数据

{% endblock %} {% block extra_css %} {% endblock %}

templates\login.html

{% extends 'base.html' %}

{% block title %}登录 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

用户登录

人脸识别登录

您也可以使用人脸识别功能直接考勤

人脸识别考勤
{% endblock %}

templates\register.html

{% extends 'base.html' %}

{% block title %}注册 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

用户注册

请输入您的学号,将作为登录账号使用
请输入有效的电子邮箱,用于接收系统通知
密码长度至少为6位,包含字母和数字
请再次输入密码进行确认
{% endblock %} {% block extra_js %} {% endblock %}

templates\user_management.html

{% extends 'base.html' %}

{% block title %}用户管理 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

用户管理

{% if users %}
{% for user in users %} {% endfor %}
学号 姓名 邮箱 注册日期 人脸数据 操作
{{ user.student_id }} {{ user.name }} {{ user.email }} {{ user.registration_date }} {% if user.has_face_data %} 已注册 {% else %} 未注册 {% endif %}
{% if not user.has_face_data %} {% endif %}
{% if total_pages > 1 %} {% endif %} {% else %}
没有找到用户记录
{% endif %}
{% endblock %} {% block extra_js %} {% endblock %}

templates\webcam_registration.html

{% extends 'base.html' %}

{% block title %}摄像头人脸注册 - 校园人脸识别考勤系统{% endblock %}

{% block content %}

摄像头人脸注册

请面向摄像头,确保光线充足,面部清晰可见
请保持自然表情,正面面对摄像头
已拍摄照片
请点击下方按钮启动摄像头
{% endblock %} {% block extra_css %} {% endblock %} {% block extra_js %} {% endblock %}

你可能感兴趣的:(Python项目实战,python)