随着人工智能技术的快速发展,深度学习在计算机视觉领域的应用日益广泛。人脸识别作为其中的一个重要分支,已经在安防、金融、教育等多个领域展现出巨大的应用价值。本文将详细介绍如何使用Python和深度学习技术构建一个校园人脸识别考勤系统,该系统能够自动识别学生身份并记录考勤信息,大大提高了考勤效率,减轻了教师的工作负担。
系统采用经典的三层架构设计:
人脸检测是整个系统的第一步,我们使用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
检测到人脸后,我们需要提取人脸的特征向量,这里使用深度学习模型(如FaceNet、ArcFace)来提取高维特征。
import face_recognition
def extract_face_features(image, face_locations):
# 提取人脸特征
face_encodings = face_recognition.face_encodings(image, face_locations)
return face_encodings
将提取的特征与数据库中已存储的特征进行比对,找出最匹配的身份。
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
识别到学生身份后,系统会自动记录考勤信息,包括学生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()
使用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)
pip install opencv-python dlib face_recognition numpy flask pandas matplotlib
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, "学生注册成功"
python app.py
在实施人脸识别系统时,必须高度重视用户隐私和数据安全:
基于深度学习的校园人脸识别考勤系统是人工智能技术在教育领域的一个典型应用。通过整合计算机视觉、深度学习和Web开发技术,我们构建了一个高效、准确的自动考勤系统,不仅大大提高了考勤效率,还为教育管理提供了数据支持。
随着深度学习技术的不断发展,人脸识别系统的准确率和性能将进一步提升,应用场景也将更加广泛。同时,我们也需要关注系统在实际应用中可能面临的挑战,如隐私保护、环境适应性等问题,不断优化和完善系统功能。
Source Directory: ./face_attendance_system
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
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)
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()
# 校园人脸识别考勤系统
基于深度学习的校园人脸识别考勤系统,使用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
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python database/init_db.py
python app.py
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
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()
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()
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()
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
/* 全局样式 */
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;
}
// 全局工具函数
// 格式化日期时间
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 = `
${message}
`;
}
// 显示成功消息
function showSuccess(element, message) {
element.innerHTML = `
${message}
`;
}
// 显示警告消息
function showWarning(element, message) {
element.innerHTML = `
${message}
`;
}
// 显示信息消息
function showInfo(element, message) {
element.innerHTML = `
${message}
`;
}
// 复制文本到剪贴板
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');
}
}
});
{% extends 'base.html' %}
{% block title %}考勤记录 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
考勤记录
{% if attendance_records %}
学号
姓名
签到时间
签退时间
状态
时长
{% for record in attendance_records %}
{{ 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 %}
{% endfor %}
考勤统计
{{ 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 %}
{% block title %}校园人脸识别考勤系统{% endblock %}
{% block extra_css %}{% endblock %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
{{ message }}
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
{% block extra_js %}{% endblock %}
{% extends 'base.html' %}
{% block title %}控制面板 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
个人信息
学号
{{ user.student_id }}
姓名
{{ user.name }}
邮箱
{{ user.email }}
注册日期
{{ user.registration_date }}
快速考勤
考勤记录
{% if attendance_records %}
日期
签到时间
签退时间
状态
{% for record in attendance_records %}
{{ record.date }}
{{ record.check_in_time }}
{{ record.check_out_time if record.check_out_time else '未签退' }}
{% if record.check_out_time %}
已完成
{% else %}
进行中
{% endif %}
{% endfor %}
{% else %}
暂无考勤记录
{% endif %}
{% endblock %}
{% block extra_css %}
{% endblock %}
{% block extra_js %}
{% endblock %}
{% extends 'base.html' %}
{% block title %}编辑用户 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
编辑用户
{% endblock %}
{% extends 'base.html' %}
{% block title %}人脸识别考勤 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
人脸识别考勤
请面向摄像头,系统将自动识别您的身份
请确保光线充足,面部无遮挡
准备中...
{% endblock %}
{% block extra_css %}
{% endblock %}
{% block extra_js %}
{% endblock %}
{% extends 'base.html' %}
{% block title %}人脸注册 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
人脸注册
上传照片
使用摄像头
{% endblock %}
{% block extra_js %}
{% endblock %}
{% extends 'base.html' %}
{% block title %}管理员人脸注册 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
为用户注册人脸数据
用户信息
学号: {{ user.student_id }}
姓名: {{ user.name }}
邮箱: {{ user.email }}
注册日期: {{ user.registration_date }}
上传照片
使用摄像头
{% endblock %}
{% block extra_js %}
{% endblock %}
{% extends 'base.html' %}
{% block title %}首页 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
智能校园考勤系统
基于深度学习的人脸识别技术,为校园考勤带来全新体验。告别传统签到方式,实现快速、准确、高效的智能考勤管理。
系统特点
快速识别
采用先进的深度学习算法,实现毫秒级人脸识别,大幅提高考勤效率。
安全可靠
人脸特征加密存储,确保用户隐私安全,防止冒名顶替,提高考勤准确性。
数据分析
自动生成考勤统计报表,提供直观的数据可视化,辅助教学管理决策。
使用流程
1
注册账号
创建个人账号,填写基本信息
2
人脸录入
上传照片或使用摄像头采集人脸数据
3
日常考勤
通过人脸识别快速完成签到签退
4
查看记录
随时查看个人考勤记录和统计数据
{% endblock %}
{% block extra_css %}
{% endblock %}
{% extends 'base.html' %}
{% block title %}登录 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
用户登录
人脸识别登录
您也可以使用人脸识别功能直接考勤
人脸识别考勤
{% endblock %}
{% extends 'base.html' %}
{% block title %}注册 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
用户注册
{% endblock %}
{% block extra_js %}
{% endblock %}
{% extends 'base.html' %}
{% block title %}用户管理 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
用户管理
{% if users %}
学号
姓名
邮箱
注册日期
人脸数据
操作
{% for user in users %}
{{ user.student_id }}
{{ user.name }}
{{ user.email }}
{{ user.registration_date }}
{% if user.has_face_data %}
已注册
{% else %}
未注册
{% endif %}
{% endfor %}
{% if total_pages > 1 %}
{% endif %}
{% else %}
没有找到用户记录
{% endif %}
{% endblock %}
{% block extra_js %}
{% endblock %}
{% extends 'base.html' %}
{% block title %}摄像头人脸注册 - 校园人脸识别考勤系统{% endblock %}
{% block content %}
摄像头人脸注册
请面向摄像头,确保光线充足,面部清晰可见
请保持自然表情,正面面对摄像头
请点击下方按钮启动摄像头
{% endblock %}
{% block extra_css %}
{% endblock %}
{% block extra_js %}
{% endblock %}