2025-09-04 22:59:27 +08:00
|
|
|
|
import os
|
|
|
|
|
import numpy as np
|
|
|
|
|
import cv2
|
2025-09-05 17:23:50 +08:00
|
|
|
|
import gc
|
|
|
|
|
import time
|
|
|
|
|
import threading
|
|
|
|
|
from PIL import Image
|
2025-09-04 22:59:27 +08:00
|
|
|
|
from insightface.app import FaceAnalysis
|
|
|
|
|
# 导入获取人脸信息的服务
|
|
|
|
|
from service.face_service import get_all_face_name_with_eigenvalue
|
|
|
|
|
|
2025-09-05 17:23:50 +08:00
|
|
|
|
# 用于检查GPU状态
|
|
|
|
|
try:
|
|
|
|
|
import pynvml
|
|
|
|
|
|
|
|
|
|
pynvml.nvmlInit()
|
|
|
|
|
_nvml_available = True
|
|
|
|
|
except ImportError:
|
2025-09-08 17:34:23 +08:00
|
|
|
|
print("警告: pynvml库未安装、无法检测GPU状态、将默认使用0号GPU")
|
2025-09-05 17:23:50 +08:00
|
|
|
|
_nvml_available = False
|
|
|
|
|
|
2025-09-04 22:59:27 +08:00
|
|
|
|
# 全局变量
|
|
|
|
|
_face_app = None
|
|
|
|
|
_known_faces_embeddings = {} # 存储姓名到特征值的映射
|
|
|
|
|
_known_faces_names = [] # 存储所有已知姓名
|
2025-09-05 17:23:50 +08:00
|
|
|
|
_using_gpu = False # 标记是否使用GPU
|
|
|
|
|
_used_gpu_id = -1 # 记录当前使用的GPU ID
|
|
|
|
|
|
|
|
|
|
# 资源管理变量
|
|
|
|
|
_ref_count = 0
|
|
|
|
|
_last_used_time = 0
|
|
|
|
|
_lock = threading.Lock()
|
|
|
|
|
_release_timeout = 8 # 5秒无使用则释放
|
|
|
|
|
_is_releasing = False # 标记是否正在释放
|
|
|
|
|
|
|
|
|
|
# 调试用计数器
|
|
|
|
|
_debug_counter = {
|
|
|
|
|
"created": 0,
|
|
|
|
|
"released": 0,
|
|
|
|
|
"detected": 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_gpu_availability(gpu_id, threshold=0.7):
|
|
|
|
|
"""检查指定GPU是否可用(内存使用率低于阈值)"""
|
|
|
|
|
if not _nvml_available:
|
|
|
|
|
return True # 无法检测时默认认为可用
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
handle = pynvml.nvmlDeviceGetHandleByIndex(gpu_id)
|
|
|
|
|
mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
|
|
|
|
|
usage = mem_info.used / mem_info.total
|
|
|
|
|
# 内存使用率低于阈值则认为可用
|
|
|
|
|
return usage < threshold
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"检查GPU {gpu_id} 状态时出错: {e}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def select_best_gpu(preferred_gpus=[0, 1]):
|
2025-09-08 17:34:23 +08:00
|
|
|
|
"""选择最佳可用GPU、严格按照首选列表顺序检查、优先使用0号GPU"""
|
2025-09-05 17:23:50 +08:00
|
|
|
|
# 首先检查首选GPU列表
|
|
|
|
|
for gpu_id in preferred_gpus:
|
|
|
|
|
try:
|
|
|
|
|
# 检查GPU是否存在
|
|
|
|
|
if _nvml_available:
|
|
|
|
|
pynvml.nvmlDeviceGetHandleByIndex(gpu_id)
|
|
|
|
|
|
|
|
|
|
# 检查GPU是否可用
|
|
|
|
|
if check_gpu_availability(gpu_id):
|
2025-09-08 17:34:23 +08:00
|
|
|
|
print(f"GPU {gpu_id} 可用、将使用该GPU")
|
2025-09-05 17:23:50 +08:00
|
|
|
|
return gpu_id
|
|
|
|
|
else:
|
|
|
|
|
if gpu_id == 0:
|
2025-09-08 17:34:23 +08:00
|
|
|
|
print(f"GPU 0 内存使用率过高(繁忙)、尝试切换到其他GPU")
|
2025-09-05 17:23:50 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"GPU {gpu_id} 不存在或无法访问: {e}")
|
|
|
|
|
continue
|
|
|
|
|
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 如果所有首选GPU都不可用、返回-1表示使用CPU
|
|
|
|
|
print("所有指定的GPU都不可用、将使用CPU进行计算")
|
2025-09-05 17:23:50 +08:00
|
|
|
|
return -1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _release_engine():
|
|
|
|
|
"""释放人脸识别引擎资源"""
|
|
|
|
|
global _face_app, _is_releasing, _known_faces_embeddings, _known_faces_names
|
|
|
|
|
if not _face_app or _is_releasing:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
_is_releasing = True
|
|
|
|
|
# 释放InsightFace资源
|
|
|
|
|
if hasattr(_face_app, 'model'):
|
|
|
|
|
# 清除模型资源
|
|
|
|
|
_face_app.model = None
|
|
|
|
|
_face_app = None
|
2025-09-04 22:59:27 +08:00
|
|
|
|
|
2025-09-05 17:23:50 +08:00
|
|
|
|
# 清空人脸数据
|
|
|
|
|
_known_faces_embeddings.clear()
|
|
|
|
|
_known_faces_names.clear()
|
2025-09-04 22:59:27 +08:00
|
|
|
|
|
2025-09-05 17:23:50 +08:00
|
|
|
|
_debug_counter["released"] += 1
|
|
|
|
|
print(f"Face recognition engine released. Stats: {_debug_counter}")
|
|
|
|
|
|
|
|
|
|
# 清理GPU缓存
|
|
|
|
|
gc.collect()
|
|
|
|
|
try:
|
|
|
|
|
import torch
|
|
|
|
|
if torch.cuda.is_available():
|
|
|
|
|
torch.cuda.empty_cache()
|
|
|
|
|
torch.cuda.ipc_collect()
|
|
|
|
|
except ImportError:
|
|
|
|
|
pass
|
|
|
|
|
try:
|
|
|
|
|
import tensorflow as tf
|
|
|
|
|
tf.keras.backend.clear_session()
|
|
|
|
|
except ImportError:
|
|
|
|
|
pass
|
|
|
|
|
finally:
|
|
|
|
|
_is_releasing = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _monitor_thread():
|
2025-09-08 17:34:23 +08:00
|
|
|
|
"""监控线程、检查并释放超时未使用的资源"""
|
2025-09-05 17:23:50 +08:00
|
|
|
|
global _ref_count, _last_used_time, _face_app
|
|
|
|
|
while True:
|
|
|
|
|
time.sleep(5) # 每5秒检查一次
|
|
|
|
|
with _lock:
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 只有当引擎存在、没有引用且超时、才释放
|
2025-09-05 17:23:50 +08:00
|
|
|
|
if _face_app and _ref_count == 0 and not _is_releasing:
|
|
|
|
|
elapsed = time.time() - _last_used_time
|
|
|
|
|
if elapsed > _release_timeout:
|
|
|
|
|
print(f"Idle timeout ({elapsed:.1f}s > {_release_timeout}s), releasing face engine")
|
|
|
|
|
_release_engine()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_model(prefer_gpu=True, preferred_gpus=[0, 1]):
|
2025-09-08 17:34:23 +08:00
|
|
|
|
"""加载人脸识别模型及已知人脸特征库、默认优先使用0号GPU"""
|
2025-09-05 17:23:50 +08:00
|
|
|
|
global _face_app, _known_faces_embeddings, _known_faces_names, _using_gpu, _used_gpu_id
|
|
|
|
|
|
|
|
|
|
# 确保监控线程只启动一次
|
|
|
|
|
if not any(t.name == "FaceMonitor" for t in threading.enumerate()):
|
|
|
|
|
threading.Thread(target=_monitor_thread, daemon=True, name="FaceMonitor").start()
|
|
|
|
|
print("Face monitor thread started")
|
|
|
|
|
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 如果正在释放中、等待释放完成
|
2025-09-05 17:23:50 +08:00
|
|
|
|
while _is_releasing:
|
|
|
|
|
time.sleep(0.1)
|
|
|
|
|
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 如果已经初始化、直接返回
|
2025-09-05 17:23:50 +08:00
|
|
|
|
if _face_app:
|
|
|
|
|
return True
|
2025-09-04 22:59:27 +08:00
|
|
|
|
|
|
|
|
|
# 初始化InsightFace模型
|
|
|
|
|
try:
|
2025-09-05 17:23:50 +08:00
|
|
|
|
# 初始化InsightFace
|
|
|
|
|
print("正在初始化InsightFace人脸识别引擎...")
|
|
|
|
|
_face_app = FaceAnalysis(name='buffalo_l', root='~/.insightface')
|
|
|
|
|
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 选择合适的GPU、默认优先使用0号
|
2025-09-05 17:23:50 +08:00
|
|
|
|
ctx_id = 0
|
|
|
|
|
if prefer_gpu:
|
|
|
|
|
ctx_id = select_best_gpu(preferred_gpus)
|
|
|
|
|
_using_gpu = ctx_id != -1
|
|
|
|
|
_used_gpu_id = ctx_id if _using_gpu else -1
|
|
|
|
|
|
|
|
|
|
if _using_gpu:
|
2025-09-08 17:34:23 +08:00
|
|
|
|
print(f"成功初始化、使用GPU {ctx_id} 进行计算")
|
2025-09-05 17:23:50 +08:00
|
|
|
|
else:
|
2025-09-08 17:34:23 +08:00
|
|
|
|
print("成功初始化、使用CPU进行计算")
|
2025-09-05 17:23:50 +08:00
|
|
|
|
|
|
|
|
|
# 准备模型
|
|
|
|
|
_face_app.prepare(ctx_id=ctx_id, det_size=(640, 640))
|
|
|
|
|
print("InsightFace人脸识别引擎初始化成功。")
|
|
|
|
|
_debug_counter["created"] += 1
|
|
|
|
|
print(f"Face engine initialized. Stats: {_debug_counter}")
|
|
|
|
|
|
2025-09-04 22:59:27 +08:00
|
|
|
|
except Exception as e:
|
2025-09-05 17:23:50 +08:00
|
|
|
|
print(f"初始化失败: {e}")
|
2025-09-04 22:59:27 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# 从服务获取所有人脸姓名和特征值
|
|
|
|
|
try:
|
|
|
|
|
face_data = get_all_face_name_with_eigenvalue()
|
|
|
|
|
|
|
|
|
|
# 处理获取到的人脸数据
|
|
|
|
|
for person_name, eigenvalue_data in face_data.items():
|
|
|
|
|
# 处理特征值数据 - 兼容数组和字符串两种格式
|
|
|
|
|
if isinstance(eigenvalue_data, np.ndarray):
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 如果已经是numpy数组、直接使用
|
2025-09-04 22:59:27 +08:00
|
|
|
|
eigenvalue = eigenvalue_data.astype(np.float32)
|
|
|
|
|
elif isinstance(eigenvalue_data, str):
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 清理字符串: 移除方括号、换行符和多余空格
|
2025-09-04 22:59:27 +08:00
|
|
|
|
cleaned = eigenvalue_data.replace('[', '').replace(']', '').replace('\n', '').strip()
|
|
|
|
|
# 按空格或逗号分割(处理可能的不同分隔符)
|
|
|
|
|
values = [v for v in cleaned.split() if v]
|
|
|
|
|
# 转换为数组
|
|
|
|
|
eigenvalue = np.array(list(map(float, values)), dtype=np.float32)
|
|
|
|
|
else:
|
|
|
|
|
# 不支持的类型
|
|
|
|
|
print(f"Unsupported eigenvalue type for {person_name}")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 归一化处理
|
|
|
|
|
norm = np.linalg.norm(eigenvalue)
|
|
|
|
|
if norm != 0:
|
|
|
|
|
eigenvalue = eigenvalue / norm
|
|
|
|
|
|
|
|
|
|
_known_faces_embeddings[person_name] = eigenvalue
|
|
|
|
|
_known_faces_names.append(person_name)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error loading face data from service: {e}")
|
|
|
|
|
|
|
|
|
|
return True if _face_app else False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def detect(frame, threshold=0.4):
|
2025-09-08 17:34:23 +08:00
|
|
|
|
"""检测并识别人脸、返回结果元组(是否匹配到已知人脸, 结果字符串)"""
|
2025-09-05 17:23:50 +08:00
|
|
|
|
global _face_app, _known_faces_embeddings, _known_faces_names, _using_gpu, _used_gpu_id
|
|
|
|
|
global _ref_count, _last_used_time
|
|
|
|
|
|
|
|
|
|
# 验证前置条件
|
|
|
|
|
if frame is None or frame.size == 0:
|
|
|
|
|
return (False, "无效帧数据")
|
|
|
|
|
|
|
|
|
|
# 增加引用计数并获取引擎实例
|
|
|
|
|
engine = None
|
|
|
|
|
with _lock:
|
|
|
|
|
_ref_count += 1
|
|
|
|
|
_last_used_time = time.time()
|
|
|
|
|
_debug_counter["detected"] += 1
|
2025-09-04 22:59:27 +08:00
|
|
|
|
|
2025-09-05 17:23:50 +08:00
|
|
|
|
# 初始化引擎(如果未初始化且不在释放中)
|
|
|
|
|
if not _face_app and not _is_releasing:
|
|
|
|
|
if not load_model(prefer_gpu=True):
|
|
|
|
|
_ref_count -= 1 # 恢复引用计数
|
|
|
|
|
return (False, "引擎初始化失败")
|
|
|
|
|
|
|
|
|
|
# 获取当前引擎引用
|
|
|
|
|
engine = _face_app
|
|
|
|
|
|
|
|
|
|
# 检查引擎是否可用
|
|
|
|
|
if not engine or not _known_faces_names:
|
|
|
|
|
with _lock:
|
|
|
|
|
_ref_count = max(0, _ref_count - 1)
|
|
|
|
|
return (False, "人脸识别引擎不可用或未初始化")
|
2025-09-04 22:59:27 +08:00
|
|
|
|
|
|
|
|
|
try:
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 如果使用GPU、确保输入帧在处理前是连续的数组
|
2025-09-05 17:23:50 +08:00
|
|
|
|
if _using_gpu and not frame.flags.contiguous:
|
|
|
|
|
frame = np.ascontiguousarray(frame)
|
|
|
|
|
|
2025-09-04 22:59:27 +08:00
|
|
|
|
faces = _face_app.get(frame)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Face detect error: {e}")
|
2025-09-05 17:23:50 +08:00
|
|
|
|
# 检测到错误时尝试重新选择GPU并重新初始化
|
|
|
|
|
print("尝试重新选择GPU并重新初始化...")
|
|
|
|
|
with _lock:
|
|
|
|
|
_ref_count = max(0, _ref_count - 1)
|
|
|
|
|
load_model(prefer_gpu=True) # 重新初始化时保持默认GPU优先级
|
2025-09-04 22:59:27 +08:00
|
|
|
|
return (False, f"检测错误: {str(e)}")
|
|
|
|
|
|
|
|
|
|
result_parts = []
|
2025-09-05 17:23:50 +08:00
|
|
|
|
has_matched = False # 标记是否有匹配到的已知人脸
|
2025-09-04 22:59:27 +08:00
|
|
|
|
|
|
|
|
|
for face in faces:
|
|
|
|
|
# 特征归一化
|
|
|
|
|
embedding = face.embedding.astype(np.float32)
|
|
|
|
|
norm = np.linalg.norm(embedding)
|
|
|
|
|
if norm == 0:
|
|
|
|
|
continue
|
|
|
|
|
embedding = embedding / norm
|
|
|
|
|
|
|
|
|
|
# 对比已知人脸
|
|
|
|
|
max_sim, best_name = -1.0, "Unknown"
|
|
|
|
|
for name in _known_faces_names:
|
|
|
|
|
known_emb = _known_faces_embeddings[name]
|
|
|
|
|
sim = np.dot(embedding, known_emb)
|
|
|
|
|
if sim > max_sim:
|
|
|
|
|
max_sim = sim
|
|
|
|
|
best_name = name
|
|
|
|
|
|
|
|
|
|
# 判断匹配结果
|
|
|
|
|
is_match = max_sim >= threshold
|
|
|
|
|
if is_match:
|
2025-09-08 17:34:23 +08:00
|
|
|
|
has_matched = True # 只要有一个匹配成功、就标记为True
|
2025-09-04 22:59:27 +08:00
|
|
|
|
|
|
|
|
|
bbox = face.bbox
|
|
|
|
|
result_parts.append(
|
|
|
|
|
f"{'匹配' if is_match else '不匹配'}: {best_name} (相似度: {max_sim:.2f}, 边界框: {bbox})"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 构建结果字符串
|
|
|
|
|
if not result_parts:
|
|
|
|
|
result_str = "未检测到人脸"
|
|
|
|
|
else:
|
|
|
|
|
result_str = "; ".join(result_parts)
|
|
|
|
|
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 减少引用计数、确保线程安全
|
2025-09-05 17:23:50 +08:00
|
|
|
|
with _lock:
|
|
|
|
|
_ref_count = max(0, _ref_count - 1)
|
|
|
|
|
# 持续使用时更新最后使用时间
|
|
|
|
|
if _ref_count > 0:
|
|
|
|
|
_last_used_time = time.time()
|
|
|
|
|
|
2025-09-08 17:34:23 +08:00
|
|
|
|
# 第一个返回值为: 是否匹配到已知人脸
|
2025-09-05 17:23:50 +08:00
|
|
|
|
return (has_matched, result_str)
|