人脸识别服务端代码
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)
log_handler = TimedRotatingFileHandler(
'face.log',
when='midnight',
interval=1,
backupCount=7,
encoding='utf-8'
)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
log_handler.setFormatter(formatter)
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))
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
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:
"""
使用face_recognition库优化的人脸预处理
优点:
- 自动处理模型下载
- 支持多角度人脸检测
- 内置人脸关键点检测
"""
try:
image = cv2.imread(image_path)
if image is None:
logger.warning(f"无法加载图像: {image_path}")
return False
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
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
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
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)
cropped = image[top:bottom, left:right]
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)
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:
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:
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])
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:
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:
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)
result = face_manager.recognize_face(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:
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
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_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)
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)
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)
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()
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:
img_base64 = self.image_to_base64(image_path)
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:
img_base64 = self.image_to_base64(image_path)
img_base64 = f"data:image/jpeg;base64,{img_base64}"
data = {
"imgBase64": img_base64
}
logger.info(f"调用识别API: {API_BASE_URL}/face/faceIdentify")
response = requests.post(
f"{API_BASE_URL}/face/faceIdentify",
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",
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("智能人脸识别系统")
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:
conda config --add channels https:
conda config --add channels https:
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:
5 批量安装其他库
pip install opencv-python -i https:
pip install mediapipe -i https:
pip install pyttsx3 -i https:
pip install requests -i https:
pip install func_timeout -i https:
pip install pyqt5 -i https:
pip install face_recognition -i https:
pip install faiss-cpu -i https:
pip install flask -i https:
pip install fastapi -i https:
pip install pydantic -i https:
pip install uvicorn -i https:
pip install flask_cors -i https:
先开启服务端在开启客户端代码 测试