python人脸识别服务端和客户端代码

99 阅读22分钟

人脸识别服务端代码

from flask import Flask, request, jsonify
import face_recognition
import numpy as np
import sqlite3
import logging
import os
from datetime import datetime
import threading
import base64
from PIL import Image
import io
import faiss
import cv2
from flask_cors import CORS
from logging.handlers import TimedRotatingFileHandler
# 增强日志配置
# 增强日志配置
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# 创建一个TimedRotatingFileHandler,每天轮换一次日志文件
log_handler = TimedRotatingFileHandler(
    'face.log',
    when='midnight',  # 每天午夜轮换
    interval=1,  # 每隔1天轮换一次
    backupCount=7,  # 保留最近7天的日志文件
    encoding='utf-8'
)

# 设置日志格式
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
log_handler.setFormatter(formatter)

# 添加处理器到logger
logger.addHandler(log_handler)

# 添加一个控制台处理器,用于实时查看日志
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)


app = Flask(__name__)
CORS(app)

class FaceManagerServer:
    @staticmethod
    def base64_to_image(base64_string, output_path):
        """Base64转图片并保存"""
        try:
            if ',' in base64_string:
                base64_string = base64_string.split(',')[1]
            image_data = base64.b64decode(base64_string)
            image = Image.open(io.BytesIO(image_data))
            # 打印图像信息
            # logger.info(f"Image mode: {image.mode}, size: {image.size}, format: {image.format}")
            # 如果是RGBA模式,转换为RGB模式
            if image.mode == 'RGBA':
                image = image.convert('RGB')

            image.save(output_path)
            return True
        except Exception as e:
            logger.error(f"图像转换错误: {e}")
            return False

    def __init__(self, database_path='face_recognitionfaissBitch.db'):
        self.database_path = database_path
        self.connection_lock = threading.Lock()
        self.index_lock = threading.Lock()
        self.index = None
        self.user_ids = []
        self.face_encoding_dim = 128  # face_recognition库的固定维度
        self.min_face_size = 40  # 最小人脸像素尺寸
        self._initialize_database()
        self._build_faiss_index()

    def _get_connection(self):
        """获取线程安全的数据库连接"""
        with self.connection_lock:
            return sqlite3.connect(self.database_path)

    def _initialize_database(self):
        """初始化数据库结构"""
        try:
            with self._get_connection() as conn:
                cursor = conn.cursor()
                cursor.execute('''
                    CREATE TABLE IF NOT EXISTS users (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        user_id TEXT UNIQUE NOT NULL,
                        name TEXT NOT NULL,
                        face_encoding BLOB NOT NULL,
                        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                    )
                ''')
                conn.commit()
        except sqlite3.Error as e:
            logger.error(f"数据库初始化失败: {e}")
            raise

    # def _preprocess_face_image(self, image_path: str) -> bool:
    #     """
    #     人脸图像预处理(优化版)
    #     - 自动旋转校正
    #     - 自适应亮度均衡化(处理强光/背光问题)
    #     - 多尺度人脸检测
    #     - 人脸区域智能裁剪
    #     返回是否预处理成功
    #     """
    #     try:
    #         image = cv2.imread(image_path)
    #         if image is None:
    #             return False
    #
    #         # 确保图像是8位无符号格式
    #         if image.dtype != np.uint8:
    #             image = image.astype(np.uint8)
    #
    #         # 1. 图像增强处理(处理强光/背光问题)
    #         hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    #         h, s, v = cv2.split(hsv)
    #
    #         # 自适应直方图均衡化(限制对比度)
    #         clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    #         v = clahe.apply(v)
    #
    #         # 合并回HSV图像
    #         hsv = cv2.merge((h, s, v))
    #         image = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
    #
    #         # 转换为灰度图
    #         gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    #
    #         # 2. 多尺度人脸检测
    #         face_cascade = cv2.CascadeClassifier(
    #             cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
    #
    #         # 尝试不同尺度检测(确保scaleFactor > 1)
    #         detected = False
    #         for scale in [1.05, 1.1, 1.2, 1.3]:  # 移除了1.0,确保scaleFactor > 1
    #             faces = face_cascade.detectMultiScale(
    #                 gray,
    #                 scaleFactor=scale,
    #                 minNeighbors=5,
    #                 minSize=(int(self.min_face_size * 0.8), int(self.min_face_size * 0.8)),
    #                 flags=cv2.CASCADE_SCALE_IMAGE)
    #
    #             if len(faces) > 0:
    #                 detected = True
    #                 break
    #
    #         if not detected:
    #             # 尝试更宽松的参数
    #             faces = face_cascade.detectMultiScale(
    #                 gray,
    #                 scaleFactor=1.3,
    #                 minNeighbors=3,
    #                 minSize=(int(self.min_face_size * 0.5), int(self.min_face_size * 0.5)))
    #
    #             if len(faces) == 0:
    #                 return False
    #
    #         # 3. 智能选择最佳人脸区域
    #         def score_face(face):
    #             x, y, w, h = face
    #             area = w * h
    #             center_x = x + w / 2
    #             center_y = y + h / 2
    #             img_center_x = image.shape[1] / 2
    #             img_center_y = image.shape[0] / 2
    #             dist = ((center_x - img_center_x) / image.shape[1]) ** 2 + \
    #                    ((center_y - img_center_y) / image.shape[0]) ** 2
    #             return area * (1 - dist * 2)
    #
    #         best_face = max(faces, key=score_face)
    #         x, y, w, h = best_face
    #
    #         # 4. 智能扩展人脸区域
    #         margin_ratio = 0.2 + 0.3 * (1 - min(w, h) / (max(image.shape[0], image.shape[1]) * 0.5))
    #         margin_w = int(w * margin_ratio)
    #         margin_h = int(h * margin_ratio)
    #
    #         x = max(0, x - margin_w)
    #         y = max(0, y - margin_h)
    #         w = min(image.shape[1] - x, w + 2 * margin_w)
    #         h = min(image.shape[0] - y, h + 2 * margin_h)
    #
    #         # 5. 裁剪并保存人脸区域
    #         cropped = image[y:y + h, x:x + w]
    #
    #         if cropped.shape[0] < self.min_face_size or cropped.shape[1] < self.min_face_size:
    #             scale_factor = max(self.min_face_size / cropped.shape[0],
    #                                self.min_face_size / cropped.shape[1])
    #             cropped = cv2.resize(cropped, None, fx=scale_factor, fy=scale_factor,
    #                                  interpolation=cv2.INTER_AREA)
    #
    #         cv2.imwrite(image_path, cropped)
    #         return True
    #
    #     except Exception as e:
    #         logger.warning(f"人脸图像预处理失败: {e}")
    #         return False
    def _preprocess_face_image(self, image_path: str) -> bool:
        """
        使用face_recognition库优化的人脸预处理
        优点:
        - 自动处理模型下载
        - 支持多角度人脸检测
        - 内置人脸关键点检测
        """
        try:
            # 1. 加载图像
            image = cv2.imread(image_path)
            if image is None:
                logger.warning(f"无法加载图像: {image_path}")
                return False

            # 2. 转换为RGB格式(face_recognition需要)
            rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

            # 3. 使用face_recognition检测人脸(支持多角度)
            face_locations = face_recognition.face_locations(
                rgb_image,
                model="cnn" if self.min_face_size > 80 else "hog",
                number_of_times_to_upsample=1
            )

            if not face_locations:
                # 尝试更宽松的参数
                face_locations = face_recognition.face_locations(
                    rgb_image,
                    number_of_times_to_upsample=2
                )
                if not face_locations:
                    return False

            # 4. 选择最佳人脸(基于面积和中心位置)
            def _calculate_face_score(face_location, img_width, img_height):
                top, right, bottom, left = face_location
                area = (bottom - top) * (right - left)
                center_x = (left + right) / 2
                center_y = (top + bottom) / 2
                position_score = 1 - ((center_x - img_width / 2) ** 2 / (img_width / 2) ** 2 +
                                      (center_y - img_height / 2) ** 2 / (img_height / 2) ** 2)
                return area * position_score

            img_height, img_width = image.shape[:2]
            best_face = max(face_locations,
                            key=lambda loc: _calculate_face_score(loc, img_width, img_height))
            top, right, bottom, left = best_face

            # 5. 智能扩展人脸区域
            height, width = bottom - top, right - left
            margin_vertical = int(height * 0.3)
            margin_horizontal = int(width * 0.25)

            top = max(0, top - margin_vertical)
            bottom = min(img_height, bottom + margin_vertical)
            left = max(0, left - margin_horizontal)
            right = min(img_width, right + margin_horizontal)

            # 6. 裁剪人脸区域
            cropped = image[top:bottom, left:right]

            # 7. 尺寸标准化
            if cropped.shape[0] < self.min_face_size or cropped.shape[1] < self.min_face_size:
                scale = max(self.min_face_size / cropped.shape[0],
                            self.min_face_size / cropped.shape[1])
                cropped = cv2.resize(cropped, None, fx=scale, fy=scale,
                                     interpolation=cv2.INTER_CUBIC)

            # 8. 保存结果
            cv2.imwrite(image_path, cropped)
            return True

        except Exception as e:
            logger.error(f"人脸预处理失败: {e}", exc_info=True)
            return False

    def _validate_face_encoding(self, encoding):
        """验证人脸编码维度是否正确"""
        if len(encoding) != self.face_encoding_dim:
            raise ValueError(
                f"人脸编码维度错误: 期望{self.face_encoding_dim}维, 实际{len(encoding)}维"
            )

    def _build_faiss_index(self):
        """构建FAISS索引(带维度校验)"""
        with self.index_lock:
            try:
                # 获取所有用户数据
                with self._get_connection() as conn:
                    cursor = conn.cursor()
                    cursor.execute("SELECT user_id, face_encoding FROM users")
                    users = cursor.fetchall()

                # 准备索引数据
                encodings = []
                self.user_ids = []
                valid_count = 0
                invalid_count = 0

                for user_id, face_blob in users:
                    try:
                        encoding = np.frombuffer(face_blob, dtype=np.float32)
                        self._validate_face_encoding(encoding)
                        encodings.append(encoding)
                        self.user_ids.append(user_id)
                        valid_count += 1
                    except ValueError as e:
                        logger.warning(f"用户 {user_id} 编码无效: {str(e)}")
                        invalid_count += 1

                # 构建索引
                if encodings:
                    encodings = np.vstack(encodings).astype('float32')
                    self.index = faiss.IndexFlatL2(self.face_encoding_dim)
                    self.index.add(encodings)
                    logger.info(
                        f"FAISS索引构建完成: 有效用户{valid_count}个, 跳过{invalid_count}个无效编码"
                    )
                else:
                    self.index = faiss.IndexFlatL2(self.face_encoding_dim)
                    logger.info("初始化空FAISS索引")

            except Exception as e:
                logger.error(f"构建FAISS索引失败: {e}")
                raise RuntimeError(f"索引构建失败: {str(e)}")

    def user_exists(self, user_id):
        """检查用户是否存在"""
        with self._get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT 1 FROM users WHERE user_id = ?", (user_id,))
            return cursor.fetchone() is not None

    def save_or_update_user(self, name, image_path, user_id):
        """保存或更新用户信息(带编码校验)"""
        try:
            # 图像预处理
            # if not self._preprocess_face_image(image_path):
            #     logger.warning(f"图像预处理失败,可能未检测到人脸: {image_path}")
            #     raise ValueError("图像中未检测到合格的人脸")
            # 提取人脸编码
            image = face_recognition.load_image_file(image_path)
            face_encodings = face_recognition.face_encodings(image)

            if not face_encodings:
                raise ValueError("图像中未检测到人脸")

            face_encoding = face_encodings[0]
            self._validate_face_encoding(face_encoding)

            # 存储到数据库
            face_blob = np.array(face_encoding, dtype=np.float32).tobytes()

            with self._get_connection() as conn:
                cursor = conn.cursor()

                if self.user_exists(user_id):
                    cursor.execute('''
                        UPDATE users 
                        SET name=?, face_encoding=?, updated_at=CURRENT_TIMESTAMP 
                        WHERE user_id=?
                    ''', (name, face_blob, user_id))
                    action = "updated"
                else:
                    cursor.execute('''
                        INSERT INTO users (user_id, name, face_encoding) 
                        VALUES (?, ?, ?)
                    ''', (user_id, name, face_blob))
                    action = "created"

                conn.commit()

            # 重建索引
            self._build_faiss_index()

            return {
                "status": "success",
                "action": action,
                "userId": user_id,
                "userName": name
            }

        except Exception as e:
            logger.error(f"保存用户失败: {e}")
            raise RuntimeError(f"保存用户失败: {str(e)}")

    def delete_user(self, user_id):
        """删除用户并重建索引"""
        if not self.user_exists(user_id):
            raise ValueError(f"用户ID {user_id} 不存在")

        with self._get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute("DELETE FROM users WHERE user_id = ?", (user_id,))
            conn.commit()

        self._build_faiss_index()
        return {"status": "success", "userId": user_id}

    def recognize_face(self, image_path, threshold=0.12):
        """使用FAISS进行人脸识别"""
        try:
            # 图像预处理
            # if not self._preprocess_face_image(image_path):
            #     logger.warning(f"图像预处理失败,可能未检测到人脸: {image_path}")
            #     raise ValueError("图像中未检测到合格的人脸")

            # 提取查询人脸编码
            image = face_recognition.load_image_file(image_path)
            query_encodings = face_recognition.face_encodings(image)

            if not query_encodings:
                return None

            query_encoding = query_encodings[0].astype('float32').reshape(1, -1)
            self._validate_face_encoding(query_encoding[0])

            # FAISS搜索
            with self.index_lock:
                if self.index.ntotal == 0:  # 空索引检查
                    return None

                distances, indices = self.index.search(query_encoding, 1)

            # 处理结果
            if distances[0][0] < threshold:
                user_idx = indices[0][0]
                user_id = self.user_ids[user_idx]

                with self._get_connection() as conn:
                    cursor = conn.cursor()
                    cursor.execute(
                        "SELECT name FROM users WHERE user_id = ?",
                        (user_id,)
                    )
                    name = cursor.fetchone()[0]

                return {
                    "userId": user_id,
                    "userName": name,
                    "distance": float(distances[0][0])
                }
            return None

        except Exception as e:
            logger.error(f"人脸识别失败: {e}")
            raise

    def get_all_users(self):
        """获取所有人员信息"""
        try:
            with self._get_connection() as conn:
                cursor = conn.cursor()
                cursor.execute("SELECT user_id, name FROM users")
                users = cursor.fetchall()

            # 格式化返回数据
            users_list = [
                {
                    "userId": user[0],
                    "userName": user[1]
                }
                for user in users
            ]

            return {
                "status": "success",
                "data": users_list,
                "count": len(users_list)
            }
        except Exception as e:
            logger.error(f"获取用户列表失败: {e}")
            raise RuntimeError("获取用户列表失败")


# 初始化人脸识别系统
face_manager = FaceManagerServer()


def standard_response(success, data=None, message=None, status_code=200,count=None):
    """标准化响应格式"""
    response = {
        "success": success,
        "code": status_code,
        "message": message,
        "data": data,
        "count": count
    }
    return jsonify(response), 200


@app.route('/user', methods=['POST'])
def save_or_update_user():
    """
    保存或更新用户接口
    参数: name(用户名), user_id(用户ID), image(人脸图片)
    """
    try:
        if 'image' not in request.files:
            return standard_response(False, None, "未上传图片", 400)

        image_file = request.files['image']
        name = request.form.get('name')
        user_id = request.form.get('user_id')

        if not name or not user_id:
            return standard_response(False, None, "缺少必要参数: name或user_id", 400)

        # 保存临时文件
        temp_image_path = f"temp_{user_id}.jpg"
        image_file.save(temp_image_path)

        # 保存或更新用户
        result = face_manager.save_or_update_user(name, temp_image_path, user_id)

        # 删除临时文件
        if os.path.exists(temp_image_path):
            os.remove(temp_image_path)

        return standard_response(True, result, f"用户{result['action']}成功")

    except ValueError as e:
        return standard_response(False, None, str(e), 400)
    except Exception as e:
        logger.error(f"用户保存接口错误: {str(e)}")
        return standard_response(False, None, "内部服务器错误", 500)


@app.route('/user/<user_id>', methods=['DELETE'])
def delete_user(user_id):
    """
    删除用户接口
    参数: user_id(用户ID)
    """
    try:
        result = face_manager.delete_user(user_id)
        return standard_response(True, result, "用户删除成功")
    except ValueError as e:
        return standard_response(False, None, str(e), 404)
    except Exception as e:
        logger.error(f"删除接口错误: {str(e)}")
        return standard_response(False, None, "内部服务器错误", 500)


@app.route('/recognize', methods=['POST'])
def recognize():
    """
    人脸识别接口
    参数: image(人脸图片)
    """
    try:
        if 'image' not in request.files:
            return standard_response(False, None, "未上传图片", 400)

        image_file = request.files['image']

        # 保存临时文件
        temp_image_path = "temp_recognition.jpg"
        image_file.save(temp_image_path)

        # 识别人脸
        result = face_manager.recognize_face(temp_image_path)

        # 删除临时文件
        if os.path.exists(temp_image_path):
            os.remove(temp_image_path)

        if result:
            return standard_response(True, result, "识别成功")
        else:
            return standard_response(True, None, "未识别到匹配用户", 400)
    except ValueError as e:
        return standard_response(False, None, str(e), 400)
    except Exception as e:
        logger.error(f"识别接口错误: {str(e)}")
        return standard_response(False, None, "内部服务器错误", 500)


@app.route('/whrj/face/registerUser', methods=['POST'])
def registerUser():
    """注册/更新用户接口"""
    try:
        # 解析JSON请求体
        data = request.get_json()
        if not data:
            return standard_response(False, None, "请求体不能为空", 400)
        imgBase64 = data.get('imgBase64')
        name = data.get('userName')
        user_id = data.get('userId')

        if not name or not user_id:
            return standard_response(False, None, "缺少必要参数: name或user_id", 400)

        # 保存临时文件
        temp_image_path = f"temp_{user_id}.jpg"
        if not face_manager.base64_to_image(imgBase64, temp_image_path):
            return standard_response(False, None, "图片转换失败", 400)

        # 保存或更新用户
        result = face_manager.save_or_update_user(name, temp_image_path, user_id)

        # 删除临时文件
        if os.path.exists(temp_image_path):
            os.remove(temp_image_path)

        return standard_response(True, result, f"用户{result['action']}成功")

    except ValueError as e:
        return standard_response(False, None, str(e), 400)
    except Exception as e:
        logger.error(f"用户保存接口错误: {str(e)}")
        return standard_response(False, None, "内部服务器错误", 500)


@app.route('/whrj/face/faceIdentify', methods=['POST'])
def faceIdentify():
    """人脸识别接口(FAISS优化版)"""
    try:
        # 解析JSON请求体
        data = request.get_json()
        if not data:
            return standard_response(False, None, "请求体不能为空", 400)
        imgBase64 = data.get('imgBase64')
        if not imgBase64:
            return standard_response(False, None, "缺少必要参数: imgBase64", 400)

        # 保存临时文件
        temp_image_path = "temp_faceIdentify.jpg"
        if not face_manager.base64_to_image(imgBase64, temp_image_path):
            return standard_response(False, None, "图片转换失败", 400)

        # 识别人脸(使用FAISS优化)
        result = face_manager.recognize_face(temp_image_path)

        # 删除临时文件
        # if os.path.exists(temp_image_path):
        #     os.remove(temp_image_path)

        if result:
            return standard_response(True, result, "识别成功")
        else:
            return standard_response(True, None, "未识别到匹配用户", 400)
    except ValueError as e:
        logger.error(f"未识别到用户 ValueError : {str(e)}")
        return standard_response(False, None, str(e), 400)
    except Exception as e:
        logger.error(f"识别接口错误: {str(e)}")
        return standard_response(False, None, "内部服务器错误", 500)


@app.route('/whrj/face/faceDelete', methods=['POST'])
def faceDelete():
    """删除用户接口"""
    try:
        # 解析JSON请求体
        data = request.get_json()
        if not data:
            return standard_response(False, None, "请求体不能为空", 400)
        user_id = data.get('userId')
        if not user_id:
            return standard_response(False, None, "缺少必要参数: user_id", 400)
        result = face_manager.delete_user(user_id)
        return standard_response(True, result, "用户删除成功")
    except ValueError as e:
        return standard_response(False, None, str(e), 404)
    except Exception as e:
        logger.error(f"删除接口错误: {str(e)}")
        return standard_response(False, None, "内部服务器错误", 500)


@app.route('/whrj/getUserAll', methods=['GET'])
def get_all_users():
    """获取所有人员接口"""
    try:
        result = face_manager.get_all_users()
        return standard_response(True, result["data"], "获取用户列表成功",200,result["count"])
    except Exception as e:
        logger.error(f"获取用户列表接口错误: {str(e)}")
        return standard_response(False, None, "内部服务器错误", 500)

def recognize_test():
    image_path = r"D:\PycharmProjects\pythonProject1\com\face\detected_faces\9.jpg"
    res = face_manager.recognize_face(image_path)
    print(res)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

人脸识别客户端代码

import sys
import os
import logging
import requests
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QPushButton, QLineEdit, QListWidget, QMessageBox,
                             QFileDialog, QTabWidget, QFrame, QSizePolicy, QFormLayout, QInputDialog)
from PyQt5.QtGui import QPixmap, QImage, QFont, QIcon
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QObject, QSettings
import cv2
import base64
from datetime import datetime

# 打包成exe  pyinstaller --onefile --windowed FacePcDemoV2.py
# pyinstaller --onefile --windowed --name FaceDemoTest FacePcDemoV2.py
# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('face_recognition_system.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 配置API地址
API_BASE_URL = "http://192.168.1.66:5000/whrj"  # 根据实际修改


class SignalHandler(QObject):
    """自定义信号处理器"""
    error_signal = pyqtSignal(str, str)
    info_signal = pyqtSignal(str)


class FaceRecognitionSystemApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.signals = SignalHandler()
        self.init_logging()
        self.setup_ui()
        self.show_input_dialog()
        self.setup_camera()
        self.setup_connections()
        logger.info("人脸识别系统初始化完成")

    def show_input_dialog(self):
        # 弹出输入对话框
        text, ok = QInputDialog.getText(
            self,
            '提示',
            '请输入服务器ip:',
            text='192.168.1.66'  # 可选的默认值
        )

        # 如果用户点击了确定按钮
        if ok:
            # 在这里处理获取到的输入内容
            global API_BASE_URL
            API_BASE_URL = f"http://{text}:5000/whrj"

    def init_logging(self):
        """初始化日志系统"""
        self.signals.error_signal.connect(self.show_error)
        self.signals.info_signal.connect(self.show_info)

    def display_selected_image(self, image_path):
        """显示选择的图片在摄像头区域"""
        try:
            # 读取图片文件
            frame = cv2.imread(image_path)
            if frame is None:
                raise IOError("无法读取图片文件")

            # 调整大小以匹配摄像头显示区域
            frame = cv2.resize(frame, (400, 400))
            frame = cv2.flip(frame, 1)  # 水平翻转以匹配摄像头显示
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

            h, w, ch = frame.shape
            bytes_per_line = ch * w
            q_img = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
            self.camera_label.setPixmap(QPixmap.fromImage(q_img).scaled(
                self.camera_label.width(), self.camera_label.height(),
                Qt.KeepAspectRatio, Qt.SmoothTransformation
            ))

        except Exception as e:
            logger.error(f"显示选择的图片时出错: {str(e)}")
            self.signals.error_signal.emit("图片显示错误", f"显示选择的图片时出错:\n{str(e)}")
            self.clear_image()  # 出错时清除图片选择

    def setup_ui(self):
        """设置用户界面"""
        self.setWindowTitle("智能人脸识别系统")
        self.setGeometry(100, 100, 800, 550)
        self.setStyleSheet("""
            QMainWindow {
                background-color: #f5f5f5;
            }
            QTabWidget::pane {
                border: 1px solid #c4c4c4;
                background: white;
            }
            QTabBar::tab {
                padding: 8px 16px;
                background: #e0e0e0;
                border: 1px solid #c4c4c4;
                border-bottom: none;
                border-radius: 4px 4px 0 0;
            }
            QTabBar::tab:selected {
                background: white;
                border-bottom: 2px solid #2196F3;
            }
            QPushButton {
                padding: 8px 16px;
                background-color: #2196F3;
                color: white;
                border: none;
                border-radius: 4px;
                min-width: 80px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
            QPushButton:disabled {
                background-color: #BDBDBD;
            }
            QLineEdit {
                padding: 8px;
                border: 1px solid #c4c4c4;
                border-radius: 4px;
            }
            QLabel {
                font-size: 14px;
            }
        """)

        # 主布局
        main_widget = QWidget()
        main_layout = QHBoxLayout()
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

        # 左侧摄像头区域
        camera_frame = QFrame()
        camera_layout = QVBoxLayout()
        camera_frame.setLayout(camera_layout)
        camera_frame.setStyleSheet("""
            QFrame {
                background: #ffffff;
                border: 2px solid #2196F3;
                border-radius: 8px;
                padding: 8px;
            }
        """)

        self.camera_label = QLabel("摄像头画面")
        self.camera_label.setFixedSize(400, 400)
        self.camera_label.setAlignment(Qt.AlignCenter)
        self.camera_label.setStyleSheet("""
            QLabel {
                background: #e0e0e0;
                border: 1px dashed #757575;
            }
        """)
        camera_layout.addWidget(self.camera_label)

        status_label = QLabel("系统状态: 运行中")
        status_label.setFont(QFont("Arial", 10, QFont.Bold))
        status_label.setStyleSheet("color: #4CAF50;")
        camera_layout.addWidget(status_label, alignment=Qt.AlignBottom | Qt.AlignHCenter)

        # 右侧功能区域
        right_widget = QTabWidget()
        right_widget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

        # 注册标签页
        register_tab = self.create_register_tab()

        # 识别标签页
        recognize_tab = self.create_recognize_tab()

        # 用户管理标签页
        manage_tab = self.create_manage_tab()

        # 添加标签页
        right_widget.addTab(recognize_tab, "人脸识别")
        right_widget.addTab(register_tab, "用户注册")
        right_widget.addTab(manage_tab, "用户管理")

        # 添加到主布局
        main_layout.addWidget(camera_frame, stretch=1)
        main_layout.addWidget(right_widget, stretch=1)

    def create_register_tab(self):
        """创建注册标签页"""
        register_tab = QWidget()
        register_layout = QVBoxLayout()
        register_tab.setLayout(register_layout)

        # 表单区域
        form_frame = QFrame()
        form_layout = QFormLayout()
        form_frame.setLayout(form_layout)
        form_frame.setStyleSheet("""
            QFrame {
                background: #ffffff;
                border: 1px solid #e0e0e0;
                border-radius: 8px;
                padding: 16px;
            }
            QLabel {
                font-weight: normal;
                color: red;
                min-height: 20px;
                max-height: 20px;
                font-size: 12px;
                padding:8px;
                border: none;
            }
            QLineEdit {
                min-height: 20px;
                max-height: 20px;
                font-size: 12px;
            }
        """)
        self.register_name_label = QLabel("用户姓名:")
        self.register_name_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)  # 设置标签对齐方式
        self.register_name_input = QLineEdit()
        self.register_name_input.setPlaceholderText("请输入用户姓名")
        form_layout.addRow(self.register_name_label, self.register_name_input)

        # 创建用户ID标签和输入框
        self.register_id_label = QLabel("用户ID:")
        self.register_id_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)  # 设置标签对齐方式
        self.register_id_input = QLineEdit()
        self.register_id_input.setPlaceholderText("请输入用户ID")
        form_layout.addRow(self.register_id_label, self.register_id_input)

        # 新增:图片选择部分
        self.image_path_label = QLabel("选择图片文件:")
        self.image_path_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        # 图片路径显示和选择按钮的水平布局
        image_path_layout = QHBoxLayout()
        self.image_path_input = QLineEdit()
        self.image_path_input.setPlaceholderText("未选择图片文件")
        self.image_path_input.setReadOnly(True)
        self.image_select_btn = QPushButton("选择")
        self.image_select_btn.setToolTip("选择本地图片文件用于注册")
        self.image_select_btn.clicked.connect(self.select_image_file)
        self.delete_select_btn = QPushButton("删除")
        self.delete_select_btn.setToolTip("删除")
        self.delete_select_btn.clicked.connect(self.delete_image_file)
        image_path_layout.addWidget(self.image_path_input, stretch=5)
        image_path_layout.addWidget(self.image_select_btn, stretch=1)
        image_path_layout.addWidget(self.delete_select_btn, stretch=1)
        form_layout.addRow(self.image_path_label, image_path_layout)

        register_layout.addWidget(form_frame)

        # 按钮区域
        btn_frame = QFrame()
        btn_layout = QHBoxLayout()
        btn_frame.setLayout(btn_layout)

        self.register_btn = QPushButton("注册人脸")
        self.register_btn.setToolTip("点击开始注册人脸信息")
        btn_layout.addWidget(self.register_btn)

        register_layout.addWidget(btn_frame)

        # 添加一些间距
        register_layout.addSpacing(20)

        # 说明文字
        info_label = QLabel("""
        <b>注册说明:</b><br>
        1. 可以选择本地图片文件或使用摄像头实时画面<br>
        2. 如果选择了图片文件,将使用该图片进行注册<br>
        3. 如果没有选择图片文件,将使用摄像头当前画面进行注册
        """)
        info_label.setStyleSheet("color: #616161;")
        info_label.setWordWrap(True)
        register_layout.addWidget(info_label)

        return register_tab

    def select_image_file(self):
        """选择图片文件"""
        try:
            file_path, _ = QFileDialog.getOpenFileName(
                self, "选择图片文件", "",
                "图片文件 (*.jpg *.jpeg *.png *.bmp)"
            )

            if file_path:
                self.image_path_input.setText(file_path)
                logger.info(f"已选择图片文件: {file_path}")

                # 显示选择的图片在摄像头区域
                self.display_selected_image(file_path)

        except Exception as e:
            logger.error(f"选择图片文件时出错: {str(e)}")
            self.signals.error_signal.emit("图片选择错误", f"选择图片文件时出错:\n{str(e)}")

    def delete_image_file(self):
        """选择图片文件"""
        try:
            self.image_path_input.setText(None)
        except Exception as e:
            logger.error(f"选择图片文件时出错: {str(e)}")

    def display_selected_image(self, image_path):
        """显示选择的图片在摄像头区域"""
        try:
            # 读取图片文件
            frame = cv2.imread(image_path)
            if frame is None:
                raise IOError("无法读取图片文件")

            # 调整大小以匹配摄像头显示区域
            frame = cv2.resize(frame, (400, 400))
            frame = cv2.flip(frame, 1)  # 水平翻转以匹配摄像头显示
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

            h, w, ch = frame.shape
            bytes_per_line = ch * w
            q_img = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
            self.camera_label.setPixmap(QPixmap.fromImage(q_img).scaled(
                self.camera_label.width(), self.camera_label.height(),
                Qt.KeepAspectRatio, Qt.SmoothTransformation
            ))

        except Exception as e:
            logger.error(f"显示选择的图片时出错: {str(e)}")
            self.signals.error_signal.emit("图片显示错误", f"显示选择的图片时出错:\n{str(e)}")

    def create_recognize_tab(self):
        """创建识别标签页"""
        recognize_tab = QWidget()
        recognize_layout = QVBoxLayout()
        recognize_tab.setLayout(recognize_layout)

        # 结果显示区域
        result_frame = QFrame()
        result_layout = QVBoxLayout()
        result_frame.setLayout(result_layout)
        result_frame.setStyleSheet("""
            QFrame {
                background: #ffffff;
                border: 1px solid #e0e0e0;
                border-radius: 8px;
                padding: 16px;
                min-height: 150px;
            }
        """)

        self.recognize_result = QLabel("等待识别...")
        self.recognize_result.setAlignment(Qt.AlignCenter)
        self.recognize_result.setStyleSheet("""
            QLabel {
                font-size: 18px;
                color: #212121;
            }
        """)
        result_layout.addWidget(self.recognize_result)

        recognize_layout.addWidget(result_frame)

        # 按钮区域
        btn_frame = QFrame()
        btn_layout = QHBoxLayout()
        btn_frame.setLayout(btn_layout)

        self.recognize_btn = QPushButton("识别人脸")
        self.recognize_btn.setToolTip("点击开始识别人脸信息")
        btn_layout.addWidget(self.recognize_btn)

        recognize_layout.addWidget(btn_frame)

        # 添加一些间距
        recognize_layout.addSpacing(20)

        # 说明文字
        info_label = QLabel("""
        <b>识别说明:</b><br>
        1. 确保人脸正对摄像头<br>
        2. 点击识别按钮后保持面部在画面中<br>
        3. 系统会显示识别结果和相似度
        """)
        info_label.setStyleSheet("color: #616161;")
        info_label.setWordWrap(True)
        recognize_layout.addWidget(info_label)

        return recognize_tab

    def create_manage_tab(self):
        """创建用户管理标签页"""
        manage_tab = QWidget()
        manage_layout = QVBoxLayout()
        manage_tab.setLayout(manage_layout)

        # 新增:搜索区域
        search_frame = QFrame()
        search_layout = QHBoxLayout()
        search_frame.setLayout(search_layout)
        search_frame.setStyleSheet("""
                    QFrame {
                        background: #ffffff;
                        border: 1px solid #e0e0e0;
                        border-radius: 8px;
                        padding: 16px;
                    }
                    QLabel {
                        font-weight: normal;
                        color: red;
                        min-height: 20px;
                        max-height: 20px;
                        font-size: 12px;
                        padding:8px;
                        border: none;
                    }
                    QLineEdit {
                        min-height: 20px;
                        max-height: 20px;
                        font-size: 12px;
                    }
                """)

        self.search_label = QLabel("搜索用户ID:")
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("输入用户ID进行模糊搜索")
        self.search_input.textChanged.connect(self.filter_user_list)

        search_layout.addWidget(self.search_label)
        search_layout.addWidget(self.search_input, stretch=1)

        manage_layout.addWidget(search_frame)

        # 用户列表区域
        list_frame = QFrame()
        list_layout = QVBoxLayout()
        list_frame.setLayout(list_layout)
        list_frame.setStyleSheet("""
            QFrame {
                background: #ffffff;
                border: 1px solid #e0e0e0;
                border-radius: 8px;
            }
        """)

        self.user_list = QListWidget()
        self.user_list.setStyleSheet("""
            QListWidget {
                border: none;
                font-size: 14px;
            }
            QListWidget::item {
                padding: 8px;
                border-bottom: 1px solid #e0e0e0;
            }
            QListWidget::item:hover {
                background: #f5f5f5;
                color: black;  /* 悬停时的文字颜色 */
            }
            QListWidget::item:selected {
                background: #2196F3;  /* 选中项的背景颜色 */
                color: black;         /* 选中项的文字颜色 */
            }
        """)
        list_layout.addWidget(self.user_list)

        manage_layout.addWidget(list_frame)

        # 按钮区域
        btn_frame = QFrame()
        btn_layout = QHBoxLayout()
        btn_frame.setLayout(btn_layout)
        btn_layout.setContentsMargins(0, 10, 0, 0)

        self.refresh_btn = QPushButton("刷新列表")
        self.delete_btn = QPushButton("删除用户")
        self.delete_btn.setEnabled(False)

        btn_layout.addWidget(self.refresh_btn)
        btn_layout.addWidget(self.delete_btn)

        manage_layout.addWidget(btn_frame)

        # 添加一些间距
        manage_layout.addSpacing(20)

        # 说明文字
        info_label = QLabel("""
        <b>用户管理说明:</b><br>
        1. 在搜索框中输入用户ID进行模糊搜索<br>
        2. 从列表中选择要管理的用户<br>
        3. 点击删除按钮可移除用户<br>
        4. 定期刷新列表获取最新数据
        """)
        info_label.setStyleSheet("color: #616161;")
        info_label.setWordWrap(True)
        manage_layout.addWidget(info_label)

        return manage_tab

    def filter_user_list(self, search_text):
        """根据搜索文本过滤用户列表"""
        try:
            # 先清空列表
            self.user_list.clear()

            if not search_text:
                # 如果搜索框为空,重新加载完整列表
                self.load_user_list()
                return

            logger.info(f"正在根据用户ID搜索: {search_text}")

            response = requests.get(
                f"{API_BASE_URL}/getUserAll",
                timeout=10
            )
            result = response.json()

            if result.get('success'):
                users = result.get('data', [])
                if users:
                    # 执行模糊搜索
                    search_text = search_text.lower()
                    filtered_users = [
                        user for user in users
                        if search_text in user['userId'].lower()
                    ]

                    if filtered_users:
                        for user in filtered_users:
                            self.user_list.addItem(f"{user['userId']} - {user['userName']}")
                        logger.info(f"找到 {len(filtered_users)} 个匹配用户")
                    else:
                        self.user_list.addItem("(未找到匹配用户)")
                        logger.info("未找到匹配用户")
                else:
                    self.user_list.addItem("(无用户数据)")
                    logger.info("用户列表为空")
            else:
                error_msg = result.get('message', '获取用户列表失败')
                logger.warning(f"获取用户列表失败: {error_msg}")
                QMessageBox.warning(self, "失败", error_msg)

        except Exception as e:
            logger.error(f"搜索用户时发生错误: {str(e)}")
            self.signals.error_signal.emit("搜索错误", f"搜索用户时发生错误:\n{str(e)}")

    def setup_camera(self):
        """初始化摄像头"""
        try:
            self.cap = cv2.VideoCapture(0)
            if not self.cap.isOpened():
                raise IOError("无法打开摄像头设备")

            # 设置摄像头分辨率
            self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 400)
            self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 400)

            # 定时器更新摄像头画面
            self.timer = QTimer(self)
            self.timer.timeout.connect(self.update_camera)
            self.timer.start(30)  # 30ms更新一次

            logger.info("摄像头初始化成功")

        except Exception as e:
            logger.error(f"摄像头初始化失败: {str(e)}")
            QMessageBox.critical(self, "错误", f"无法初始化摄像头:\n{str(e)}")
            sys.exit(1)

    def setup_connections(self):
        """设置信号和槽连接"""
        self.register_btn.clicked.connect(self.register_face)
        self.recognize_btn.clicked.connect(self.recognize_face)
        self.refresh_btn.clicked.connect(self.load_user_list)
        self.delete_btn.clicked.connect(self.delete_user)
        self.user_list.itemSelectionChanged.connect(self.update_delete_button_state)

    def update_camera(self):
        """更新摄像头画面"""
        try:
            if self.image_path_input.text():
                return  # 如果有选择的图片,不更新摄像头画面
            ret, frame = self.cap.read()
            if ret:
                frame = cv2.flip(frame, 1)  # 参数1表示水平翻转
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                h, w, ch = frame.shape
                bytes_per_line = ch * w
                q_img = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
                self.camera_label.setPixmap(QPixmap.fromImage(q_img).scaled(
                    self.camera_label.width(), self.camera_label.height(),
                    Qt.KeepAspectRatio, Qt.SmoothTransformation
                ))
        except Exception as e:
            logger.error(f"更新摄像头画面时出错: {str(e)}")
            self.signals.error_signal.emit("摄像头错误", f"无法获取画面: {str(e)}")

    def capture_face(self):
        """捕获当前画面(摄像头或选择的图片)"""
        try:
            # 检查是否有选择的图片文件
            image_path = self.image_path_input.text()
            if image_path and os.path.exists(image_path):
                logger.info(f"使用选择的图片文件进行注册: {image_path}")
                return image_path

            # 如果没有选择的图片文件,使用摄像头画面
            ret, frame = self.cap.read()
            if not ret:
                raise IOError("无法从摄像头获取画面")

            # 保存临时图片
            timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
            temp_path = f"temp_capture_{timestamp}.jpg"
            cv2.imwrite(temp_path, frame)
            logger.info(f"成功捕获并保存临时图片: {temp_path}")
            return temp_path

        except Exception as e:
            logger.error(f"捕获人脸时出错: {str(e)}")
            raise

    def image_to_base64(self, image_path):
        """将图片转换为base64编码"""
        try:
            with open(image_path, "rb") as image_file:
                return base64.b64encode(image_file.read()).decode('utf-8')
        except Exception as e:
            logger.error(f"图片转base64时出错: {str(e)}")
            raise

    def register_face(self):
        """注册人脸信息"""
        try:
            name = self.register_name_input.text().strip()
            user_id = self.register_id_input.text().strip()

            # 检查用户名和用户ID是否为空
            if not name:
                QMessageBox.warning(self, "输入错误", "用户姓名不能为空")
                return
            if not user_id:
                QMessageBox.warning(self, "输入错误", "用户ID不能为空")
                return

            logger.info(f"开始注册用户: ID={user_id}, Name={name}")

            # 获取图片路径(优先使用选择的图片文件)
            image_path = self.image_path_input.text()
            if not image_path or not os.path.exists(image_path):
                image_path = self.capture_face()

            try:
                # 转换为base64
                img_base64 = self.image_to_base64(image_path)
                # 调用API
                # 确保base64字符串格式正确
                img_base64 = f"data:image/jpeg;base64,{img_base64}"
                data = {
                    'userName': name,
                    'userId': user_id,
                    "imgBase64": img_base64
                }

                logger.info(f"调用注册API: {API_BASE_URL}/face/registerUser")
                response = requests.post(
                    f"{API_BASE_URL}/face/registerUser",
                    json=data,
                    timeout=10
                )
                result = response.json()

                if result.get('success'):
                    logger.info(f"用户注册成功: {name} ({user_id})")
                    QMessageBox.information(self, "成功", f"用户 {name} 注册成功!")
                    self.load_user_list()
                    # 清空输入框
                    self.register_name_input.clear()
                    self.register_id_input.clear()
                    self.image_path_input.clear()
                    # 恢复摄像头显示
                    self.update_camera()
                else:
                    error_msg = result.get('message', '注册失败')
                    logger.warning(f"用户注册失败: {error_msg}")
                    QMessageBox.warning(self, "失败", error_msg)

            finally:
                # 如果是临时文件,删除它
                self.image_path_input.setText(None)
                if image_path and ("temp_capture_" in image_path) and os.path.exists(image_path):
                    os.remove(image_path)
                    logger.info(f"已删除临时图片文件: {image_path}")

        except Exception as e:
            logger.error(f"注册过程中发生错误: {str(e)}")
            self.signals.error_signal.emit("注册错误", f"注册过程中发生错误:\n{str(e)}")

    def recognize_face(self):
        """识别人脸信息"""
        try:
            logger.info("开始识别人脸...")

            # 获取图片路径(优先使用选择的图片文件)
            image_path = self.image_path_input.text()
            if not image_path or not os.path.exists(image_path):
                # 捕获当前帧
                image_path = self.capture_face()
            try:
                # 转换为base64
                img_base64 = self.image_to_base64(image_path)

                # 调用API
                # 确保base64字符串格式正确
                img_base64 = f"data:image/jpeg;base64,{img_base64}"

                # 3. 准备表单数据
                data = {
                    "imgBase64": img_base64
                    # 添加其他必要参数
                }
                logger.info(f"调用识别API: {API_BASE_URL}/face/faceIdentify")
                response = requests.post(
                    f"{API_BASE_URL}/face/faceIdentify",
                    # data=data,
                    json=data,
                    timeout=10
                )
                result = response.json()

                if result.get('success'):
                    if result.get('data'):
                        user_info = result['data']
                        similarity = 1 - user_info['distance']
                        display_text = (
                            f"<b>识别成功!</b><br><br>"
                            f"用户ID: {user_info['userId']}<br>"
                            f"姓名: {user_info['userName']}<br>"
                            f"相似度: {similarity:.2%}"
                        )
                        logger.info(f"识别成功: {user_info['userName']} ({user_info['userId']})")
                    else:
                        display_text = "<b>未识别到匹配用户</b>"
                        logger.info("未识别到匹配用户")

                    self.recognize_result.setText(display_text)
                else:
                    error_msg = result.get('message', '识别失败')
                    logger.warning(f"人脸识别失败: {error_msg}")
                    QMessageBox.warning(self, "失败", error_msg)

            finally:
                if os.path.exists(image_path) and image_path.startswith('temp_capture'):
                    os.remove(image_path)
                    logger.info(f"已删除临时图片文件: {image_path}")

        except Exception as e:
            logger.error(f"识别过程中发生错误: {str(e)}")
            self.signals.error_signal.emit("识别错误", f"识别过程中发生错误:\n{str(e)}")

    def load_user_list(self):
        """加载用户列表"""
        try:
            logger.info("正在加载用户列表...")
            self.user_list.clear()

            response = requests.get(
                f"{API_BASE_URL}/getUserAll",
                timeout=10
            )
            result = response.json()

            if result.get('success'):
                users = result.get('data', [])
                if users:
                    for user in users:
                        self.user_list.addItem(f"{user['userId']} - {user['userName']}")
                    logger.info(f"成功加载 {len(users)} 个用户")
                else:
                    self.user_list.addItem("(无用户数据)")
                    logger.info("用户列表为空")
            else:
                error_msg = result.get('message', '获取用户列表失败')
                logger.warning(f"获取用户列表失败: {error_msg}")
                QMessageBox.warning(self, "失败", error_msg)

        except Exception as e:
            logger.error(f"获取用户列表时发生错误: {str(e)}")
            self.signals.error_signal.emit("列表错误", f"获取用户列表时发生错误:\n{str(e)}")

    def update_delete_button_state(self):
        """更新删除按钮状态"""
        has_selection = bool(self.user_list.selectedItems())
        self.delete_btn.setEnabled(has_selection)

    def delete_user(self):
        """删除用户"""
        try:
            current_items = self.user_list.selectedItems()
            if not current_items:
                raise ValueError("请先选择要删除的用户")

            current_item = current_items[0]
            user_id = current_item.text().split(' - ')[0]
            user_name = current_item.text().split(' - ')[1]

            reply = QMessageBox.question(
                self, '确认删除',
                f"确定要删除用户 <b>{user_name}</b> (ID: {user_id}) 吗?<br>"
                "<b>警告: 此操作不可逆!</b>",
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No
            )

            if reply == QMessageBox.Yes:
                logger.info(f"开始删除用户: ID={user_id}, Name={user_name}")

                data = {'userId': user_id}
                response = requests.post(
                    f"{API_BASE_URL}/face/faceDelete",
                    # data=data,
                    json=data,
                    timeout=10
                )
                result = response.json()

                if result.get('success'):
                    logger.info(f"用户删除成功: {user_name} ({user_id})")
                    QMessageBox.information(self, "成功", "用户删除成功!")
                    self.load_user_list()
                else:
                    error_msg = result.get('message', '删除失败')
                    logger.warning(f"用户删除失败: {error_msg}")
                    QMessageBox.warning(self, "失败", error_msg)

        except Exception as e:
            logger.error(f"删除用户时发生错误: {str(e)}")
            self.signals.error_signal.emit("删除错误", f"删除用户时发生错误:\n{str(e)}")

    def show_error(self, title, message):
        """显示错误消息"""
        QMessageBox.critical(self, title, message)

    def show_info(self, message):
        """显示信息消息"""
        QMessageBox.information(self, "信息", message)

    def closeEvent(self, event):
        # 创建一个确认对话框
        reply = QMessageBox.question(
            self,
            '确认关闭',
            '确定要关闭窗口吗?',
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No
        )

        if reply == QMessageBox.Yes:
            """关闭事件处理"""
            try:
                logger.info("正在关闭系统...")

                # 释放摄像头
                if hasattr(self, 'cap') and self.cap.isOpened():
                    self.cap.release()
                    logger.info("摄像头已释放")

                # 保存窗口状态
                self.settings = QSettings("FaceRecognitionSystemApp", "FaceRecognitionSystemApp")
                self.settings.setValue("geometry", self.saveGeometry())
                self.settings.setValue("windowState", self.saveState())

                logger.info("系统已安全关闭")
                event.accept()

            except Exception as e:
                logger.error(f"关闭系统时出错: {str(e)}")
                event.accept()  # 仍然接受关闭事件
        else:
            event.ignore()  # 忽略关闭事件


if __name__ == '__main__':
    from PyQt5.QtCore import QSettings

    app = QApplication(sys.argv)

    # 设置应用程序信息
    app.setApplicationName("FaceRecognitionSystemApp")
    app.setApplicationDisplayName("智能人脸识别系统")
    # app.setWindowIcon(QIcon("icon.png"))  # 如果有图标文件可以取消注释

    # 加载保存的窗口状态
    settings = QSettings("FaceRecognitionSystemApp", "FaceRecognitionSystemApp")

    window = FaceRecognitionSystemApp()
    if settings.contains("geometry"):
        window.restoreGeometry(settings.value("geometry"))
    if settings.contains("windowState"):
        window.restoreState(settings.value("windowState"))

    window.show()
    sys.exit(app.exec_())

环境安装命令

1 设置镜像地址
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
conda config --set show_channel_urls yes

2 创建虚拟环境 这里用 py3.8
conda create --name cv python=3.8
3 激活
conda activate  cv
4 安装dlib库 过程较慢 耐心等待
conda install -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge dlib
5 批量安装其他库
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn
pip install mediapipe -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn
pip install pyttsx3 -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn
pip install func_timeout -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn
pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install face_recognition -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install faiss-cpu -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install flask -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install fastapi -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install pydantic -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install uvicorn -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install flask_cors  -i https://pypi.tuna.tsinghua.edu.cn/simple

先开启服务端在开启客户端代码 测试