内容安全审核

This commit is contained in:
2025-09-30 17:17:20 +08:00
commit cc6e66bbf8
523 changed files with 4853 additions and 0 deletions

Binary file not shown.

196
util/face_util.py Normal file
View 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
View 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