在课堂、会议、远程办公等场景中,实时监测人员专注度与自动签到是提升效率的重要手段。本文基于OpenCVface_recognitionTkinter,从零实现一套实时人脸识别签到 + 专注度分析的桌面应用,兼顾实用性与可扩展性,适合 Python 计算机视觉入门与课程设计实践。

一、项目功能与技术栈

核心功能

  • 自动加载本地人脸库,完成精准人脸识别
  • 实时摄像头画面展示,人脸框标注与姓名显示
  • 自动签到统计,支持签到记录重置
  • 基于人脸数量的专注度智能计算
  • 可视化 GUI 界面,操作简洁友好

技术选型

  • OpenCV:视频采集、图像预处理、画面绘制
  • face_recognition:人脸特征编码、相似度匹配(底层基于 dlib)
  • Tkinter:桌面 GUI 界面搭建
  • Pillow:图像格式转换,适配 Tkinter 显示
  • NumPy:数值计算与距离匹配

二、环境准备

安装项目依赖库,执行以下命令:

pip install opencv-python pillow face-recognition numpy

说明:face_recognition依赖 dlib,Windows 环境若安装失败,可先下载预编译 whl 包安装。

三、项目结构设计

专注度分析系统/
├── known_faces/    # 人脸库文件夹(存放注册人员照片,文件名=姓名)
└── main.py          # 主程序代码

使用时将人员正面照片放入known_faces文件夹,程序自动加载并生成人脸特征编码。

四、核心代码实现

1. 人脸库加载与特征编码

核心逻辑:读取人脸图片,提取 128 维人脸特征向量,用于后续实时比对。

known_face_encodings = []
known_face_names = []
path = "known_faces"

if not os.path.exists(path):
    os.makedirs(path)
    messagebox.showwarning("提示", "请将人脸照片放入 known_faces 文件夹,重启程序")
    exit()

# 遍历加载人脸特征
for filename in os.listdir(path):
    if filename.lower().endswith(('.jpg', '.jpeg', '.png')):
        img_path = os.path.join(path, filename)
        image = face_recognition.load_image_file(img_path)
        face_enc = face_recognition.face_encodings(image)
        if face_enc:
            known_face_encodings.append(face_enc[0])
            name = os.path.splitext(filename)[0]
            known_face_names.append(name)

2. GUI 界面搭建

基于 Tkinter 构建可视化界面,包含视频显示区信息统计区控制按钮区,采用深色主题提升视觉体验。

class AttentionApp:
    def __init__(self, root):
        self.root = root
        self.root.title("专注度分析系统")
        self.root.geometry("850x650")
        self.root.configure(bg="#2c3e50")

        # 标题
        title = Label(root, text="专注度分析系统", font=("微软雅黑",24,"bold"), fg="white", bg="#2c3e50")
        title.pack(pady=10)

        # 视频画面标签
        self.video_label = Label(root, bg="#34495e")
        self.video_label.pack(pady=10)

        # 信息面板
        info_frame = tk.Frame(root, bg="#2c3e50")
        info_frame.pack(pady=10)
        self.sign_label = Label(info_frame, text="已签到: 0 人", font=("微软雅黑",16), fg="#2ecc71", bg="#2c3e50")
        self.attention_label = Label(info_frame, text="平均专注度: 80", font=("微软雅黑",16), fg="#f1c40f", bg="#2c3e50")

3. 摄像头与实时视频处理

七、项目优化方向

八、总结

本项目以Python + 计算机视觉为核心,实现了一套轻量、实用的专注度分析与自动签到系统,完整覆盖人脸检测、特征匹配、GUI 开发、实时视频处理等关键技能。代码结构清晰、注释完善,既可作为计算机视觉入门案例,也能快速迭代为课程设计、毕业设计作品,欢迎大家二次开发拓展功能!

  • 调用摄像头获取实时帧
  • 缩放图像提升识别速度
  • 人脸定位 + 特征提取 + 相似度匹配
  • 绘制人脸框与姓名,更新签到与专注度
    def update_video(self):
        if not self.running:
            return
        ret, frame = self.cap.read()
        if not ret:
            self.root.after(30, self.update_video)
            return
    
        # 缩小图像加速处理
        small_frame = cv2.resize(frame, (0,0), fx=0.25, fy=0.25)
        rgb_small = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB)
    
        # 人脸检测与编码
        face_locations = face_recognition.face_locations(rgb_small)
        face_encodings = face_recognition.face_encodings(rgb_small, face_locations)
    
        detected_names = []
        for face_enc in face_encodings:
            matches = face_recognition.compare_faces(known_face_encodings, face_enc)
            name = "未知人员"
            face_distances = face_recognition.face_distance(known_face_encodings, face_enc)
            if face_distances.size>0:
                best_idx = np.argmin(face_distances)
                if matches[best_idx]:
                    name = known_face_names[best_idx]
            detected_names.append(name)

    4. 专注度计算逻辑

    基础专注度 80 分,检测到的人脸数量越多,专注度越低(模拟多人分散注意力),同时限制最低分为 0。

    attention = max(0, self.base_attention - len(detected_names) * 5)
    self.attention_label.config(text=f"平均专注度: {attention}")

    五、完整优化代码

    原代码存在人脸匹配错误无真实人脸识别无签到重置等问题,已完成全面优化,完整代码如下:

    import cv2
    import os
    import tkinter as tk
    from tkinter import Label, Button, messagebox
    from PIL import Image, ImageTk
    import face_recognition
    import numpy as np
    
    print("正在启动专注度分析系统...")
    
    known_face_encodings = []
    known_face_names = []
    path = "known_faces"
    
    if not os.path.exists(path):
        os.makedirs(path)
        messagebox.showwarning("提示", "请将人脸照片放入 known_faces 文件夹,重启程序")
        exit()
    
    for filename in os.listdir(path):
        suffix = filename.lower()
        if suffix.endswith(('.jpg', '.jpeg', '.png')):
            img_path = os.path.join(path, filename)
            image = face_recognition.load_image_file(img_path)
            face_enc = face_recognition.face_encodings(image)
            if len(face_enc) > 0:
                known_face_encodings.append(face_enc[0])
                name = os.path.splitext(filename)[0]
                known_face_names.append(name)
                print(f"已注册人脸: {name}")
            else:
                print(f"警告:{filename} 未检测到人脸,跳过")
    
    if len(known_face_names) == 0:
        messagebox.showerror("错误", "known_faces 文件夹无有效人脸照片")
        exit()
    
    print(f"成功加载 {len(known_face_names)} 位注册人员人脸")
    
    class AttentionApp:
        def __init__(self, root):
            self.root = root
            self.root.title("专注度分析系统")
            self.root.geometry("850x650")
            self.root.configure(bg="#2c3e50")
    
            title = Label(root, text="专注度分析系统", font=("微软雅黑", 24, "bold"), fg="white", bg="#2c3e50")
            title.pack(pady=10)
    
            self.video_label = Label(root, bg="#34495e")
            self.video_label.pack(pady=10)
    
            info_frame = tk.Frame(root, bg="#2c3e50")
            info_frame.pack(pady=10)
    
            self.sign_label = Label(info_frame, text="已签到: 0 人", font=("微软雅黑", 16), fg="#2ecc71", bg="#2c3e50")
            self.sign_label.grid(row=0, column=0, padx=30)
    
            self.attention_label = Label(info_frame, text="平均专注度: 80", font=("微软雅黑", 16), fg="#f1c40f", bg="#2c3e50")
            self.attention_label.grid(row=0, column=1, padx=30)
    
            self.name_label = Label(info_frame, text="签到名单: 无", font=("微软雅黑", 12), fg="white", bg="#2c3e50", wraplength=700)
            self.name_label.grid(row=1, column=0, columnspan=2, pady=10)
    
            btn_frame = tk.Frame(root, bg="#2c3e50")
            btn_frame.pack(pady=10)
    
            self.start_btn = Button(btn_frame, text="启动摄像头", font=("微软雅黑", 14), bg="#3498db", fg="white", padx=20, pady=5, command=self.start_camera)
            self.start_btn.grid(row=0, column=0, padx=10)
    
            self.stop_btn = Button(btn_frame, text="停止摄像头", font=("微软雅黑", 14), bg="#e74c3c", fg="white", padx=20, pady=5, command=self.stop_camera)
            self.stop_btn.grid(row=0, column=1, padx=10)
    
            self.reset_btn = Button(btn_frame, text="重置签到", font=("微软雅黑", 14), bg="#d35400", fg="white", padx=20, pady=5, command=self.reset_sign)
            self.reset_btn.grid(row=0, column=2, padx=10)
    
            self.quit_btn = Button(btn_frame, text="退出系统", font=("微软雅黑", 14), bg="#95a5a6", fg="white", padx=20, pady=5, command=self.quit_app)
            self.quit_btn.grid(row=0, column=3, padx=10)
    
            self.cap = None
            self.running = False
            self.signed_in = set()
            self.base_attention = 80
    
        def reset_sign(self):
            self.signed_in.clear()
            self.update_info_ui()
    
        def start_camera(self):
            if self.running:
                return
            self.cap = cv2.VideoCapture(0)
            if not self.cap.isOpened():
                messagebox.showerror("摄像头错误", "无法调用设备摄像头,请检查权限/设备")
                return
            self.running = True
            self.start_btn.config(state="disabled")
            self.update_video()
    
        def stop_camera(self):
            self.running = False
            if self.cap:
                self.cap.release()
                self.cap = None
            self.start_btn.config(state="normal")
            self.video_label.config(image="")
            self.video_label.image = None
    
        def quit_app(self):
            self.stop_camera()
            self.root.destroy()
    
        def update_info_ui(self):
            self.sign_label.config(text=f"已签到: {len(self.signed_in)} 人")
            name_text = ', '.join(self.signed_in) if self.signed_in else "无"
            self.name_label.config(text=f"签到名单: {name_text}")
    
        def update_video(self):
            if not self.running:
                return
    
            ret, frame = self.cap.read()
            if not ret:
                self.root.after(30, self.update_video)
                return
    
            small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
            rgb_small = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB)
    
            face_locations = face_recognition.face_locations(rgb_small)
            face_encodings = face_recognition.face_encodings(rgb_small, face_locations)
    
            detected_names = []
            for face_enc in face_encodings:
                matches = face_recognition.compare_faces(known_face_encodings, face_enc)
                name = "未知人员"
                face_distances = face_recognition.face_distance(known_face_encodings, face_enc)
                if len(face_distances) > 0:
                    best_idx = np.argmin(face_distances)
                    if matches[best_idx]:
                        name = known_face_names[best_idx]
                detected_names.append(name)
                if name != "未知人员" and name not in self.signed_in:
                    self.signed_in.add(name)
                    print(f"✅ {name} 签到成功")
    
            for (top, right, bottom, left), name in zip(face_locations, detected_names):
                top *= 4
                right *= 4
                bottom *= 4
                left *= 4
                color = (0, 255, 0) if name != "未知人员" else (0, 0, 255)
                cv2.rectangle(frame, (left, top), (right, bottom), color, 2)
                cv2.rectangle(frame, (left, bottom - 25), (right, bottom), color, -1)
                cv2.putText(frame, name, (left + 6, bottom - 6), cv2.FONT_HERSHEY_DUPLEX, 0.6, (255,255,255), 1)
    
            attention = max(0, self.base_attention - len(detected_names) * 5)
            self.attention_label.config(text=f"平均专注度: {attention}")
            self.update_info_ui()
    
            frame = cv2.resize(frame, (640, 360))
            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = Image.fromarray(frame_rgb)
            imgtk = ImageTk.PhotoImage(image=img)
            self.video_label.config(image=imgtk)
            self.video_label.image = imgtk
    
            self.root.after(30, self.update_video)
    
    if __name__ == "__main__":
        root = tk.Tk()
        app = AttentionApp(root)
        root.mainloop()
        print(f"最终签到人数:{len(app.signed_in)}")
        print(f"签到名单:{list(app.signed_in)}")

    六、运行与使用说明

  • 在项目目录下创建known_faces文件夹,放入人员正面照片(文件名设为姓名,如张三.jpg
  • 运行main.py,程序自动加载人脸库
  • 点击启动摄像头,系统开始实时检测
  • 注册人员入镜后自动签到,界面实时更新签到人数、名单与专注度
  • 支持停止摄像头重置签到退出系统操作
  • 精准专注度判断:接入 MediaPipe 人脸关键点,计算眨眼频率、头部姿态、视线方向,提升专注度判断准确性
  • 数据持久化:将签到记录保存至 Excel / 数据库,支持历史查询
  • 多人同时管理:添加人员注册、删除功能,动态管理人脸库
  • 异常告警:专注度低于阈值时弹窗 / 声音提醒
  • 性能优化:多线程分离视频采集与识别,提升流畅度
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐