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

从头开始开发基于虹软SDK的人脸识别考勤系统(python+RTSP开源)(五)补充剩余内容

本篇是对照之前代码剩余的部分代码做补充,分享给大家,便于对照运行测试。

人脸识别抽象层,这个大家应该都知道,就是为了方便使用其他的人脸识别算法设置的。

# 人脸识别服务抽象层
class FaceServiceBase:
    def __init__(self, db_service):
        self.db = db_service
        self.known_features = []
        self.known_ids = []

    def refresh_data(self):
        """从数据库加载特征数据"""
        raise NotImplementedError

    def recognize(self, frame):
        """识别图像中的人脸"""
        raise NotImplementedError

    @staticmethod
    def get_face_feature(image_path):
        """从图片提取特征"""
        raise NotImplementedError

为了增强识别效果,对图片进行处理。效果怎么样这个还真不明显。水平有限,走这步有点画蛇添足

def preprocess_image(frame):
    # 灰度化
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 直方图均衡化
    equalized = cv2.equalizeHist(gray)
    # 高斯模糊
    blurred = cv2.GaussianBlur(equalized, (5, 5), 0)
    # 将单通道灰度图转换为三通道图像
    blurred = cv2.cvtColor(blurred, cv2.COLOR_GRAY2BGR)
    return blurred

绘制识别人脸框,原来是个蓝色的框框太扎眼,换了个橙色半透明的。

# 提取绘制人脸框的代码为独立函数,修改为半透明橙色
def draw_face_boxes(pixmap, face_rects):
    painter = QPainter(pixmap)
    pen = QPen(QColor(255, 165, 0, 128), 2)  # 半透明橙色
    painter.setPen(pen)
    for rect in face_rects:
        if isinstance(rect, MRECT):
            painter.drawRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top)
        else:
            top, right, bottom, left = rect
            painter.drawRect(left, top, right - left, bottom - top)
    painter.end()
    return pixmap

 看方法名就知道了! 

def update_frame(self, frame):
        if not self.camera_worker.running:  # 仅在运行状态下更新
            return

        try:
            self.frame_mutex.lock()
            self.current_frame = frame.copy()

            # 显示帧(添加空指针检查)
            if self.current_frame is not None:
                h, w, ch = self.current_frame.shape
                bytes_per_line = ch * w
                q_img = QImage(
                    self.current_frame.data,
                    w, h, bytes_per_line,
                    QImage.Format_RGB888
                )
                pixmap = QPixmap.fromImage(q_img)

                # 人脸检测与绘制人脸框

                face_rects = self._detect_faces_arcsoft(self.current_frame)

                pixmap = draw_face_boxes(pixmap, face_rects)
                self.current_pixmap = pixmap  # 存储当前的 QPixmap
                # print("Updating video label pixmap")
                self._update_video_label_pixmap(pixmap)
            else:
                print("接收到的帧数据为空")
        finally:
            self.frame_mutex.unlock()

 调整摄像头随同窗口大小按比例改变

#调整摄像头随同窗口大小按比例改变
    def _update_video_label_pixmap(self, pixmap):
        label_size = self.video_label.size()
        if pixmap.width() > label_size.width() or pixmap.height() > label_size.height():
            pixmap = pixmap.scaled(label_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self.video_label.setPixmap(pixmap)

    def resizeEvent(self, event):
        width = self.width()
        if width > 1300:
            self.num_records = 20
        else:
            self.num_records = 15
        self.update_attendance_list(self.num_records)
        self.attendance_label.adjustSize()  # 调整 QLabel 大小
        super().resizeEvent(event)

识别时候的提示框,忽闪忽闪的。

def reset_status_label(self):
        default_style = f"color: white; font-size: 30px; font-weight: bold; background-color: transparent;"
        self.status_label.setStyleSheet(default_style)
        self.status_label.setText("等待识别...")

 

人脸识别逻辑,不是考勤规则,注释里有详细的说明,比较啰嗦一点

def get_check_type(self):
        current_time = datetime.now()
        current_hour = current_time.hour
        current_minute = current_time.minute

        # 第一次打卡时间:每天的11点30分之前(不包括11点30)只记录最早的一次打卡
        if current_hour < 11 or (current_hour == 11 and current_minute < 30):
            return'morning'
        # 第二次打卡时间:每天的11点30分之后,14点之前(包括11点30和14点)时间区间内只记录最早的一次,后续不更新
        elif (current_hour > 11 or (current_hour == 11 and current_minute >= 30)) and current_hour <= 14:
            return 'noon'
        # 第三次打卡时间:每天的14点之后均可打卡,多次打卡,自动更新为最后一次打卡时间
        elif current_hour > 14:
            return 'night'
        else:
            return 'other'

 配套的数据库写入

def record_attendance(self, staff_id, check_type, is_holiday=False):
        with self.lock:
            try:
                current_date = datetime.now().strftime('%Y-%m-%d')
                cursor = self.conn.cursor()
                # 检查该员工在当前日期、当前考勤时间段内是否已经有考勤记录
                cursor.execute('''
                    SELECT id, check_time 
                    FROM attendance 
                    WHERE staff_id =? AND check_type =? AND date =?
                ''', (staff_id, check_type, current_date))
                existing_record = cursor.fetchone()

                if existing_record:
                    record_id, existing_check_time = existing_record
                    existing_check_time = datetime.strptime(existing_check_time, "%Y-%m-%d %H:%M:%S.%f")
                    current_time = datetime.now()
                    if check_type in ('morning', 'noon'):
                        # 第一次和第二次打卡取首次打卡时间,不更新
                        pass
                    elif check_type == 'night':
                        # 第三次打卡更新为最新时间
                        cursor.execute('''
                            UPDATE attendance 
                            SET check_time =? 
                            WHERE id =?
                        ''', (current_time, record_id))
                        self.conn.commit()
                else:
                    # 没有记录则插入新记录
                    cursor.execute('''
                        INSERT INTO attendance (staff_id, check_time, date, check_type, is_holiday)
                        VALUES (?,?,?,?,?)
                    ''', (staff_id, datetime.now(), current_date, check_type, is_holiday))
                    self.conn.commit()
            except sqlite3.Error as e:
                print(f"记录考勤失败: {e}")
                raise

显示倒序排列的考勤列表,就是摄像头画面旁边的那个白色框框。

def update_attendance_list(self,num_records):
        # 从数据库中获取最新的 num_records 条考勤记录
        last_attendances = self.db.get_last_attendances(num_records)
        # 构建表格的 HTML 内容,添加白色单细线边框样式和自适应宽度样式
        table_html = '<table style="width: 100%;  height: auto;border-collapse: collapse; border: 1px solid white; table-layout: fixed;">'
        table_html += '<tr><th style="text-align: center; border: 1px solid white; padding: 8px; word-wrap: break-word;">倒序</th><th style="text-align: center; border: 1px solid white; padding: 8px; word-wrap: break-word;">姓        名</th><th style="text-align: center; border: 1px solid white; padding: 8px; word-wrap: break-word;">识别时间</th></tr>'
        for index, (name, check_time) in enumerate(last_attendances, start=1):
            time_str = check_time.strftime("%Y-%m-%d %H:%M:%S")
            table_html += f'<tr><td style="border: 1px solid white; padding: 8px; word-wrap: break-word;">{index}</td><td style="border: 1px solid white; padding: 8px; word-wrap: break-word;">{name}</td><td style="border: 1px solid white; padding: 8px; word-wrap: break-word;">{time_str}</td></tr>'
        table_html += '</table>'

        # 设置 QLabel 以富文本格式显示
        self.attendance_label.setTextFormat(Qt.RichText)
        # 设置考勤记录内容
        self.attendance_label.setText(table_html)
        # 手动刷新界面
        self.attendance_label.repaint()

 

这里才是考勤规则,在查询中设置

def show_attendance_results(self, results):
        dialog = QDialog(self)
        dialog.setWindowTitle("考勤查询结果")
        dialog.resize(640, 360)

        layout = QVBoxLayout()

        table = QTableWidget()
        table.setColumnCount(8)
        table.setHorizontalHeaderLabels(["姓名", "日期", "上午打卡时间", "上午状态", "中午打卡时间", "中午状态", "下午打卡时间", "下午状态"])
        table.setRowCount(len(results))

        from datetime import datetime

        for i, (name, date, morning_check_time, noon_check_time, night_check_time) in enumerate(results):
            table.setItem(i, 0, QTableWidgetItem(name))
            table.setItem(i, 1, QTableWidgetItem(date))

            # 处理上午打卡时间和状态
            if morning_check_time:
                try:
                    morning_check_time = datetime.strptime(morning_check_time, '%Y-%m-%d %H:%M:%S.%f')
                    morning_check_time_str = morning_check_time.strftime('%H:%M:%S')
                    morning_status = "正常"
                    if morning_check_time.time() > self.morning_end_time:
                        morning_status = "迟到"
                except ValueError:
                    morning_check_time_str = morning_check_time
                    morning_status = "异常"
            else:
                morning_check_time_str = '未打卡'
                morning_status = "未打卡"
            table.setItem(i, 2, QTableWidgetItem(morning_check_time_str))
            table.setItem(i, 3, QTableWidgetItem(morning_status))

            # 处理中午打卡时间和状态
            if noon_check_time:
                try:
                    noon_check_time = datetime.strptime(noon_check_time, '%Y-%m-%d %H:%M:%S.%f')
                    noon_check_time_str = noon_check_time.strftime('%H:%M:%S')
                    noon_status = "正常"

                except ValueError:
                    noon_check_time_str = noon_check_time
                    noon_status = "异常"
            else:
                noon_check_time_str = '未打卡'
                noon_status = "未打卡"
            table.setItem(i, 4, QTableWidgetItem(noon_check_time_str))
            table.setItem(i, 5, QTableWidgetItem(noon_status))

            # 处理下午打卡时间和状态
            if night_check_time:
                try:
                    night_check_time = datetime.strptime(night_check_time, '%Y-%m-%d %H:%M:%S.%f')
                    night_check_time_str = night_check_time.strftime('%H:%M:%S')
                    night_status = "正常"
                    if night_check_time.time() < self.night_end_time:
                        night_status = "早退"
                except ValueError:
                    night_check_time_str = night_check_time
                    night_status = "异常"
            else:
                night_check_time_str = '未打卡'
                night_status = "未打卡"
            table.setItem(i, 6, QTableWidgetItem(night_check_time_str))
            table.setItem(i, 7, QTableWidgetItem(night_status))

        layout.addWidget(table)

        button_box = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
        button_box.accepted.connect(lambda: self.export_attendance_results(table))
        button_box.rejected.connect(dialog.reject)
        layout.addWidget(button_box)

        dialog.setLayout(layout)
        dialog.exec_()

 

异步人脸识别

# ==================== 异步人脸识别处理 ====================
class RecognitionWorker(QThread):
    result_ready = pyqtSignal(list)

    def __init__(self, frame, face_service):
        super().__init__()
        self.frame = frame.copy()
        self.face_service = face_service

    def run(self):
        try:
            # print("RecognitionWorker开始运行")
            results = self.face_service.recognize(self.frame)
            # print(f"识别结果: {results}")
            self.result_ready.emit(results)
            # print("RecognitionWorker运行结束")
        except Exception as e:
            print(f"RecognitionWorker运行出错: {e}")

增加人员时的写入数据库和同步复制图片到目录

def validate_input(self):
        if not self.name_input.text().strip():
            QMessageBox.warning(self, "输入错误", "请输入姓名")
            return
        if not self.photo_path:
            QMessageBox.warning(self, "输入错误", "请选择照片")
            return

        feature = ArcSoftService.get_face_feature(self.photo_path)

        if feature is None:
            QMessageBox.warning(self, "错误", "未检测到人脸")
            return

        try:
            # 保存照片到指定目录
            os.makedirs(config.face_img_dir, exist_ok=True)
            filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}.jpg"
            save_path = os.path.join(config.face_img_dir, filename)
            shutil.copy(self.photo_path, save_path)

            # 添加记录到数据库
            self.db.add_staff(
                self.name_input.text().strip(),
                self.dept_input.text().strip(),
                save_path,
                feature
            )
            self.accept()
        except Exception as e:
            QMessageBox.critical(self, "保存失败", f"发生错误:{str(e)}")

大约差不多就这些了吧

程序入口

# ==================== 应用程序入口 ====================
if __name__ == "__main__":
    # 初始化应用程序
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create("Fusion"))
    # 主窗口
    main_window = MainWindow()
    main_window.show()

    # 运行应用程序
    sys.exit(app.exec_())

项目结束。欢迎讨论。

目前程序在低端机器上运行就是感觉识别的时候卡顿,大家有什么优化建议都来说哈,我是小白,欢迎大家指导,谢谢。


http://www.kler.cn/a/582103.html

相关文章:

  • Android List按属性排序方法总结工具类
  • python总结(2)
  • 【数学建模】001
  • ASE5N20-ASEMI智能家居专用ASE5N20
  • 【git】ssh配置提交 gitcode-ssh提交
  • kafka消息中间件的rebalance机制
  • 高速PCB设计(布线设计)
  • 12 【HarmonyOS NEXT】 仿uv-ui组件开发之Avatar组件设计精髓(三)
  • unity使用mesh 画图(1)
  • Unity单例模式更新金币数据
  • 华为OD机试-山峰个数(Java 2024 D卷 100分)
  • The Rust Programming Language 学习 (四)
  • vue el-select 省市区三级联动 vue + element-ui使用第三方插件实现省市区三级联动
  • 力扣hot100——子串、普通数组、矩阵
  • 店匠科技携手 PayPal 升级支付体验,助力独立站商家实现全球增长
  • 证券行业SCA开源风险治理实践
  • LivePlayer.js视频H5播放器如何配置iframe允许自动播放和全屏操作
  • 江科大51单片机笔记【11】AT24C02(I2C总线)
  • 【SpringBoot】实现登录功能
  • LeetCode3226 使两个整数相等的位更改次数