一、项目概述

1.1 系统背景

随着人工智能技术的快速发展,人脸识别技术在各个领域得到了广泛应用。传统的考勤方式(如打卡机、指纹识别、签到表等)存在代打卡、接触式卫生问题、效率低下等弊端。基于人脸识别的考勤系统能够有效解决这些问题,实现无接触、高效率、高准确率的考勤管理。

1.2 系统特点

  • 多语言实现:提供Java、PHP、Python、.NET四种技术栈实现方案

  • 高精度识别:基于深度学习人脸识别算法,识别准确率达99%以上

  • 实时考勤:支持实时人脸检测与识别,自动记录考勤时间

  • 多端适配:支持Web端、移动端、桌面端应用

  • 数据可视化:考勤数据统计分析,图表展示


二、技术选型对比

技术栈 优势 适用场景 主要依赖
Java 稳定性高,适合大型企业级应用 企业级考勤系统 Spring Boot, OpenCV, MySQL, MyBatis
PHP 开发速度快,部署简单 中小型Web考勤系统 Laravel/ThinkPHP, OpenCV, MySQL
Python AI生态完善,算法集成方便 需要深度优化算法的系统 Django/Flask, TensorFlow/PyTorch, dlib, face_recognition
.NET Windows生态集成好 企业内部Windows环境 ASP.NET Core, OpenCVSharp, SQL Server

三、系统功能模块

3.1 用户端功能

├── 人脸注册
│   ├── 拍照录入
│   ├── 图片上传
│   └── 活体检测
├── 日常考勤
│   ├── 上班打卡
│   ├── 下班打卡
│   ├── 考勤记录查询
│   └── 考勤异常申诉
├── 个人信息管理
│   ├── 基本信息维护
│   └── 人脸更新
└── 考勤统计
    ├── 个人考勤日历
    └── 考勤报表导出

3.2 管理员端功能

├── 部门管理
│   ├── 部门增删改查
│   └── 部门人员分配
├── 员工管理
│   ├── 员工信息管理
│   ├── 人脸信息管理
│   └── 账号启停用
├── 考勤规则设置
│   ├── 上班时间设置
│   ├── 下班时间设置
│   ├── 迟到早退阈值
│   └── 节假日设置
├── 考勤审核
│   ├── 异常考勤审核
│   ├── 补卡申请审批
│   └── 批量导入导出
├── 报表统计
│   ├── 部门考勤统计
│   ├── 个人考勤明细
│   └── 图表可视化
└── 系统管理
    ├── 角色权限管理
    ├── 操作日志
    └── 数据备份

四、数据库设计

4.1 主要数据表结构

-- 部门表
CREATE TABLE department (
    id INT PRIMARY KEY AUTO_INCREMENT,
    dept_name VARCHAR(50) NOT NULL,
    parent_id INT DEFAULT 0,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 员工表
CREATE TABLE employee (
    id INT PRIMARY KEY AUTO_INCREMENT,
    emp_no VARCHAR(20) UNIQUE NOT NULL,
    name VARCHAR(50) NOT NULL,
    gender CHAR(1),
    phone VARCHAR(20),
    email VARCHAR(100),
    dept_id INT,
    position VARCHAR(50),
    face_feature TEXT,  -- 人脸特征向量(JSON格式)
    face_image VARCHAR(255),  -- 人脸图片路径
    status TINYINT DEFAULT 1,  -- 1:在职 0:离职
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (dept_id) REFERENCES department(id)
);

-- 考勤记录表
CREATE TABLE attendance (
    id INT PRIMARY KEY AUTO_INCREMENT,
    emp_id INT,
    check_date DATE NOT NULL,
    check_in_time DATETIME,  -- 上班打卡时间
    check_out_time DATETIME, -- 下班打卡时间
    in_status VARCHAR(20),    -- 正常、迟到、缺卡
    out_status VARCHAR(20),   -- 正常、早退、缺卡
    work_hours DECIMAL(4,2),  -- 工作时长
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (emp_id) REFERENCES employee(id),
    UNIQUE KEY uk_emp_date (emp_id, check_date)
);

-- 考勤规则表
CREATE TABLE attendance_rule (
    id INT PRIMARY KEY AUTO_INCREMENT,
    rule_name VARCHAR(50),
    check_in_time TIME NOT NULL,  -- 上班时间
    check_out_time TIME NOT NULL, -- 下班时间
    late_threshold INT DEFAULT 0,  -- 迟到阈值(分钟)
    early_threshold INT DEFAULT 0, -- 早退阈值(分钟)
    work_days VARCHAR(20) DEFAULT '1,2,3,4,5', -- 工作日(周一=1,周日=7)
    is_active TINYINT DEFAULT 1
);

-- 请假/补卡申请表
CREATE TABLE leave_apply (
    id INT PRIMARY KEY AUTO_INCREMENT,
    emp_id INT,
    apply_type VARCHAR(20),  -- 请假、补卡、外勤
    start_time DATETIME,
    end_time DATETIME,
    reason TEXT,
    status VARCHAR(20) DEFAULT '待审批',  -- 待审批、通过、驳回
    approver_id INT,
    approve_time DATETIME,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (emp_id) REFERENCES employee(id)
);

五、各语言实现方案

5.1 Java实现方案

核心技术栈:

  • Spring Boot 2.7.x

  • Spring Security + JWT

  • MyBatis Plus

  • OpenCV 4.5

  • MySQL/Redis

  • Vue.js(前端)

人脸识别核心代码示例:

@Service
public class FaceRecognitionService {
    
    @Autowired
    private EmployeeMapper employeeMapper;
    
    // 加载OpenCV库
    static {
        nu.pattern.OpenCV.loadLocally();
    }
    
    /**
     * 人脸特征提取
     */
    public String extractFaceFeature(MultipartFile file) {
        Mat image = Imgcodecs.imread(file.getBytes());
        // 转换为灰度图
        Mat gray = new Mat();
        Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);
        
        // 人脸检测
        CascadeClassifier faceDetector = new CascadeClassifier(
            "haarcascade_frontalface_alt.xml"
        );
        MatOfRect faceDetections = new MatOfRect();
        faceDetector.detectMultiScale(gray, faceDetections);
        
        Rect[] faces = faceDetections.toArray();
        if (faces.length == 0) {
            throw new RuntimeException("未检测到人脸");
        }
        
        // 提取特征向量(简化版,实际可使用深度学习模型)
        Mat face = new Mat(gray, faces[0]);
        MatOfFloat featureVector = new MatOfFloat();
        // 这里可以使用预训练的深度学习模型提取特征
        
        return convertMatToJson(featureVector);
    }
    
    /**
     * 人脸比对
     */
    public boolean compareFace(String feature1, String feature2, double threshold) {
        Mat vec1 = convertJsonToMat(feature1);
        Mat vec2 = convertJsonToMat(feature2);
        
        // 计算余弦相似度
        double similarity = calculateCosineSimilarity(vec1, vec2);
        return similarity > threshold;
    }
}

5.2 PHP实现方案(Laravel + OpenCV)

核心技术栈:

  • Laravel 10.x

  • OpenCV for PHP

  • MySQL/Redis

  • JWT认证

  • Bootstrap 5(前端)

人脸识别核心代码:

<?php

namespace App\Services;

use OpenCV\Image as OpenCVImage;
use OpenCV\FaceRecognizer;

class FaceRecognitionService
{
    /**
     * 人脸检测与特征提取
     */
    public function detectFace($imagePath)
    {
        // 加载图片
        $image = OpenCVImage::load($imagePath);
        
        // 转换为灰度图
        $gray = $image->cvtColor(OpenCVImage::COLOR_BGR2GRAY);
        
        // 人脸检测
        $faceCascade = new OpenCV\CascadeClassifier();
        $faceCascade->load('haarcascade_frontalface_alt.xml');
        
        $faces = $faceCascade->detectMultiScale($gray);
        
        if (empty($faces)) {
            return null;
        }
        
        // 提取人脸区域
        $face = $gray->getImageROI($faces[0]);
        
        // 调整大小为统一尺寸
        $faceResized = $face->resize(100, 100);
        
        // 提取LBP特征
        $lbph = FaceRecognizer::createLBPHFaceRecognizer();
        $features = $lbph->compute($faceResized);
        
        return json_encode([
            'features' => $features,
            'bbox' => $faces[0]
        ]);
    }
    
    /**
     * 人脸比对
     */
    public function matchFace($features1, $features2)
    {
        $lbph = FaceRecognizer::createLBPHFaceRecognizer();
        $similarity = $lbph->similarity($features1, $features2);
        
        return $similarity;
    }
}

5.3 Python实现方案(Flask + face_recognition)

核心技术栈:

  • Flask 2.3.x

  • face_recognition库

  • OpenCV-Python

  • MySQL/Redis

  • JWT认证

  • Vue.js/Element UI(前端)

人脸识别核心代码:

import face_recognition
import cv2
import numpy as np
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
import base64
import io
from PIL import Image

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:password@localhost/attendance'
db = SQLAlchemy(app)

class Employee(db.Model):
    __tablename__ = 'employee'
    id = db.Column(db.Integer, primary_key=True)
    emp_no = db.Column(db.String(20), unique=True)
    name = db.Column(db.String(50))
    face_encoding = db.Column(db.Text)  # 存储人脸特征编码

class FaceRecognitionService:
    
    @staticmethod
    def extract_face_encoding(image_data):
        """
        提取人脸特征编码
        """
        # 将base64或二进制转换为图像
        if isinstance(image_data, str):
            # base64格式
            image_data = base64.b64decode(image_data.split(',')[1])
        
        image = Image.open(io.BytesIO(image_data))
        image = np.array(image)
        
        # 转换为RGB格式
        if image.shape[2] == 4:
            image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
        elif len(image.shape) == 2:
            image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
        
        # 检测人脸
        face_locations = face_recognition.face_locations(image)
        if len(face_locations) == 0:
            raise ValueError("未检测到人脸")
        
        # 提取人脸编码
        face_encodings = face_recognition.face_encodings(image, face_locations)
        if len(face_encodings) == 0:
            raise ValueError("无法提取人脸特征")
        
        # 将numpy数组转换为列表存储
        encoding_list = face_encodings[0].tolist()
        return json.dumps(encoding_list)
    
    @staticmethod
    def compare_faces(encoding1, encoding2, threshold=0.6):
        """
        比较两个人脸特征
        """
        enc1 = np.array(json.loads(encoding1))
        enc2 = np.array(json.loads(encoding2))
        
        # 计算欧氏距离
        distance = np.linalg.norm(enc1 - enc2)
        
        # 距离越小越相似,通常阈值设为0.6
        similarity = 1 - distance
        return similarity > threshold, similarity

@app.route('/api/attendance/check', methods=['POST'])
def check_attendance():
    """
    考勤打卡接口
    """
    try:
        data = request.json
        emp_no = data.get('emp_no')
        image_data = data.get('image')  # base64格式
        
        # 查询员工
        employee = Employee.query.filter_by(emp_no=emp_no).first()
        if not employee:
            return jsonify({'code': 404, 'msg': '员工不存在'})
        
        # 提取当前人脸特征
        current_encoding = FaceRecognitionService.extract_face_encoding(image_data)
        
        # 比对特征
        is_match, similarity = FaceRecognitionService.compare_faces(
            employee.face_encoding, 
            current_encoding
        )
        
        if not is_match:
            return jsonify({
                'code': 401, 
                'msg': '人脸识别失败,相似度:{:.2f}'.format(similarity)
            })
        
        # 记录考勤
        now = datetime.now()
        today = now.date()
        
        attendance = Attendance.query.filter_by(
            emp_id=employee.id, 
            check_date=today
        ).first()
        
        if not attendance:
            # 上班打卡
            attendance = Attendance(
                emp_id=employee.id,
                check_date=today,
                check_in_time=now
            )
            db.session.add(attendance)
            msg = '上班打卡成功'
        else:
            # 下班打卡
            attendance.check_out_time = now
            msg = '下班打卡成功'
        
        db.session.commit()
        
        return jsonify({
            'code': 200,
            'msg': msg,
            'data': {
                'similarity': float(similarity),
                'time': now.strftime('%Y-%m-%d %H:%M:%S')
            }
        })
        
    except Exception as e:
        return jsonify({'code': 500, 'msg': str(e)})

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

更多推荐