ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

【免费】人脸识别 智能考勤系统(深度学习+OpenCV DNN+FastAPI+Vue3) 锋哥原创出品,必属精品

【免费】人脸识别 智能考勤系统(深度学习+OpenCV DNN+FastAPI+Vue3) 锋哥原创出品,必属精品

大家好,我是Java1234_小锋老师,分享一套锋哥原创的人脸识别 智能考勤系统(深度学习+OpenCV DNN+FastAPI+Vue3)

项目介绍

随着深度学习与计算机视觉技术的快速发展,人脸识别逐渐成为身份认证与智能考勤领域的重要手段。传统考勤方式普遍存在代打卡、效率低、统计不便等问题,难以满足现代企事业单位精细化管理的需要。本文围绕“带人脸识别的智能考勤系统设计与实现”这一课题,设计并实现了一套前后端分离的智能考勤系统。系统后端采用 Python 语言与 FastAPI 框架构建 RESTful 接口服务,使用 SQLAlchemy 访问 MySQL 数据库;前端采用 Vue3、Element Plus、Pinia、Axios 与 ECharts 实现管理后台与刷脸打卡页面;人脸识别核心基于 OpenCV DNN 模块,结合 YuNet 人脸检测模型与 SFace 人脸识别模型,完成人脸定位、对齐、128 维特征提取与余弦相似度比对。系统实现了管理员登录与个人中心、部门岗位员工班次管理、人脸库注册、刷脸签到签退、考勤状态自动判定、请假加班审批、识别日志查询以及首页数据统计等功能。测试结果表明,系统运行稳定,识别流程清晰,能够有效提升考勤管理效率,具有较好的实用价值与推广意义。

源码下载

链接: https://pan.baidu.com/s/1xjca762MfAu-4zCuoG7jrQ?pwd=1234
提取码: 1234

系统展示

核心代码

"""考勤业务服务:打卡判定与统计。""" from datetime import date, datetime, timedelta from decimal import Decimal from typing import Optional from sqlalchemy import func from sqlalchemy.orm import Session from app.models.attendance import Attendance from app.models.department import Department from app.models.employee import Employee from app.models.recognition_log import RecognitionLog from app.models.shift import Shift class AttendanceService: """考勤业务服务类。""" @staticmethod def _combine_datetime(d: date, t) -> datetime: """将日期与时间组合为 datetime。""" return datetime.combine(d, t) def punch( self, db: Session, employee: Employee, similarity: float, image_path: str, ) -> Attendance: """处理员工刷脸打卡(签到/签退)。""" today = date.today() now = datetime.now() record = ( db.query(Attendance) .filter( Attendance.employee_id == employee.id, Attendance.attendance_date == today, ) .first() ) shift = None if employee.shift_id: shift = db.query(Shift).filter(Shift.id == employee.shift_id).first() if not record: status = self._calc_check_in_status(now, shift) record = Attendance( employee_id=employee.id, attendance_date=today, check_in_time=now, check_in_image=image_path, status=status, similarity=Decimal(str(round(similarity, 4))), ) db.add(record) else: if record.check_out_time: raise ValueError("今日已完成签退,无需重复打卡") status = self._calc_check_out_status(now, shift, record.status) record.check_out_time = now record.check_out_image = image_path record.status = status if record.check_in_time: hours = (record.check_out_time - record.check_in_time).total_seconds() / 3600 record.work_hours = Decimal(str(round(hours, 2))) record.similarity = Decimal(str(round(similarity, 4))) db.commit() db.refresh(record) return record def _calc_check_in_status(self, check_time: datetime, shift: Optional[Shift]) -> str: """根据班次判定签到状态。""" if not shift: return "正常" start_dt = self._combine_datetime(check_time.date(), shift.start_time) tolerance = timedelta(minutes=shift.late_tolerance or 0) if check_time <= start_dt + tolerance: return "正常" return "迟到" def _calc_check_out_status( self, check_time: datetime, shift: Optional[Shift], current_status: str ) -> str: """根据班次判定签退状态。""" if not shift: return current_status if current_status != "迟到" else "迟到" end_dt = self._combine_datetime(check_time.date(), shift.end_time) tolerance = timedelta(minutes=shift.early_tolerance or 0) is_early = check_time < end_dt - tolerance if current_status == "迟到" and is_early: return "迟到且早退" if current_status == "迟到": return "迟到" if is_early: return "早退" return "正常" def get_dashboard_stats(self, db: Session) -> dict: """获取首页统计数据。""" today = date.today() total_employees = db.query(Employee).filter(Employee.status == 1).count() today_attendance = ( db.query(Attendance) .filter(Attendance.attendance_date == today, Attendance.check_in_time.isnot(None)) .count() ) today_late = ( db.query(Attendance) .filter( Attendance.attendance_date == today, Attendance.status.in_(["迟到", "迟到且早退"]), ) .count() ) from app.models.face import Face face_count = db.query(Face).count() total_logs = db.query(RecognitionLog).count() success_logs = db.query(RecognitionLog).filter(RecognitionLog.success == 1).count() success_rate = round(success_logs / total_logs * 100, 1) if total_logs else 0 trend = [] for i in range(6, -1, -1): d = today - timedelta(days=i) count = ( db.query(Attendance) .filter(Attendance.attendance_date == d, Attendance.check_in_time.isnot(None)) .count() ) trend.append({"date": d.strftime("%Y-%m-%d"), "count": count}) status_stats = [] for status_name in ["正常", "迟到", "早退", "迟到且早退", "缺卡"]: cnt = ( db.query(Attendance) .filter(Attendance.attendance_date == today, Attendance.status == status_name) .count() ) status_stats.append({"name": status_name, "value": cnt}) dept_stats = [] departments = db.query(Department).filter(Department.status == 1).all() for dept in departments: cnt = ( db.query(Employee) .filter(Employee.department_id == dept.id, Employee.status == 1) .count() ) dept_stats.append({"name": dept.name, "value": cnt}) return { "total_employees": total_employees, "today_attendance": today_attendance, "today_late": today_late, "face_count": face_count, "success_rate": success_rate, "trend": trend, "status_stats": status_stats, "dept_stats": dept_stats, } attendance_service = AttendanceService()
<template> <div class="page-container"> <div class="page-header"> <h2 class="page-title">岗位管理</h2> <el-button type="primary" @click="openDialog()">新增岗位</el-button> </div> <div class="card-panel"> <div class="search-bar"> <el-input v-model="query.keyword" placeholder="搜索岗位名称/编码" clearable style="width: 240px" @clear="loadData" /> <el-button type="primary" @click="loadData">搜索</el-button> </div> <el-table :data="tableData" stripe border> <el-table-column prop="name" label="岗位名称" min-width="140" show-overflow-tooltip /> <el-table-column prop="code" label="岗位编码" min-width="120" show-overflow-tooltip /> <el-table-column prop="sort_order" label="排序" min-width="80" /> <el-table-column label="状态" min-width="80"> <template #default="{ row }"> <el-tag :type="row.status === 1 ? 'success' : 'danger'">{{ row.status === 1 ? '启用' : '禁用' }}</el-tag> </template> </el-table-column> <el-table-column label="操作" min-width="160" fixed="right"> <template #default="{ row }"> <el-button link type="primary" @click="openDialog(row)">编辑</el-button> <el-button link type="danger" @click="handleDelete(row)">删除</el-button> </template> </el-table-column> </el-table> <el-pagination class="pagination" v-model:current-page="query.page" v-model:page-size="query.page_size" :total="total" layout="total, sizes, prev, pager, next" @change="loadData" /> </div> <el-dialog v-model="dialogVisible" :title="form.id ? '编辑岗位' : '新增岗位'" width="500px"> <el-form :model="form" label-width="80px"> <el-form-item label="名称"><el-input v-model="form.name" /></el-form-item> <el-form-item label="编码"><el-input v-model="form.code" /></el-form-item> <el-form-item label="排序"><el-input-number v-model="form.sort_order" :min="0" /></el-form-item> <el-form-item label="状态"><el-switch v-model="form.status" :active-value="1" :inactive-value="0" /></el-form-item> </el-form> <template #footer> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="handleSave">确定</el-button> </template> </el-dialog> </div> </template> <script setup> /** 岗位管理页面 */ import { ref, reactive, onMounted } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { positionApi } from '@/api' const tableData = ref([]); const total = ref(0); const dialogVisible = ref(false) const query = reactive({ page: 1, page_size: 10, keyword: '' }) const form = reactive({ id: null, name: '', code: '', sort_order: 0, status: 1 }) async function loadData() { const res = await positionApi.list(query); tableData.value = res.data.list; total.value = res.data.total } function openDialog(row) { Object.assign(form, row ? { ...row } : { id: null, name: '', code: '', sort_order: 0, status: 1 }); dialogVisible.value = true } async function handleSave() { form.id ? await positionApi.update(form.id, form) : await positionApi.create(form); ElMessage.success('保存成功'); dialogVisible.value = false; loadData() } async function handleDelete(row) { await ElMessageBox.confirm('确定删除?', '提示', { type: 'warning' }); await positionApi.remove(row.id); ElMessage.success('删除成功'); loadData() } onMounted(loadData) </script> <style scoped>.pagination { margin-top: 16px; justify-content: flex-end; }</style>
返回列表