内容安全审核
This commit is contained in:
BIN
util/__pycache__/face_util.cpython-310.pyc
Normal file
BIN
util/__pycache__/face_util.cpython-310.pyc
Normal file
Binary file not shown.
196
util/face_util.py
Normal file
196
util/face_util.py
Normal file
@ -0,0 +1,196 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import insightface
|
||||
from insightface.app import FaceAnalysis
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
import logging
|
||||
from mysql.connector import Error as MySQLError
|
||||
|
||||
from ds.db import db
|
||||
|
||||
# 配置日志(便于排查)
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - [FaceUtil] - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 全局变量存储InsightFace引擎和特征列表
|
||||
_insightface_app = None
|
||||
_feature_list = []
|
||||
|
||||
|
||||
def init_insightface():
|
||||
"""初始化InsightFace引擎(确保成功后再使用)"""
|
||||
global _insightface_app
|
||||
try:
|
||||
if _insightface_app is not None:
|
||||
logger.info("InsightFace引擎已初始化、无需重复执行")
|
||||
return _insightface_app
|
||||
|
||||
logger.info("正在初始化InsightFace引擎(模型:buffalo_l)...")
|
||||
# 手动指定模型下载路径(避免权限问题、可选)
|
||||
app = FaceAnalysis(
|
||||
name='buffalo_l',
|
||||
root='~/.insightface', # 模型默认下载路径
|
||||
providers=['CPUExecutionProvider'] # 强制用CPU(若有GPU可加'CUDAExecutionProvider')
|
||||
)
|
||||
app.prepare(ctx_id=0, det_size=(640, 640)) # det_size越大、小人脸检测越准
|
||||
logger.info("InsightFace引擎初始化完成")
|
||||
_insightface_app = app
|
||||
return app
|
||||
except Exception as e:
|
||||
logger.error(f"InsightFace初始化失败:{str(e)}", exc_info=True) # 打印详细堆栈
|
||||
_insightface_app = None
|
||||
return None
|
||||
|
||||
|
||||
def add_binary_data(binary_data):
|
||||
"""
|
||||
接收单张图片的二进制数据、提取特征并保存
|
||||
返回:(True, 特征值numpy数组) 或 (False, 错误信息字符串)
|
||||
"""
|
||||
global _insightface_app, _feature_list
|
||||
|
||||
# 1. 先检查引擎是否初始化成功
|
||||
if not _insightface_app:
|
||||
init_result = init_insightface() # 尝试重新初始化
|
||||
if not init_result:
|
||||
error_msg = "InsightFace引擎未初始化、无法检测人脸"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
try:
|
||||
# 2. 验证二进制数据有效性
|
||||
if len(binary_data) < 1024: # 过滤过小的无效图片(小于1KB)
|
||||
error_msg = f"图片过小({len(binary_data)}字节)、可能不是有效图片"
|
||||
logger.warning(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
# 3. 二进制数据转CV2格式(关键步骤、避免通道错误)
|
||||
try:
|
||||
img = Image.open(BytesIO(binary_data)).convert("RGB") # 强制转RGB
|
||||
frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) # InsightFace需要BGR格式
|
||||
except Exception as e:
|
||||
error_msg = f"图片格式转换失败:{str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return False, error_msg
|
||||
|
||||
# 4. 检查图片尺寸(避免极端尺寸导致检测失败)
|
||||
height, width = frame.shape[:2]
|
||||
if height < 64 or width < 64: # 人脸检测最小建议尺寸
|
||||
error_msg = f"图片尺寸过小({width}x{height})、需至少64x64像素"
|
||||
logger.warning(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
# 5. 调用InsightFace检测人脸
|
||||
logger.info(f"开始检测人脸(图片尺寸:{width}x{height}、格式:BGR)")
|
||||
faces = _insightface_app.get(frame)
|
||||
|
||||
if not faces:
|
||||
error_msg = "未检测到人脸(请确保图片包含清晰正面人脸、无遮挡、不模糊)"
|
||||
logger.warning(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
# 6. 提取特征并保存
|
||||
current_feature = faces[0].embedding
|
||||
_feature_list.append(current_feature)
|
||||
logger.info(f"人脸检测成功、提取特征值(维度:{current_feature.shape[0]})、累计特征数:{len(_feature_list)}")
|
||||
return True, current_feature
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"处理图片时发生异常:{str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return False, error_msg
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# 获取数据库最新的人脸及其特征
|
||||
# ------------------------------
|
||||
def get_all_face_name_with_eigenvalue() -> dict:
|
||||
conn = None
|
||||
cursor = None
|
||||
try:
|
||||
conn = db.get_connection()
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
|
||||
query = "SELECT name, eigenvalue FROM face WHERE name IS NOT NULL"
|
||||
cursor.execute(query)
|
||||
faces = cursor.fetchall()
|
||||
|
||||
name_to_eigenvalues = {}
|
||||
for face in faces:
|
||||
name = face["name"]
|
||||
eigenvalue = face["eigenvalue"]
|
||||
if name in name_to_eigenvalues:
|
||||
name_to_eigenvalues[name].append(eigenvalue)
|
||||
else:
|
||||
name_to_eigenvalues[name] = [eigenvalue]
|
||||
|
||||
face_dict = {}
|
||||
for name, eigenvalues in name_to_eigenvalues.items():
|
||||
if len(eigenvalues) > 1:
|
||||
face_dict[name] = get_average_feature(eigenvalues)
|
||||
else:
|
||||
face_dict[name] = eigenvalues[0]
|
||||
|
||||
return face_dict
|
||||
|
||||
except MySQLError as e:
|
||||
raise Exception(f"获取人脸特征失败: {str(e)}") from e
|
||||
finally:
|
||||
db.close_connection(conn, cursor)
|
||||
|
||||
# 以下函数保持不变
|
||||
def get_average_feature(features=None):
|
||||
global _feature_list
|
||||
try:
|
||||
if features is None:
|
||||
features = _feature_list
|
||||
if not isinstance(features, list) or len(features) == 0:
|
||||
logger.warning("输入必须是包含至少一个特征值的列表")
|
||||
return None
|
||||
|
||||
processed_features = []
|
||||
for i, embedding in enumerate(features):
|
||||
try:
|
||||
if isinstance(embedding, str):
|
||||
embedding_str = embedding.replace('[', '').replace(']', '').replace(',', ' ').strip()
|
||||
embedding_list = [float(num) for num in embedding_str.split() if num.strip()]
|
||||
embedding_np = np.array(embedding_list, dtype=np.float32)
|
||||
else:
|
||||
embedding_np = np.array(embedding, dtype=np.float32)
|
||||
|
||||
if len(embedding_np.shape) == 1:
|
||||
processed_features.append(embedding_np)
|
||||
logger.info(f"已添加第 {i + 1} 个特征值用于计算平均值")
|
||||
else:
|
||||
logger.warning(f"跳过第 {i + 1} 个特征值:不是一维数组")
|
||||
except Exception as e:
|
||||
logger.error(f"处理第 {i + 1} 个特征值时出错:{str(e)}")
|
||||
|
||||
if not processed_features:
|
||||
logger.warning("没有有效的特征值用于计算平均值")
|
||||
return None
|
||||
|
||||
dims = {feat.shape[0] for feat in processed_features}
|
||||
if len(dims) > 1:
|
||||
logger.error(f"特征值维度不一致:{dims}、无法计算平均值")
|
||||
return None
|
||||
|
||||
avg_feature = np.mean(processed_features, axis=0)
|
||||
logger.info(f"计算成功:{len(processed_features)} 个特征值的平均向量(维度:{avg_feature.shape[0]})")
|
||||
return avg_feature
|
||||
except Exception as e:
|
||||
logger.error(f"计算平均特征值出错:{str(e)}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
def clear_features():
|
||||
global _feature_list
|
||||
_feature_list = []
|
||||
logger.info("已清空所有特征数据")
|
||||
|
||||
|
||||
def get_feature_list():
|
||||
global _feature_list
|
||||
logger.info(f"当前特征列表长度:{len(_feature_list)}")
|
||||
return _feature_list.copy()
|
||||
61
util/model_util.py
Normal file
61
util/model_util.py
Normal file
@ -0,0 +1,61 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import traceback
|
||||
from ultralytics import YOLO
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def load_yolo_model(model_path: str) -> Optional[YOLO]:
|
||||
"""
|
||||
加载YOLO模型(支持v5/v8)、并校验模型有效性
|
||||
:param model_path: 模型文件的绝对路径
|
||||
:return: 加载成功返回YOLO模型实例、失败返回None
|
||||
"""
|
||||
try:
|
||||
# 加载前的基础信息检查
|
||||
print(f"\n[模型工具] 开始加载模型:{model_path}")
|
||||
print(f"[模型工具] 文件是否存在:{os.path.exists(model_path)}")
|
||||
if os.path.exists(model_path):
|
||||
print(f"[模型工具] 文件大小:{os.path.getsize(model_path) / 1024 / 1024:.2f} MB")
|
||||
|
||||
# 强制重新加载模型、避免缓存问题
|
||||
model = YOLO(model_path)
|
||||
|
||||
# 兼容性校验:使用numpy空数组测试模型
|
||||
dummy_image = np.zeros((640, 640, 3), dtype=np.uint8)
|
||||
|
||||
try:
|
||||
# 优先使用新版本参数
|
||||
model.predict(
|
||||
source=dummy_image,
|
||||
imgsz=640,
|
||||
conf=0.25,
|
||||
verbose=False,
|
||||
stream=False
|
||||
)
|
||||
except Exception as pred_e:
|
||||
print(f"[模型工具] 预测校验兼容处理:{str(pred_e)}")
|
||||
# 兼容旧版本YOLO参数
|
||||
model.predict(
|
||||
img=dummy_image,
|
||||
imgsz=640,
|
||||
conf=0.25,
|
||||
verbose=False
|
||||
)
|
||||
|
||||
# 验证模型基本属性
|
||||
if not hasattr(model, 'names'):
|
||||
print("[模型工具] 警告:模型缺少类别名称属性")
|
||||
else:
|
||||
print(f"[模型工具] 模型包含类别:{list(model.names.values())[:5]}...") # 显示前5个类别
|
||||
|
||||
print(f"[模型工具] 模型加载成功!")
|
||||
return model
|
||||
|
||||
except Exception as e:
|
||||
# 详细错误信息输出
|
||||
print(f"\n[模型工具] 加载模型失败!路径:{model_path}")
|
||||
print(f"[模型工具] 异常类型:{type(e).__name__}")
|
||||
print(f"[模型工具] 异常详情:{str(e)}")
|
||||
print(f"[模型工具] 堆栈跟踪:\n{traceback.format_exc()}")
|
||||
return None
|
||||
Reference in New Issue
Block a user