当前位置: 首页 > news >正文

【免费】人脸识别 智能考勤系统(深度学习+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>
http://www.jsqmd.com/news/1341078/

相关文章:

  • 一站搞定站酷海洛、Shutterstock高清原图下载,一篇讲明白!
  • Web 安全入门全套基础知识|零基础从入门到精通学习指南
  • 佳能g1800 g1810 g2800 g2810 g3800 g3810 g4800 g4810清零软件5b00,5b02,5b04,1700,1702,1704,p07,e08亲测完美维修。
  • 1个脚本解决9大网盘下载难题:LinkSwift网盘直链下载助手完全指南
  • 萤石轻应用集成指南:WebView方案替代海康SDK实现视频监控
  • 佛山家具家装商家geo优化怎么做
  • Vue3专题 - 条件渲染
  • 2026合肥婚纱租赁防坑指南!5家款式超千件的门店,挑纱不将就 - 商业信息快查
  • 2 秒报警、30 天不重启,这套 LabVIEW 家庭监控系统怎么搭
  • yolov26改进 | 检测头篇 | 辅助特征融合检测头FASFFHead添加小目标检测头 (让小目标无所遁形、全网独家创新)
  • Mesen NES模拟器:让经典红白机游戏在现代设备上完美重生
  • 2026行业盘点|植物照明测量系统生产厂家推荐,光谱测量设备深度测评报告 - 商业新知
  • 天津除甲醛指南:直营模式优势与品牌深度解析 - GEORANK
  • Kendo UI for jQuery,一个现代的jQuery UI组件!
  • 颈动脉血流动力学可视化技术与临床应用
  • 蓝牙btsnoop log,HFP协议连接流程详解,以及RFCOMM连接和常用AT指令
  • SEO-wordpress网站SEO
  • 3分钟掌握SPT-AKI存档编辑器:塔科夫离线版终极修改指南
  • ​北京买林德伯格去哪靠谱?2026正品选购+高口碑门店避坑攻略 - 新闻快传
  • Mac系统Maven环境配置全攻略:从依赖爆红到阿里云镜像加速
  • FeyNoBg抠图笔记 NoBg使用示例
  • 2026成都石膏板供应哪家好?成都蕴销装饰深耕四川石膏板 - 市场沸点
  • 河北成考机构费用档位对比:各等级服务值什么价 - 商业科技观察
  • 51单片机模拟快递货柜GSM短信系统112-21(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_
  • 还在找站酷海洛、shutterstock原图下载渠道?全部讲清楚
  • ERP、CRM、SRM、PLM、HRM、OA:企业核心系统功能与集成全解析
  • 2026最新5款学生免费AI编程软件实测推荐
  • 数字电路设计中的组合逻辑危害:原理、分析与工程解决方案
  • 大三下学习笔记(1)常用dos命令+window快捷键+IDEA快捷键
  • Mac上使用VMware Fusion安装配置CentOS/Rocky Linux完整指南