Files
video/ocr/face_recognizer.py

187 lines
8.3 KiB
Python
Raw Normal View History

2025-09-03 14:38:42 +08:00
import os
import cv2
import numpy as np
import insightface
from insightface.app import FaceAnalysis
class FaceRecognizer:
"""
封装InsightFace人脸识别功能支持从文件夹加载已知人脸
"""
def __init__(self, known_faces_dir: str):
self.known_faces_dir = known_faces_dir
self.app = self._initialize_insightface()
self.known_faces_embeddings = {}
self.known_faces_names = []
self._load_known_faces()
def _initialize_insightface(self):
"""
初始化InsightFace FaceAnalysis应用
默认使用CPU如果检测到CUDA会自动使用GPU
"""
print("正在初始化InsightFace人脸识别引擎...")
try:
# 默认模型是 'buffalo_l',包含检测、对齐、识别功能
# 如果需要更小的模型,可以尝试 'buffalo_s' 或 'buffalo_m'
# ctx_id=0 表示使用GPUctx_id=-1 表示使用CPU
# InsightFace会自动检测CUDA并选择GPU所以通常不需要手动设置ctx_id
app = FaceAnalysis(name='buffalo_l', root='~/.insightface') # 模型下载到用户目录
app.prepare(ctx_id=0, det_size=(640, 640)) # det_size影响检测性能和精度
print("InsightFace人脸识别引擎初始化成功。")
return app
except Exception as e:
print(f"InsightFace人脸识别引擎初始化失败: {e}")
print("请确保已安装insightface和onnxruntime并且模型文件已下载或可访问。")
return None
def _load_known_faces(self):
"""
扫描已知人脸目录加载每个人的照片并计算人脸特征
"""
if not os.path.exists(self.known_faces_dir):
print(f"警告: 已知人脸目录 '{self.known_faces_dir}' 不存在。请创建并放入照片。")
os.makedirs(self.known_faces_dir, exist_ok=True)
return
print(f"正在加载已知人脸特征从: '{self.known_faces_dir}'...")
for person_name in os.listdir(self.known_faces_dir):
person_dir = os.path.join(self.known_faces_dir, person_name)
if os.path.isdir(person_dir):
print(f" 加载人物: {person_name}")
embeddings = []
for filename in os.listdir(person_dir):
if filename.lower().endswith(('.png', '.jpg', '.jpeg')):
image_path = os.path.join(person_dir, filename)
try:
img = cv2.imread(image_path)
if img is None:
print(f" 警告: 无法读取图片 '{image_path}',已跳过。")
continue
# 查找人脸并提取特征
faces = self.app.get(img)
if faces:
# 通常一张照片只有一个人脸,取第一个
embeddings.append(faces[0].embedding)
print(f" 成功提取 '{filename}' 的人脸特征。")
else:
print(f" 警告: 在图片 '{filename}' 中未检测到人脸,已跳过。")
except Exception as e:
print(f" 处理图片 '{image_path}' 时发生错误: {e}")
if embeddings:
# 将多张照片的特征取平均,作为该人物的最终特征
self.known_faces_embeddings[person_name] = np.array(embeddings).mean(axis=0)
self.known_faces_names.append(person_name)
print(f" 人物 '{person_name}' 加载完成,共 {len(embeddings)} 张照片。")
else:
print(f" 警告: 人物 '{person_name}' 没有有效的人脸特征,已跳过。")
print(f"已知人脸加载完成。共 {len(self.known_faces_names)} 个人物。")
def recognize(self, frame, threshold=0.4):
"""
在视频帧中识别人脸
Args:
frame: 输入的图像帧 (NumPy数组, BGR格式)
threshold (float): 识别相似度阈值0.0到1.0越高越严格
Returns:
tuple[bool, str|None, float|None]: 是否检测到已知人脸检测到的人名以及相似度
"""
if not self.app or not self.known_faces_names:
return False, None, None
faces = self.app.get(frame) # 在帧中检测并提取所有人的脸
if not faces:
return False, None, None
for face in faces:
# 遍历已知人脸,进行比对
for known_name in self.known_faces_names:
known_embedding = self.known_faces_embeddings[known_name]
# --- 关键修改:手动计算余弦相似度 ---
# 确保embedding是float32类型避免潜在的类型不匹配问题
embedding1 = face.embedding.astype(np.float32)
embedding2 = known_embedding.astype(np.float32)
# 计算点积
dot_product = np.dot(embedding1, embedding2)
# 计算L2范数向量长度
norm_embedding1 = np.linalg.norm(embedding1)
norm_embedding2 = np.linalg.norm(embedding2)
# 避免除以零
if norm_embedding1 == 0 or norm_embedding2 == 0:
similarity = 0.0
else:
similarity = dot_product / (norm_embedding1 * norm_embedding2)
# -------------------------------------
if similarity >= threshold:
print(f"!!! 人脸识别检测到已知人物: '{known_name}' (相似度: {similarity:.4f}) !!!")
return True, known_name, similarity # 只要检测到一个就立即返回
return False, None, None # 没有检测到已知人脸
# def test_single_image(self, image_path: str, threshold=0.4):
# """
# 测试单张图片的人脸识别效果
#
# Args:
# image_path: 图片路径
# threshold: 识别阈值
#
# Returns:
# tuple[bool, str|None, float|None]: 是否检测到已知人脸,检测到的人名,以及相似度
# """
# if not os.path.exists(image_path):
# print(f"错误: 图片 '{image_path}' 不存在")
# return False, None, None
#
# # 读取图片
# frame = cv2.imread(image_path)
# if frame is None:
# print(f"错误: 无法读取图片 '{image_path}'")
# return False, None, None
#
# # 调用识别方法
# result, name, similarity = self.recognize(frame, threshold)
#
# # 显示结果
# if result:
# print(f"测试结果: 在图片中识别到 {name},相似度: {similarity:.4f}")
#
# # 绘制识别结果并显示图片
# faces = self.app.get(frame)
# for face in faces:
# bbox = face.bbox.astype(int)
# # 绘制 bounding box
# cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 255, 0), 2)
# # 绘制姓名和相似度
# text = f"{name}: {similarity:.2f}"
# cv2.putText(frame, text, (bbox[0], bbox[1] - 10),
# cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
#
# # 显示图片
# cv2.imshow('Recognition Result', frame)
# print("按任意键关闭图片窗口...")
# cv2.waitKey(0)
# cv2.destroyAllWindows()
# else:
# print("测试结果: 未在图片中识别到已知人脸")
#
# return result, name, similarity
# if __name__ == "__main__":
# # 初始化人脸识别器,指定已知人脸目录
# recognizer = FaceRecognizer(known_faces_dir="known_faces")
#
# # 测试单张图片
# test_image_path = r"F:\OCR\RapidOCR-main\known_faces\B\14sino-qiu02-master1050.jpg" # 替换为你的测试图片路径
# recognizer.test_single_image(test_image_path, threshold=0.4)