news 2026/8/6 17:21:41

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【免费】人脸识别 智能考勤系统(深度学习+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>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/6 17:20:56

6款AI写作辅助软件精选

真正的学术 AI&#xff0c;从不替你代笔&#xff0c;而是做你的选题军师、文献管家、逻辑教练、润色专家。从中文毕业论文到英文期刊发表&#xff0c;从框架搭建到降重合规&#xff0c;这 6 款工具覆盖全场景&#xff0c;帮你用最低时间成本&#xff0c;写出高质量、高原创、高…

作者头像 李华
网站建设 2026/8/6 17:20:06

Dhizuku终极指南:如何简单免费地分享Android设备所有者权限

Dhizuku终极指南&#xff1a;如何简单免费地分享Android设备所有者权限 【免费下载链接】Dhizuku A tool that can share DeviceOwner permissions to other application. 项目地址: https://gitcode.com/gh_mirrors/dh/Dhizuku Dhizuku是一款革命性的Android工具&#…

作者头像 李华
网站建设 2026/8/6 17:20:01

第六次面试2026.8.5北京一面已OC

1.平时开发用到哪些ai开发工具&#xff1f;2.开发的一个任务的时候一个工作流是什么&#xff1f;接到一个需求&#xff0c;你平时是怎么做的&#xff1f;接到一个需求你会怎么拆解&#xff1f;技术栈没学过&#xff0c;不可能直接去学到可以开发&#xff0c;怎么用ai提高适应力…

作者头像 李华
网站建设 2026/8/6 17:19:43

make和makefile(自动化构建)

一、makefile 1.1makefile的基本了解会不会写makefile&#xff0c;从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒,⼀个⼯程中的源⽂件不计数&#xff0c;其按类型、功能、模块分别放在若⼲个⽬录中&#xff0c;makefile定义了⼀系列的规则来指定&#xff0c;哪些⽂件需要先…

作者头像 李华
网站建设 2026/8/6 17:17:28

Mac系统Maven环境配置全攻略:从依赖爆红到阿里云镜像加速

1. 从“依赖爆红”到环境搭建&#xff1a;为什么Mac上的Maven配置是Java开发的基石 如果你在Mac上刚装好IDEA&#xff0c;兴致勃勃地新建一个Spring Boot项目&#xff0c;结果一打开 pom.xml 文件&#xff0c;满屏的依赖项都标着刺眼的红色波浪线&#xff0c;旁边还提示“Ca…

作者头像 李华
网站建设 2026/8/6 17:16:20

CTF逆向入门:从UPX脱壳到IDA静态分析与Python脚本解题

1. 项目概述&#xff1a;从一道MoeCTF题开始的逆向之旅 最近在带一些刚入门安全的朋友&#xff0c;发现很多新手对CTF逆向既向往又畏惧。向往的是那种破解谜题、找到Flag的成就感&#xff0c;畏惧的是面对一堆汇编指令和加密算法时的茫然。正好&#xff0c;MoeCTF Week1有一道典…

作者头像 李华