备份一些py文件

This commit is contained in:
zqc
2025-12-20 18:07:49 +08:00
parent de5ac3ef22
commit 713ad3f3e4
17 changed files with 21 additions and 20 deletions

242
backup/cuda_t.py Normal file
View File

@@ -0,0 +1,242 @@
import torch
import onnxruntime as ort
import insightface
import subprocess
import sys
import os
def detailed_diagnosis():
"""详细诊断脚本"""
print("=" * 60)
print("详细CUDA诊断")
print("=" * 60)
# 1. 系统信息
print("\n📋 1. 系统信息:")
print(f"Python版本: {sys.version}")
print(f"Python路径: {sys.executable}")
print(f"Conda环境: {sys.prefix}")
# 2. PyTorch详细信息
print("\n🔥 2. PyTorch详细信息:")
print(f"PyTorch版本: {torch.__version__}")
print(f"PyTorch路径: {torch.__file__}")
print(f"CUDA可用: {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f"CUDA版本: {torch.version.cuda}")
print(f"GPU数量: {torch.cuda.device_count()}")
for i in range(torch.cuda.device_count()):
print(f" GPU {i}: {torch.cuda.get_device_name(i)}")
print(f" 内存: {torch.cuda.get_device_properties(i).total_memory / 1024 ** 3:.1f} GB")
else:
print("❌ PyTorch无法使用CUDA")
# 检查可能的原因
print("\n🔍 PyTorch CUDA问题排查:")
print(f" torch.cuda.is_available(): {torch.cuda.is_available()}")
try:
print(f" CUDA设备数量: {torch.cuda.device_count()}")
except:
print(" CUDA设备数量: 无法获取")
# 3. ONNX Runtime详细信息
print("\n⚡ 3. ONNX Runtime详细信息:")
print(f"ONNX Runtime版本: {ort.__version__}")
print(f"ONNX Runtime路径: {ort.__file__}")
available_providers = ort.get_available_providers()
print(f"可用Providers: {available_providers}")
if 'CUDAExecutionProvider' in available_providers:
print("✅ CUDAExecutionProvider可用")
# 测试CUDA provider
try:
options = ort.SessionOptions()
session = ort.InferenceSession(
os.path.join(os.path.dirname(insightface.__file__), 'models', 'buffalo_l', '1k3d68.onx'),
providers=['CUDAExecutionProvider'],
sess_options=options
)
print("✅ CUDAExecutionProvider测试通过")
except Exception as e:
print(f"❌ CUDAExecutionProvider测试失败: {e}")
else:
print("❌ CUDAExecutionProvider不可用")
# 4. InsightFace信息
print("\n👁️ 4. InsightFace信息:")
print(f"InsightFace版本: {insightface.__version__}")
print(f"InsightFace路径: {insightface.__file__}")
# 5. 系统CUDA检查
print("\n🖥️ 5. 系统CUDA检查:")
try:
# 检查nvidia-smi
result = subprocess.run(['nvidia-smi'], capture_output=True, text=True, timeout=10)
if result.returncode == 0:
print("✅ nvidia-smi可用")
# 提取关键信息
lines = result.stdout.split('\n')
for line in lines:
if 'Driver Version' in line:
print(f" 驱动版本: {line.strip()}")
if 'CUDA Version' in line:
print(f" CUDA版本: {line.strip()}")
else:
print("❌ nvidia-smi不可用")
except (subprocess.TimeoutExpired, FileNotFoundError, Exception) as e:
print(f"❌ nvidia-smi执行失败: {e}")
try:
# 检查nvcc
result = subprocess.run(['nvcc', '--version'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
print("✅ nvcc可用")
version_line = result.stdout.split('\n')[3] if len(result.stdout.split('\n')) > 3 else result.stdout
print(f" {version_line.strip()}")
else:
print("❌ nvcc不可用")
except (subprocess.TimeoutExpired, FileNotFoundError, Exception) as e:
print(f"❌ nvcc执行失败: {e}")
# 6. 环境变量检查
print("\n🌍 6. 环境变量检查:")
cuda_paths = []
for key, value in os.environ.items():
if 'CUDA' in key.upper() or 'CUDNN' in key.upper():
print(f" {key}: {value}")
if 'PATH' in key or 'HOME' in key:
cuda_paths.append((key, value))
# 7. 包版本兼容性检查
print("\n📦 7. 包版本兼容性检查:")
try:
import pkg_resources
packages = ['torch', 'torchvision', 'torchaudio', 'onnxruntime', 'insightface', 'opencv-python', 'numpy']
for pkg in packages:
try:
version = pkg_resources.get_distribution(pkg).version
print(f" {pkg}: {version}")
except:
print(f" {pkg}: 未安装")
except:
print(" 无法检查包版本")
# 8. 实际性能测试
print("\n🚀 8. 实际性能测试:")
try:
# 测试InsightFace实际使用
app_cpu = insightface.app.FaceAnalysis(name='buffalo_l')
app_cpu.prepare(ctx_id=-1) # 强制CPU
app_gpu = insightface.app.FaceAnalysis(name='buffalo_l')
app_gpu.prepare(ctx_id=0) # 强制GPU
# 创建测试图像
import numpy as np
import cv2
test_img = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
# CPU测试
import time
print(" CPU测试...")
start_time = time.time()
for _ in range(5):
faces_cpu = app_cpu.get(test_img)
cpu_time = (time.time() - start_time) * 1000 / 5
# GPU测试
print(" GPU测试...")
start_time = time.time()
for _ in range(5):
faces_gpu = app_gpu.get(test_img)
gpu_time = (time.time() - start_time) * 1000 / 5
print(f" CPU平均时间: {cpu_time:.1f}ms")
print(f" GPU平均时间: {gpu_time:.1f}ms")
if gpu_time < cpu_time * 0.8: # GPU应该比CPU快
print(" ✅ GPU加速生效")
else:
print(" ⚠️ GPU加速未生效或效果不明显")
except Exception as e:
print(f" ❌ 性能测试失败: {e}")
def check_package_installation():
"""检查包安装情况"""
print("\n" + "=" * 60)
print("包安装检查")
print("=" * 60)
packages = {
'torch': 'PyTorch (深度学习框架)',
'torchvision': 'PyTorch视觉库',
'torchaudio': 'PyTorch音频库',
'onnxruntime': 'ONNX Runtime (推理引擎)',
'insightface': '人脸识别库',
'opencv-python': 'OpenCV (图像处理)',
'numpy': '数值计算库'
}
for pkg, desc in packages.items():
try:
if pkg == 'torch':
import torch
version = torch.__version__
cuda_status = "✅ CUDA可用" if torch.cuda.is_available() else "❌ CUDA不可用"
print(f"{pkg} ({desc}): {version} {cuda_status}")
elif pkg == 'onnxruntime':
import onnxruntime as ort
version = ort.__version__
providers = ort.get_available_providers()
cuda_status = "✅ 有GPU支持" if 'CUDAExecutionProvider' in providers else "❌ 无GPU支持"
print(f"{pkg} ({desc}): {version} {cuda_status}")
else:
module = __import__(pkg)
version = getattr(module, '__version__', '未知版本')
print(f"{pkg} ({desc}): {version}")
except ImportError:
print(f"{pkg} ({desc}): ❌ 未安装")
if __name__ == "__main__":
detailed_diagnosis()
check_package_installation()
# 提供解决方案
print("\n" + "=" * 60)
print("解决方案建议")
print("=" * 60)
# 基于诊断结果给出建议
if not torch.cuda.is_available():
print("\n❌ 主要问题: PyTorch没有CUDA支持")
print("💡 解决方案:")
print("1. 完全卸载当前PyTorch:")
print(" pip uninstall torch torchvision torchaudio")
print("2. 安装GPU版本的PyTorch:")
print(" pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118")
if 'CUDAExecutionProvider' not in ort.get_available_providers():
print("\n❌ 主要问题: ONNX Runtime没有GPU支持")
print("💡 解决方案:")
print("1. 卸载CPU版本:")
print(" pip uninstall onnxruntime")
print("2. 安装GPU版本:")
print(" pip install onnxruntime-gpu")
# 通用建议
print("\n🔄 通用建议:")
print("1. 创建全新的conda环境:")
print(" conda create -n face_gpu python=3.10")
print(" conda activate face_gpu")
print("2. 按顺序安装:")
print(" pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118")
print(" pip install onnxruntime-gpu")
print(" pip install insightface opencv-python")
print("3. 验证安装:")
print(
" python -c \"import torch; print(f'PyTorch CUDA: {torch.cuda.is_available()}'); import onnxruntime as ort; print(f'ONNX Providers: {ort.get_available_providers()}')\"")

200
backup/cuda_t2.py Normal file
View File

@@ -0,0 +1,200 @@
# final_gpu_test.py
import torch
import onnxruntime as ort
import insightface
import cv2
import numpy as np
import time
import os
from typing import List, Dict
def comprehensive_gpu_test():
"""全面的GPU测试"""
print("=" * 60)
print("最终GPU加速测试")
print("=" * 60)
# 1. 环境验证
print("1. 环境验证:")
print(f"✅ PyTorch: {torch.__version__}")
print(f"✅ PyTorch CUDA: {torch.cuda.is_available()}")
print(f"✅ GPU设备: {torch.cuda.get_device_name(0)}")
print(f"✅ ONNX Runtime GPU支持: {'CUDAExecutionProvider' in ort.get_available_providers()}")
print(f"✅ OpenCV: {cv2.__version__}")
print(f"✅ InsightFace: {insightface.__version__}")
# 2. 创建测试数据
print("\n2. 创建测试数据...")
os.makedirs("test_data", exist_ok=True)
# 创建目标人脸
target_face = np.random.randint(0, 255, (400, 400, 3), dtype=np.uint8)
cv2.rectangle(target_face, (150, 150), (250, 250), (255, 255, 255), -1) # 简单的人脸模拟
cv2.imwrite("test_data/target_face.jpg", target_face)
# 创建测试图像(多个人脸)
test_image = np.random.randint(0, 255, (800, 600, 3), dtype=np.uint8)
# 添加多个人脸区域
cv2.rectangle(test_image, (100, 100), (200, 200), (255, 255, 255), -1)
cv2.rectangle(test_image, (300, 150), (400, 250), (255, 255, 255), -1)
cv2.rectangle(test_image, (500, 200), (600, 300), (255, 255, 255), -1)
cv2.imwrite("test_data/multi_face_test.jpg", test_image)
print("✅ 测试数据创建完成")
# 3. GPU加速的人脸识别系统
print("\n3. 初始化GPU人脸识别系统...")
class FastGPUFaceRecognition:
def __init__(self):
self.app = insightface.app.FaceAnalysis(name='buffalo_l')
self.app.prepare(
ctx_id=0, # GPU 0
det_thresh=0.3, # 检测阈值
det_size=(640, 640)
)
self.target_embedding = None
print("✅ GPU人脸识别系统初始化完成")
def set_target_face(self, image_path: str):
"""设置目标人脸"""
img = cv2.imread(image_path)
faces = self.app.get(img)
if faces:
self.target_embedding = faces[0].embedding
print(f"✅ 目标人脸特征提取完成")
return True
return False
def process_image(self, image_path: str, output_path: str):
"""处理图像并返回结果"""
start_time = time.time()
img = cv2.imread(image_path)
if img is None:
return None
# GPU推理
faces = self.app.get(img)
results = []
for i, face in enumerate(faces):
similarity = 0.0
if self.target_embedding is not None:
# 计算相似度
emb1 = face.embedding / np.linalg.norm(face.embedding)
emb2 = self.target_embedding / np.linalg.norm(self.target_embedding)
similarity = float(np.dot(emb1, emb2))
results.append({
'face_index': i,
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'det_score': face.det_score
})
# 绘制结果
bbox = face.bbox.astype(int)
color = (0, 255, 0) if similarity > 0.6 else (0, 0, 255)
cv2.rectangle(img, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color, 2)
cv2.putText(img, f"{similarity:.3f}", (bbox[0], bbox[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# 保存结果
cv2.imwrite(output_path, img)
processing_time = (time.time() - start_time) * 1000
return {
'faces_detected': len(faces),
'processing_time_ms': processing_time,
'results': results
}
# 初始化系统
system = FastGPUFaceRecognition()
# 4. 性能测试
print("\n4. 性能测试:")
# 设置目标人脸
if system.set_target_face("test_data/target_face.jpg"):
# 处理测试图像
result = system.process_image("test_data/multi_face_test.jpg", "test_data/gpu_result.jpg")
if result:
print(f"✅ 处理完成:")
print(f" 检测到人脸数: {result['faces_detected']}")
print(f" 处理时间: {result['processing_time_ms']:.1f}ms")
for face in result['results']:
status = "匹配" if face['similarity'] > 0.6 else "不匹配"
print(f" 人脸 {face['face_index'] + 1}: 相似度 {face['similarity']:.3f} ({status})")
# 5. 批量性能测试
print("\n5. 批量性能测试:")
# 创建多个测试图像
test_images = []
for i in range(5):
test_img = np.random.randint(0, 255, (600, 800, 3), dtype=np.uint8)
cv2.rectangle(test_img, (100, 100), (200, 200), (255, 255, 255), -1)
img_path = f"test_data/batch_test_{i}.jpg"
cv2.imwrite(img_path, test_img)
test_images.append(img_path)
total_time = 0
total_faces = 0
for i, img_path in enumerate(test_images):
result = system.process_image(img_path, f"test_data/batch_result_{i}.jpg")
if result:
total_time += result['processing_time_ms']
total_faces += result['faces_detected']
print(f" 图像 {i + 1}: {result['processing_time_ms']:.1f}ms, {result['faces_detected']}张人脸")
if len(test_images) > 0:
avg_time = total_time / len(test_images)
print(f" 平均处理时间: {avg_time:.1f}ms/张")
print(f" 总检测人脸: {total_faces}")
# 6. 与CPU性能对比可选
print("\n6. GPU vs CPU 性能对比:")
try:
# GPU测试
gpu_times = []
test_img = cv2.imread("test_data/multi_face_test.jpg")
for _ in range(10):
start = time.time()
faces = system.app.get(test_img)
gpu_times.append((time.time() - start) * 1000)
# CPU测试创建新的CPU实例
cpu_app = insightface.app.FaceAnalysis(name='buffalo_l')
cpu_app.prepare(ctx_id=-1) # CPU
cpu_times = []
for _ in range(10):
start = time.time()
faces = cpu_app.get(test_img)
cpu_times.append((time.time() - start) * 1000)
avg_gpu = np.mean(gpu_times)
avg_cpu = np.mean(cpu_times)
print(f" GPU平均推理时间: {avg_gpu:.1f}ms")
print(f" CPU平均推理时间: {avg_cpu:.1f}ms")
print(f" GPU加速比: {avg_cpu / avg_gpu:.1f}x")
except Exception as e:
print(f" 性能对比测试跳过: {e}")
print("\n" + "=" * 60)
print("🎉 GPU加速测试完成")
print("✅ 现在您的人脸识别系统正在使用GPU加速")
print("📁 结果图像保存在 test_data/ 目录中")
print("=" * 60)
if __name__ == "__main__":
comprehensive_gpu_test()

200
backup/cuda_t3.py Normal file
View File

@@ -0,0 +1,200 @@
# final_gpu_test.py
import torch
import onnxruntime as ort
import insightface
import cv2
import numpy as np
import time
import os
from typing import List, Dict
def comprehensive_gpu_test():
"""全面的GPU测试"""
print("=" * 60)
print("最终GPU加速测试")
print("=" * 60)
# 1. 环境验证
print("1. 环境验证:")
print(f"✅ PyTorch: {torch.__version__}")
print(f"✅ PyTorch CUDA: {torch.cuda.is_available()}")
print(f"✅ GPU设备: {torch.cuda.get_device_name(0)}")
print(f"✅ ONNX Runtime GPU支持: {'CUDAExecutionProvider' in ort.get_available_providers()}")
print(f"✅ OpenCV: {cv2.__version__}")
print(f"✅ InsightFace: {insightface.__version__}")
# 2. 创建测试数据
print("\n2. 创建测试数据...")
os.makedirs("test_data", exist_ok=True)
# 创建目标人脸
target_face = np.random.randint(0, 255, (400, 400, 3), dtype=np.uint8)
cv2.rectangle(target_face, (150, 150), (250, 250), (255, 255, 255), -1) # 简单的人脸模拟
cv2.imwrite("test_data/target_face.jpg", target_face)
# 创建测试图像(多个人脸)
test_image = np.random.randint(0, 255, (800, 600, 3), dtype=np.uint8)
# 添加多个人脸区域
cv2.rectangle(test_image, (100, 100), (200, 200), (255, 255, 255), -1)
cv2.rectangle(test_image, (300, 150), (400, 250), (255, 255, 255), -1)
cv2.rectangle(test_image, (500, 200), (600, 300), (255, 255, 255), -1)
# cv2.imwrite("test_data/multi_face_test.jpg", test_image)
print("✅ 测试数据创建完成")
# 3. GPU加速的人脸识别系统
print("\n3. 初始化GPU人脸识别系统...")
class FastGPUFaceRecognition:
def __init__(self):
self.app = insightface.app.FaceAnalysis(name='buffalo_l')
self.app.prepare(
ctx_id=0, # GPU 0
det_thresh=0.3, # 检测阈值
det_size=(640, 640)
)
self.target_embedding = None
print("✅ GPU人脸识别系统初始化完成")
def set_target_face(self, image_path: str):
"""设置目标人脸"""
img = cv2.imread(image_path)
faces = self.app.get(img)
if faces:
self.target_embedding = faces[0].embedding
print(f"✅ 目标人脸特征提取完成")
return True
return False
def process_image(self, image_path: str, output_path: str):
"""处理图像并返回结果"""
start_time = time.time()
img = cv2.imread(image_path)
if img is None:
return None
# GPU推理
faces = self.app.get(img)
results = []
for i, face in enumerate(faces):
similarity = 0.0
if self.target_embedding is not None:
# 计算相似度
emb1 = face.embedding / np.linalg.norm(face.embedding)
emb2 = self.target_embedding / np.linalg.norm(self.target_embedding)
similarity = float(np.dot(emb1, emb2))
results.append({
'face_index': i,
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'det_score': face.det_score
})
# 绘制结果
bbox = face.bbox.astype(int)
color = (0, 255, 0) if similarity > 0.6 else (0, 0, 255)
cv2.rectangle(img, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color, 2)
cv2.putText(img, f"{similarity:.3f}", (bbox[0], bbox[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# 保存结果
cv2.imwrite(output_path, img)
processing_time = (time.time() - start_time) * 1000
return {
'faces_detected': len(faces),
'processing_time_ms': processing_time,
'results': results
}
# 初始化系统
system = FastGPUFaceRecognition()
# 4. 性能测试
print("\n4. 性能测试:")
# 设置目标人脸
if system.set_target_face("test_data/target_face.jpg"):
# 处理测试图像
result = system.process_image("test_data/multi_face_test.jpg", "test_data/gpu_result.jpg")
if result:
print(f"✅ 处理完成:")
print(f" 检测到人脸数: {result['faces_detected']}")
print(f" 处理时间: {result['processing_time_ms']:.1f}ms")
for face in result['results']:
status = "匹配" if face['similarity'] > 0.6 else "不匹配"
print(f" 人脸 {face['face_index'] + 1}: 相似度 {face['similarity']:.3f} ({status})")
# 5. 批量性能测试
print("\n5. 批量性能测试:")
# 创建多个测试图像
test_images = []
for i in range(5):
test_img = np.random.randint(0, 255, (600, 800, 3), dtype=np.uint8)
cv2.rectangle(test_img, (100, 100), (200, 200), (255, 255, 255), -1)
img_path = f"test_data/batch_test_{i}.jpg"
cv2.imwrite(img_path, test_img)
test_images.append(img_path)
total_time = 0
total_faces = 0
for i, img_path in enumerate(test_images):
result = system.process_image(img_path, f"test_data/batch_result_{i}.jpg")
if result:
total_time += result['processing_time_ms']
total_faces += result['faces_detected']
print(f" 图像 {i + 1}: {result['processing_time_ms']:.1f}ms, {result['faces_detected']}张人脸")
if len(test_images) > 0:
avg_time = total_time / len(test_images)
print(f" 平均处理时间: {avg_time:.1f}ms/张")
print(f" 总检测人脸: {total_faces}")
# 6. 与CPU性能对比可选
print("\n6. GPU vs CPU 性能对比:")
try:
# GPU测试
gpu_times = []
test_img = cv2.imread("test_data/multi_face_test.jpg")
for _ in range(10):
start = time.time()
faces = system.app.get(test_img)
gpu_times.append((time.time() - start) * 1000)
# CPU测试创建新的CPU实例
cpu_app = insightface.app.FaceAnalysis(name='buffalo_l')
cpu_app.prepare(ctx_id=-1) # CPU
cpu_times = []
for _ in range(10):
start = time.time()
faces = cpu_app.get(test_img)
cpu_times.append((time.time() - start) * 1000)
avg_gpu = np.mean(gpu_times)
avg_cpu = np.mean(cpu_times)
print(f" GPU平均推理时间: {avg_gpu:.1f}ms")
print(f" CPU平均推理时间: {avg_cpu:.1f}ms")
print(f" GPU加速比: {avg_cpu / avg_gpu:.1f}x")
except Exception as e:
print(f" 性能对比测试跳过: {e}")
print("\n" + "=" * 60)
print("🎉 GPU加速测试完成")
print("✅ 现在您的人脸识别系统正在使用GPU加速")
print("📁 结果图像保存在 test_data/ 目录中")
print("=" * 60)
if __name__ == "__main__":
comprehensive_gpu_test()

View File

@@ -0,0 +1,40 @@
# hello_world_advanced.py
"""
Hello World 程序 - Python 3.12
演示基本语法和新特性
"""
import torch
import insightface
import cv2
def main():
# 基础输出
print("Hello, World!")
print("InsightFace版本:", insightface.__version__)
# 使用f-string (Python 3.6+)
version = "3.12"
print(f"Running on Python {version}!")
# 演示Python 3.12的改进错误信息
try:
# 这里故意创建一个错误来看改进的错误信息
x = "hello"
y = x + 1 # 这会触发TypeError
except TypeError as e:
print(f"Error demo: {e}")
# 使用match语句 (Python 3.10+)
language = "Python"
match language:
case "Python":
print("You're using Python! 🐍")
case "Java":
print("You're using Java!")
case _:
print("You're using another language")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,249 @@
import os
import cv2
import numpy as np
from insightface.app import FaceAnalysis
from insightface.data import get_image as ins_get_image
import pickle
from typing import List, Tuple, Dict
import json
class FaceRecognitionSystem:
def __init__(self, model_name: str = 'buffalo_l'):
"""
初始化人脸识别系统
Args:
model_name: 模型名称,可选 'buffalo_l', 'buffalo_s'
"""
self.app = FaceAnalysis(name=model_name, providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
self.app.prepare(ctx_id=0, det_size=(640, 640))
self.face_database = {} # 存储人脸特征向量
self.database_file = "face_database.pkl"
# 加载已有数据库
self.load_database()
def extract_face_features(self, image_path: str) -> List[Dict]:
"""
从图像中提取人脸特征
Args:
image_path: 图像路径
Returns:
List[Dict]: 包含人脸信息的列表
"""
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"无法读取图像: {image_path}")
faces = self.app.get(img)
results = []
for i, face in enumerate(faces):
face_info = {
'bbox': face.bbox.astype(int).tolist(), # 人脸框
'kps': face.kps.astype(int).tolist(), # 关键点
'embedding': face.embedding.tolist(), # 特征向量
'gender': face.gender, # 性别
'age': face.age # 年龄
}
results.append(face_info)
return results
def register_face(self, image_path: str, person_id: str):
"""
注册人脸到数据库
Args:
image_path: 图像路径
person_id: 人员ID
"""
faces = self.extract_face_features(image_path)
if not faces:
print(f"在图像 {image_path} 中未检测到人脸")
return False
if len(faces) > 1:
print(f"在图像 {image_path} 中检测到多个人脸,将使用第一个人脸")
# 存储第一个人脸的特征
self.face_database[person_id] = {
'embedding': np.array(faces[0]['embedding']),
'image_path': image_path
}
self.save_database()
print(f"成功注册人脸: {person_id}")
return True
def compare_faces(self, embedding1: np.ndarray, embedding2: np.ndarray) -> float:
"""
计算两个人脸特征的相似度
Args:
embedding1: 特征向量1
embedding2: 特征向量2
Returns:
float: 相似度得分 (0-1之间越大越相似)
"""
# 计算余弦相似度
similarity = np.dot(embedding1, embedding2) / (
np.linalg.norm(embedding1) * np.linalg.norm(embedding2)
)
return float(similarity)
def one_vs_one(self, image_path1: str, image_path2: str) -> Tuple[float, bool]:
"""
1v1人脸比对
Args:
image_path1: 图像1路径
image_path2: 图像2路径
Returns:
Tuple[float, bool]: (相似度得分, 是否同一人)
"""
faces1 = self.extract_face_features(image_path1)
faces2 = self.extract_face_features(image_path2)
if not faces1 or not faces2:
return 0.0, False
embedding1 = np.array(faces1[0]['embedding'])
embedding2 = np.array(faces2[0]['embedding'])
similarity = self.compare_faces(embedding1, embedding2)
is_same = similarity > 0.6 # 阈值可根据实际情况调整
return similarity, is_same
def one_vs_many(self, image_path: str, threshold: float = 0.6) -> List[Tuple[str, float]]:
"""
1vn人脸检索
Args:
image_path: 查询图像路径
threshold: 相似度阈值
Returns:
List[Tuple[str, float]]: 匹配结果 (人员ID, 相似度)
"""
faces = self.extract_face_features(image_path)
if not faces:
return []
query_embedding = np.array(faces[0]['embedding'])
results = []
for person_id, data in self.face_database.items():
similarity = self.compare_faces(query_embedding, data['embedding'])
if similarity > threshold:
results.append((person_id, similarity))
# 按相似度降序排序
results.sort(key=lambda x: x[1], reverse=True)
return results
def save_database(self):
"""保存人脸数据库到文件"""
# 将numpy数组转换为列表以便序列化
save_data = {}
for person_id, data in self.face_database.items():
save_data[person_id] = {
'embedding': data['embedding'].tolist(),
'image_path': data['image_path']
}
with open(self.database_file, 'wb') as f:
pickle.dump(save_data, f)
def load_database(self):
"""从文件加载人脸数据库"""
if os.path.exists(self.database_file):
with open(self.database_file, 'rb') as f:
save_data = pickle.load(f)
# 将列表转换回numpy数组
for person_id, data in save_data.items():
self.face_database[person_id] = {
'embedding': np.array(data['embedding']),
'image_path': data['image_path']
}
print(f"已加载数据库,包含 {len(self.face_database)} 个人脸")
def visualize_detection(self, image_path: str, save_path: str = None):
"""
可视化人脸检测结果
Args:
image_path: 图像路径
save_path: 保存路径
"""
img = cv2.imread(image_path)
faces = self.app.get(img)
for face in faces:
# 绘制人脸框
bbox = face.bbox.astype(int)
cv2.rectangle(img, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 255, 0), 2)
# 绘制关键点
for kp in face.kps.astype(int):
cv2.circle(img, (kp[0], kp[1]), 2, (0, 0, 255), -1)
# 显示性别和年龄
info = f"{'M' if face.gender == 1 else 'F'}/{face.age}"
cv2.putText(img, info, (bbox[0], bbox[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
if save_path:
cv2.imwrite(save_path, img)
return img
# 使用示例
def main():
# 初始化系统
face_system = FaceRecognitionSystem()
# # 创建测试图像目录
# os.makedirs("test_images", exist_ok=True)
#
# # 示例1: 注册人脸
# print("=== 注册人脸 ===")
# # 假设你有一些人脸图像放在 test_images 目录下
# test_images = {
# "person_001": "test_images/person1.png",
# "person_002": "test_images/person2.jpg",
# # "person_003": "test_images/person3.jpg"
# }
#
# for person_id, img_path in test_images.items():
# if os.path.exists(img_path):
# face_system.register_face(img_path, person_id)
# 示例2: 1v1比对
print("\n=== 1v1人脸比对 ===")
img1 = "test_data/register/person1.png"
# img1 = "test_data/query/file___media_Photo_103_IMG_1737872809_085_IMG_20250126_142509.jpg"
img2 = "test_data/query/file___media_Photo_152_IMG_1737876072_134_IMG_20250126_151932.jpg"
if os.path.exists(img1) and os.path.exists(img2):
similarity, is_same = face_system.one_vs_one(img1, img2)
print(f"相似度: {similarity:.4f}, 是否同一人: {is_same}")
# # 示例3: 1vn检索
# print("\n=== 1vn人脸检索 ===")
# query_img = "test_images/query.jpg"
# if os.path.exists(query_img):
# results = face_system.one_vs_many(query_img)
# print("检索结果:")
# for person_id, score in results:
# print(f" {person_id}: {score:.4f}")
#
# # 示例4: 可视化检测结果
# print("\n=== 人脸检测可视化 ===")
# test_img = "test_images/test.jpg"
# if os.path.exists(test_img):
# output_img = face_system.visualize_detection(test_img, "output/detection_result.jpg")
# print("检测结果已保存到 output/detection_result.jpg")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,496 @@
import os
import cv2
import numpy as np
from insightface.app import FaceAnalysis
import pickle
from typing import List, Tuple, Dict, Optional
from datetime import datetime
import json
class MultiFaceRecognitionSystem:
"""
多人脸识别系统
支持多个人脸检测、识别和标注
"""
def __init__(self, model_name: str = 'buffalo_l'):
"""
初始化人脸识别系统
Args:
model_name: 模型名称,可选 'buffalo_l', 'buffalo_s'
"""
# 初始化InsightFace
self.app = FaceAnalysis(name=model_name, providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
self.app.prepare(ctx_id=0, det_size=(640, 640))
# 人脸数据库
self.face_database = {}
self.database_file = "face_database.pkl"
# 加载已有数据库
self.load_database()
# 相似度阈值
self.similarity_threshold = 0.6
# 颜色配置
self.colors = {
'known': (0, 255, 0), # 绿色 - 已知人脸
'unknown': (0, 0, 255), # 红色 - 未知人脸
'text': (255, 255, 255), # 白色 - 文字
'landmark': (255, 0, 0) # 蓝色 - 关键点
}
def register_face(self, image_path: str, person_id: str) -> bool:
"""
注册单个人脸到数据库
Args:
image_path: 图像路径
person_id: 人员ID
Returns:
bool: 注册是否成功
"""
if not os.path.exists(image_path):
print(f"图像文件不存在: {image_path}")
return False
# 提取人脸特征
faces = self.extract_face_features(image_path)
if not faces:
print(f"在图像 {image_path} 中未检测到人脸")
return False
if len(faces) > 1:
print(f"在图像 {image_path} 中检测到多个人脸,将使用第一个人脸")
# 存储到数据库
self.face_database[person_id] = {
'embedding': np.array(faces[0]['embedding']),
'image_path': image_path,
'registration_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
# 保存数据库
self.save_database()
print(f"成功注册人脸: {person_id}")
return True
def batch_register_faces(self, image_dir: str, id_prefix: str = "person") -> int:
"""
批量注册人脸
Args:
image_dir: 图像目录
id_prefix: ID前缀
Returns:
int: 成功注册的数量
"""
if not os.path.exists(image_dir):
print(f"目录不存在: {image_dir}")
return 0
registered_count = 0
image_files = [f for f in os.listdir(image_dir)
if f.lower().endswith(('.jpg', '.jpeg', '.png'))]
for i, filename in enumerate(image_files):
image_path = os.path.join(image_dir, filename)
person_id = f"{id_prefix}_{i + 1:03d}"
if self.register_face(image_path, person_id):
registered_count += 1
print(f"批量注册完成: {registered_count}/{len(image_files)} 个人脸")
return registered_count
def extract_face_features(self, image_path: str) -> List[Dict]:
"""
从图像中提取所有人脸特征
Args:
image_path: 图像路径
Returns:
List[Dict]: 包含多个人脸信息的列表
"""
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"无法读取图像: {image_path}")
faces = self.app.get(img)
results = []
for i, face in enumerate(faces):
face_info = {
'bbox': face.bbox.astype(int).tolist(), # 人脸框 [x1, y1, x2, y2]
'kps': face.kps.astype(int).tolist(), # 关键点 [[x1,y1], [x2,y2], ...]
'embedding': face.embedding.tolist(), # 特征向量
'gender': 'Male' if face.gender == 1 else 'Female',
'age': int(face.age),
'det_score': float(face.det_score) # 检测置信度
}
results.append(face_info)
return results
def compare_faces(self, embedding1: np.ndarray, embedding2: np.ndarray) -> float:
"""
计算两个人脸特征的相似度
Args:
embedding1: 特征向量1
embedding2: 特征向量2
Returns:
float: 余弦相似度 (0-1)
"""
# 归一化向量
embedding1 = embedding1 / np.linalg.norm(embedding1)
embedding2 = embedding2 / np.linalg.norm(embedding2)
# 计算余弦相似度
similarity = np.dot(embedding1, embedding2)
return float(similarity)
def search_face(self, query_embedding: np.ndarray, top_k: int = 5) -> List[Tuple[str, float]]:
"""
在数据库中搜索最相似的人脸
Args:
query_embedding: 查询特征向量
top_k: 返回最相似的k个结果
Returns:
List[Tuple[str, float]]: (人员ID, 相似度)
"""
if not self.face_database:
return []
results = []
query_embedding = query_embedding / np.linalg.norm(query_embedding)
for person_id, data in self.face_database.items():
db_embedding = data['embedding'] / np.linalg.norm(data['embedding'])
similarity = np.dot(query_embedding, db_embedding)
if similarity > self.similarity_threshold:
results.append((person_id, float(similarity)))
# 按相似度降序排序
results.sort(key=lambda x: x[1], reverse=True)
# 返回top_k个结果
return results[:top_k]
def recognize_faces_in_image(self, image_path: str, output_path: str = None,
draw_landmarks: bool = True, draw_info: bool = True) -> Dict:
"""
识别图像中的所有脸并进行标注
Args:
image_path: 输入图像路径
output_path: 输出图像路径
draw_landmarks: 是否绘制关键点
draw_info: 是否绘制详细信息
Returns:
Dict: 识别结果
"""
# 读取图像
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"无法读取图像: {image_path}")
# 提取人脸特征
faces = self.extract_face_features(image_path)
recognition_results = {
'image_path': image_path,
'faces_detected': len(faces),
'recognitions': []
}
# 对每个检测到的人脸进行识别
for i, face in enumerate(faces):
# 在数据库中搜索
query_embedding = np.array(face['embedding'])
search_results = self.search_face(query_embedding, top_k=1)
# 识别结果
person_id = "Unknown"
similarity = 0.0
if search_results:
person_id, similarity = search_results[0]
# 存储识别结果
face_recognition = {
'face_index': i,
'bbox': face['bbox'],
'person_id': person_id,
'similarity': similarity,
'gender': face['gender'],
'age': face['age'],
'det_score': face['det_score']
}
recognition_results['recognitions'].append(face_recognition)
# 在图像上绘制标注
self._draw_face_annotation(img, face_recognition, draw_landmarks, draw_info)
# 保存输出图像
if output_path:
os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else '.', exist_ok=True)
cv2.imwrite(output_path, img)
print(f"标注图像已保存: {output_path}")
return recognition_results
def _draw_face_annotation(self, img: np.ndarray, face_info: Dict,
draw_landmarks: bool, draw_info: bool):
"""
在图像上绘制人脸标注
Args:
img: 图像数组
face_info: 人脸信息
draw_landmarks: 是否绘制关键点
draw_info: 是否绘制详细信息
"""
bbox = face_info['bbox'] # [x1, y1, x2, y2]
person_id = face_info['person_id']
similarity = face_info['similarity']
# 确定颜色:已知人脸用绿色,未知用红色
if person_id != "Unknown":
color = self.colors['known']
label = f"{person_id} ({similarity:.3f})"
else:
color = self.colors['unknown']
label = "Unknown"
# 绘制人脸框
cv2.rectangle(img, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color, 2)
# 绘制关键点
if draw_landmarks and 'kps' in face_info:
for kp in face_info['kps']:
cv2.circle(img, (kp[0], kp[1]), 2, self.colors['landmark'], -1)
# 绘制信息文本
if draw_info:
# 基础信息
text_y = bbox[1] - 10
if text_y < 20:
text_y = bbox[3] + 20
cv2.putText(img, label, (bbox[0], text_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, self.colors['text'], 2)
# 详细信息
detail_text = f"{face_info['gender']}/{face_info['age']}"
cv2.putText(img, detail_text, (bbox[0], text_y + 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, self.colors['text'], 1)
def batch_process_images(self, input_dir: str, output_dir: str,
image_extensions: tuple = ('.jpg', '.jpeg', '.png')) -> Dict:
"""
批量处理目录中的所有图像
Args:
input_dir: 输入图像目录
output_dir: 输出图像目录
image_extensions: 图像文件扩展名
Returns:
Dict: 批量处理结果统计
"""
if not os.path.exists(input_dir):
raise ValueError(f"输入目录不存在: {input_dir}")
os.makedirs(output_dir, exist_ok=True)
# 获取所有图像文件
image_files = []
for ext in image_extensions:
image_files.extend([f for f in os.listdir(input_dir) if f.lower().endswith(ext)])
print(f"找到 {len(image_files)} 个图像文件")
# 处理统计
stats = {
'total_images': len(image_files),
'processed_images': 0,
'total_faces': 0,
'recognized_faces': 0,
'results': []
}
# 批量处理
for image_file in image_files:
input_path = os.path.join(input_dir, image_file)
output_path = os.path.join(output_dir, f"annotated_{image_file}")
try:
# 处理单张图像
result = self.recognize_faces_in_image(input_path, output_path)
# 更新统计
stats['processed_images'] += 1
stats['total_faces'] += result['faces_detected']
recognized = sum(1 for face in result['recognitions'] if face['person_id'] != "Unknown")
stats['recognized_faces'] += recognized
stats['results'].append({
'image_file': image_file,
'faces_detected': result['faces_detected'],
'recognized_faces': recognized
})
print(f"处理完成: {image_file} -> 检测到 {result['faces_detected']} 张脸, 识别出 {recognized}")
except Exception as e:
print(f"处理图像 {image_file} 时出错: {e}")
# 保存处理统计
stats_path = os.path.join(output_dir, "processing_stats.json")
with open(stats_path, 'w', encoding='utf-8') as f:
json.dump(stats, f, indent=2, ensure_ascii=False)
print(f"批量处理完成! 统计信息已保存: {stats_path}")
return stats
def save_database(self):
"""保存人脸数据库到文件"""
# 转换为可序列化的格式
save_data = {}
for person_id, data in self.face_database.items():
save_data[person_id] = {
'embedding': data['embedding'].tolist(),
'image_path': data['image_path'],
'registration_time': data.get('registration_time', 'Unknown')
}
with open(self.database_file, 'wb') as f:
pickle.dump(save_data, f)
print(f"人脸数据库已保存: {self.database_file} (共 {len(self.face_database)} 人)")
def load_database(self):
"""从文件加载人脸数据库"""
if os.path.exists(self.database_file):
try:
with open(self.database_file, 'rb') as f:
save_data = pickle.load(f)
# 转换回numpy数组
for person_id, data in save_data.items():
self.face_database[person_id] = {
'embedding': np.array(data['embedding']),
'image_path': data['image_path'],
'registration_time': data.get('registration_time', 'Unknown')
}
print(f"人脸数据库已加载: {len(self.face_database)}")
except Exception as e:
print(f"加载数据库失败: {e}")
self.face_database = {}
else:
print("数据库文件不存在,将创建新数据库")
self.face_database = {}
def get_database_info(self) -> Dict:
"""获取数据库信息"""
return {
'total_persons': len(self.face_database),
'person_ids': list(self.face_database.keys()),
'database_file': self.database_file
}
def set_similarity_threshold(self, threshold: float):
"""设置相似度阈值"""
if 0 <= threshold <= 1:
self.similarity_threshold = threshold
print(f"相似度阈值已设置为: {threshold}")
else:
print("阈值必须在 0 到 1 之间")
# 使用示例和演示
def demo_usage():
"""演示使用方法"""
# 创建人脸识别系统
face_system = MultiFaceRecognitionSystem()
# 1. 注册人脸到数据库
print("=== 注册人脸 ===")
# 创建测试数据目录
os.makedirs("test_data/register", exist_ok=True)
os.makedirs("test_data/query", exist_ok=True)
os.makedirs("test_data/output", exist_ok=True)
# 假设你有一些人脸图像用于注册
# face_system.register_face("test_data/register/person1.jpg", "alice")
# face_system.register_face("test_data/register/person2.jpg", "bob")
# face_system.register_face("test_data/register/person3.jpg", "charlie")
# # 或者批量注册
# face_system.batch_register_faces("test_data/register", "person")
# 2. 查看数据库信息
print("\n=== 数据库信息 ===")
db_info = face_system.get_database_info()
print(f"数据库人数: {db_info['total_persons']}")
print(f"人员ID: {db_info['person_ids']}")
# 3. 单张图像识别示例
print("\n=== 单张图像识别 ===")
query_image = "test_data/query/multi_face.jpg" # 包含多个人的图像
if os.path.exists(query_image):
result = face_system.recognize_faces_in_image(
image_path=query_image,
output_path="test_data/output/annotated_multi_face.jpg",
draw_landmarks=True,
draw_info=True
)
print(f"检测到 {result['faces_detected']} 张人脸:")
for face in result['recognitions']:
status = "已知" if face['person_id'] != "Unknown" else "未知"
print(f" 人脸 {face['face_index'] + 1}: {face['person_id']} "
f"(相似度: {face['similarity']:.3f}, {face['gender']}/{face['age']}岁) - {status}")
else:
print(f"查询图像不存在: {query_image}")
print("请将包含多人脸的图像放在 test_data/query/ 目录下")
# 4. 批量处理示例
print("\n=== 批量处理图像 ===")
if os.path.exists("test_data/query") and len(os.listdir("test_data/query")) > 0:
stats = face_system.batch_process_images(
input_dir="test_data/query",
output_dir="test_data/output/batch_results"
)
print(f"批量处理统计:")
print(f" 总图像数: {stats['total_images']}")
print(f" 成功处理: {stats['processed_images']}")
print(f" 总人脸数: {stats['total_faces']}")
print(f" 识别出的人脸: {stats['recognized_faces']}")
# 5. 调整相似度阈值
print("\n=== 调整阈值 ===")
face_system.set_similarity_threshold(0.1) # 降低阈值,更宽松
# face_system.set_similarity_threshold(0.7) # 提高阈值,更严格
if __name__ == "__main__":
demo_usage()

View File

@@ -0,0 +1,482 @@
import os
import cv2
import numpy as np
from insightface.app import FaceAnalysis
import pickle
from typing import List, Tuple, Dict, Optional
from datetime import datetime
import json
class SingleFaceComparisonSystem:
"""
单人脸比对系统
在图像中检测多个人脸,并与单个目标人脸进行比对
"""
def __init__(self, model_name: str = 'buffalo_l'):
"""
初始化人脸比对系统
Args:
model_name: 模型名称,可选 'buffalo_l', 'buffalo_s'
"""
# 初始化InsightFace
self.app = FaceAnalysis(name=model_name, providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
self.app.prepare(ctx_id=0,
det_thresh=0.35, # 降低阈值,检测更多模糊/小脸
det_size=(640, 640))
# 目标人脸特征
self.target_embedding = None
self.target_person_id = None
self.target_image_path = None
# 相似度阈值
self.similarity_threshold = 0.3
# 颜色配置
self.colors = {
'match_high': (0, 255, 0), # 绿色 - 高相似度
'match_medium': (0, 255, 255), # 黄色 - 中等相似度
'match_low': (0, 165, 255), # 橙色 - 低相似度
'no_match': (0, 0, 255), # 红色 - 不匹配
'text': (255, 255, 255), # 白色 - 文字
'landmark': (255, 0, 0) # 蓝色 - 关键点
}
def set_target_face(self, image_path: str, person_id: str = "target_person") -> bool:
"""
设置目标人脸
Args:
image_path: 目标人脸图像路径
person_id: 目标人员ID
Returns:
bool: 设置是否成功
"""
if not os.path.exists(image_path):
print(f"目标图像文件不存在: {image_path}")
return False
# 提取人脸特征
faces = self.extract_face_features(image_path)
if not faces:
print(f"在目标图像 {image_path} 中未检测到人脸")
return False
if len(faces) > 1:
print(f"在目标图像 {image_path} 中检测到多个人脸,将使用第一个人脸")
# 存储目标人脸特征
self.target_embedding = np.array(faces[0]['embedding'])
self.target_person_id = person_id
self.target_image_path = image_path
print(f"成功设置目标人脸: {person_id}")
return True
def extract_face_features(self, image_path: str) -> List[Dict]:
"""
从图像中提取所有人脸特征
Args:
image_path: 图像路径
Returns:
List[Dict]: 包含多个人脸信息的列表
"""
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"无法读取图像: {image_path}")
faces = self.app.get(img)
results = []
for i, face in enumerate(faces):
face_info = {
'bbox': face.bbox.astype(int).tolist(), # 人脸框 [x1, y1, x2, y2]
'kps': face.kps.astype(int).tolist(), # 关键点 [[x1,y1], [x2,y2], ...]
'embedding': face.embedding.tolist(), # 特征向量
'gender': 'Male' if face.gender == 1 else 'Female',
'age': int(face.age),
'det_score': float(face.det_score) # 检测置信度
}
results.append(face_info)
return results
def compare_with_target(self, query_embedding: np.ndarray) -> float:
"""
与目标人脸进行比对
Args:
query_embedding: 查询特征向量
Returns:
float: 相似度 (0-1)
"""
if self.target_embedding is None:
raise ValueError("未设置目标人脸")
# 归一化向量
query_embedding = query_embedding / np.linalg.norm(query_embedding)
target_embedding = self.target_embedding / np.linalg.norm(self.target_embedding)
# 计算余弦相似度
similarity = np.dot(query_embedding, target_embedding)
return float(similarity)
def get_similarity_color(self, similarity: float) -> tuple:
"""
根据相似度获取对应的颜色
Args:
similarity: 相似度 (0-1)
Returns:
tuple: BGR颜色值
"""
if similarity >= self.similarity_threshold:
return self.colors['match_high'] # 高相似度 - 绿色
# elif similarity >= self.similarity_threshold/2:
# return self.colors['match_low'] # 低相似度 - 橙色
else:
return self.colors['no_match'] # 不匹配 - 红色
def process_image_with_target_comparison(self, image_path: str, output_path: str = None,
draw_landmarks: bool = True, draw_info: bool = True) -> Dict:
"""
处理图像并与目标人脸进行比对
Args:
image_path: 输入图像路径
output_path: 输出图像路径
draw_landmarks: 是否绘制关键点
draw_info: 是否绘制详细信息
Returns:
Dict: 处理结果
"""
if self.target_embedding is None:
raise ValueError("请先设置目标人脸")
# 读取图像
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"无法读取图像: {image_path}")
# 提取人脸特征
faces = self.extract_face_features(image_path)
comparison_results = {
'image_path': image_path,
'target_person_id': self.target_person_id,
'faces_detected': len(faces),
'comparisons': []
}
# 对每个检测到的人脸进行比对
for i, face in enumerate(faces):
# 与目标人脸比对
query_embedding = np.array(face['embedding'])
similarity = self.compare_with_target(query_embedding)
# 存储比对结果
face_comparison = {
'face_index': i,
'bbox': face['bbox'],
'similarity': similarity,
'is_match': similarity >= self.similarity_threshold,
'gender': face['gender'],
'age': face['age'],
'det_score': face['det_score']
}
comparison_results['comparisons'].append(face_comparison)
# 在图像上绘制标注
self._draw_face_comparison_annotation(img, face_comparison, draw_landmarks, draw_info)
# 保存输出图像
if output_path:
os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else '.', exist_ok=True)
cv2.imwrite(output_path, img)
print(f"比对结果图像已保存: {output_path}")
return comparison_results
def _draw_face_comparison_annotation(self, img: np.ndarray, face_info: Dict,
draw_landmarks: bool, draw_info: bool):
"""
在图像上绘制人脸比对标注
Args:
img: 图像数组
face_info: 人脸比对信息
draw_landmarks: 是否绘制关键点
draw_info: 是否绘制详细信息
"""
bbox = face_info['bbox'] # [x1, y1, x2, y2]
similarity = face_info['similarity']
is_match = face_info['is_match']
# 根据相似度确定颜色
color = self.get_similarity_color(similarity)
# 绘制人脸框
thickness = 3 if is_match else 2
cv2.rectangle(img, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color, thickness)
# 绘制关键点
if draw_landmarks and 'kps' in face_info:
for kp in face_info['kps']:
cv2.circle(img, (kp[0], kp[1]), 2, self.colors['landmark'], -1)
# 绘制信息文本
if draw_info:
# 相似度信息
text_y = bbox[1] - 10
if text_y < 20:
text_y = bbox[3] + 20
similarity_text = f"Similarity: {similarity:.3f}"
match_status = "MATCH" if is_match else "NO MATCH"
cv2.putText(img, similarity_text, (bbox[0], text_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, self.colors['text'], 2)
# 匹配状态
cv2.putText(img, match_status, (bbox[0], text_y + 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
# 人脸属性信息
detail_text = f"{face_info['gender']}/{face_info['age']}"
cv2.putText(img, detail_text, (bbox[0], text_y + 50),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, self.colors['text'], 1)
def batch_process_with_target(self, input_dir: str, output_dir: str,
image_extensions: tuple = ('.jpg', '.jpeg', '.png')) -> Dict:
"""
批量处理目录中的所有图像,与目标人脸进行比对
Args:
input_dir: 输入图像目录
output_dir: 输出图像目录
image_extensions: 图像文件扩展名
Returns:
Dict: 批量处理结果统计
"""
if self.target_embedding is None:
raise ValueError("请先设置目标人脸")
if not os.path.exists(input_dir):
raise ValueError(f"输入目录不存在: {input_dir}")
os.makedirs(output_dir, exist_ok=True)
# 获取所有图像文件
image_files = []
for ext in image_extensions:
image_files.extend([f for f in os.listdir(input_dir) if f.lower().endswith(ext)])
print(f"找到 {len(image_files)} 个图像文件")
# 处理统计
stats = {
'target_person_id': self.target_person_id,
'total_images': len(image_files),
'processed_images': 0,
'total_faces': 0,
'matched_faces': 0,
'max_similarity': 0.0,
'min_similarity': 1.0,
'avg_similarity': 0.0,
'results': []
}
all_similarities = []
# 批量处理
for image_file in image_files:
input_path = os.path.join(input_dir, image_file)
output_path = os.path.join(output_dir, f"compared_{image_file}")
try:
# 处理单张图像
result = self.process_image_with_target_comparison(input_path, output_path)
# 更新统计
stats['processed_images'] += 1
stats['total_faces'] += result['faces_detected']
image_similarities = [comp['similarity'] for comp in result['comparisons']]
all_similarities.extend(image_similarities)
if image_similarities:
stats['max_similarity'] = max(stats['max_similarity'], max(image_similarities))
stats['min_similarity'] = min(stats['min_similarity'], min(image_similarities))
matched_count = sum(1 for comp in result['comparisons'] if comp['is_match'])
stats['matched_faces'] += matched_count
stats['results'].append({
'image_file': image_file,
'faces_detected': result['faces_detected'],
'matched_faces': matched_count,
'max_similarity': max(image_similarities) if image_similarities else 0,
'min_similarity': min(image_similarities) if image_similarities else 0
})
print(f"处理完成: {image_file} -> 检测到 {result['faces_detected']} 张脸, "
f"匹配 {matched_count} 张, 最高相似度: {max(image_similarities) if image_similarities else 0:.3f}")
except Exception as e:
print(f"处理图像 {image_file} 时出错: {e}")
# 计算平均相似度
if all_similarities:
stats['avg_similarity'] = sum(all_similarities) / len(all_similarities)
# 保存处理统计
stats_path = os.path.join(output_dir, "comparison_stats.json")
with open(stats_path, 'w', encoding='utf-8') as f:
json.dump(stats, f, indent=2, ensure_ascii=False)
print(f"批量比对完成! 统计信息已保存: {stats_path}")
return stats
def set_similarity_threshold(self, threshold: float):
"""设置相似度阈值"""
if 0 <= threshold <= 1:
self.similarity_threshold = threshold
print(f"相似度阈值已设置为: {threshold}")
else:
print("阈值必须在 0 到 1 之间")
def get_target_info(self) -> Dict:
"""获取目标人脸信息"""
if self.target_embedding is None:
return {"status": "未设置目标人脸"}
return {
"status": "已设置",
"person_id": self.target_person_id,
"image_path": self.target_image_path,
"similarity_threshold": self.similarity_threshold
}
# 使用示例和演示
def demo_usage():
"""演示使用方法"""
# 创建人脸比对系统
face_system = SingleFaceComparisonSystem()
# 1. 设置目标人脸
print("=== 设置目标人脸 ===")
# 创建测试数据目录
os.makedirs("test_data/target", exist_ok=True)
os.makedirs("test_data/query", exist_ok=True)
os.makedirs("test_data/output", exist_ok=True)
# target_image = "test_data/register/person2.jpg"
# target_image = "test_data/register/person1.png"
target_image = "test_data/register/ztk.jpg"
# 设置目标人脸
if os.path.exists(target_image):
face_system.set_target_face(target_image, "target_person")
else:
print(f"目标图像不存在: {target_image}")
print("请将目标人脸图像放在 test_data/target/ 目录下")
return
# 2. 查看目标信息
print("\n=== 目标人脸信息 ===")
target_info = face_system.get_target_info()
print(f"目标人员ID: {target_info['person_id']}")
print(f"目标图像路径: {target_info['image_path']}")
print(f"相似度阈值: {target_info['similarity_threshold']}")
# 3. 单张图像比对示例
print("\n=== 单张图像比对 ===")
query_image = "test_data/query/multi_face.jpg" # 包含多个人的图像
if os.path.exists(query_image):
result = face_system.process_image_with_target_comparison(
image_path=query_image,
output_path="test_data/output/compared_multi_face.jpg",
draw_landmarks=True,
draw_info=True
)
print(f"检测到 {result['faces_detected']} 张人脸:")
for face in result['comparisons']:
status = "匹配" if face['is_match'] else "不匹配"
color_name = "绿色" if face['similarity'] >= 0.8 else \
"黄色" if face['similarity'] >= 0.6 else \
"橙色" if face['similarity'] >= 0.4 else "红色"
print(f" 人脸 {face['face_index'] + 1}: 相似度 {face['similarity']:.3f} "
f"({color_name}, {status}), {face['gender']}/{face['age']}")
else:
print(f"查询图像不存在: {query_image}")
print("请将包含多人脸的图像放在 test_data/query/ 目录下")
# 4. 批量处理示例
print("\n=== 批量比对图像 ===")
if os.path.exists("test_data/query") and len(os.listdir("test_data/query")) > 0:
stats = face_system.batch_process_with_target(
input_dir="test_data/query/2",
output_dir="test_data/output/batch_comparison/2"
)
print(f"批量比对统计:")
print(f" 目标人员: {stats['target_person_id']}")
print(f" 总图像数: {stats['total_images']}")
print(f" 成功处理: {stats['processed_images']}")
print(f" 总人脸数: {stats['total_faces']}")
print(f" 匹配人脸: {stats['matched_faces']}")
print(f" 最高相似度: {stats['max_similarity']:.3f}")
print(f" 最低相似度: {stats['min_similarity']:.3f}")
print(f" 平均相似度: {stats['avg_similarity']:.3f}")
# 5. 调整相似度阈值
print("\n=== 调整阈值 ===")
face_system.set_similarity_threshold(0.3) # 提高阈值,更严格
# 高级使用示例
def advanced_usage():
"""高级使用示例"""
face_system = SingleFaceComparisonSystem()
# 设置目标人脸
face_system.set_target_face("path/to/target_face.jpg", "suspect_A")
# 设置更严格的阈值
face_system.set_similarity_threshold(0.75)
# 处理单个图像并保存结果
result = face_system.process_image_with_target_comparison(
image_path="path/to/surveillance_image.jpg",
output_path="path/to/analyzed_image.jpg"
)
# 输出详细信息
print(f"分析完成: {result['image_path']}")
print(f"目标人员: {result['target_person_id']}")
print(f"检测到人脸: {result['faces_detected']}")
for comp in result['comparisons']:
print(f"人脸 {comp['face_index'] + 1}: 相似度={comp['similarity']:.3f}, "
f"匹配={comp['is_match']}, 位置={comp['bbox']}")
if __name__ == "__main__":
demo_usage()

259
backup/sorting_dce.py Normal file
View File

@@ -0,0 +1,259 @@
import os
import shutil
import re
import sys
def find_target_folders(root_path, output_path):
"""
递归遍历根目录,找到包含特定关键词子文件夹的父文件夹,并进行相应的复制和重命名操作
"""
# 确保输出目录存在
os.makedirs(os.path.join(output_path, "DCE0"), exist_ok=True)
os.makedirs(os.path.join(output_path, "DCE1"), exist_ok=True)
# 使用递归函数遍历
for item in os.listdir(root_path):
item_path = os.path.join(root_path, item)
if os.path.isdir(item_path):
process_directory(item_path, output_path)
def process_directory(current_path, output_path):
"""
递归处理目录
"""
# 获取当前文件夹的名称
current_folder_name = os.path.basename(current_path)
# 检查当前目录下是否包含目标文件夹
contains_disco = False
contains_twist = False
for sub_item in os.listdir(current_path):
sub_item_path = os.path.join(current_path, sub_item)
if os.path.isdir(sub_item_path):
# 检查是否包含DISCO目标文件夹
if "DISCO Dyn Mph 4 Nav" in sub_item:
contains_disco = True
# 检查是否包含twist目标文件夹
if "twist_dixon_tra_dyn_p4" in sub_item:
contains_twist = True
# 如果包含DISCO目标文件夹
if contains_disco:
print(f"找到包含DISCO文件夹的父文件夹: {current_folder_name} (路径: {current_path})")
process_disco_parent_folder(current_path, current_folder_name, output_path)
# 找到后不需要继续递归,因为这个父文件夹就是我们需要的
return
# 如果包含twist目标文件夹
if contains_twist:
print(f"找到包含twist文件夹的父文件夹: {current_folder_name} (路径: {current_path})")
process_twist_parent_folder(current_path, current_folder_name, output_path)
# 找到后不需要继续递归,因为这个父文件夹就是我们需要的
return
# 如果不包含目标文件夹,继续递归遍历子目录
for sub_item in os.listdir(current_path):
sub_item_path = os.path.join(current_path, sub_item)
if os.path.isdir(sub_item_path):
process_directory(sub_item_path, output_path)
def process_disco_parent_folder(folder_path, folder_name, output_path):
"""
处理包含DISCO目标文件夹的父文件夹
"""
# 遍历父文件夹内的所有子文件夹
for sub_item in os.listdir(folder_path):
sub_item_path = os.path.join(folder_path, sub_item)
if not os.path.isdir(sub_item_path):
continue
# 查找包含 "DISCO Dyn Mph 4 Nav_0" 的文件夹
if "DISCO Dyn Mph 4 Nav_0" in sub_item:
target_path = os.path.join(output_path, "DCE0", f"DCE0_{folder_name}")
# 如果目标路径已存在,先删除
if os.path.exists(target_path):
shutil.rmtree(target_path)
# 复制文件夹
try:
shutil.copytree(sub_item_path, target_path)
print(f" 复制到 DCE0: {sub_item} -> DCE0_{folder_name}")
except Exception as e:
print(f" 复制失败: {e}")
# 查找包含 "DISCO Dyn Mph 4 Nav_7" 的文件夹
elif "DISCO Dyn Mph 4 Nav_7" in sub_item:
target_path = os.path.join(output_path, "DCE1", f"DCE1_{folder_name}")
# 如果目标路径已存在,先删除
if os.path.exists(target_path):
shutil.rmtree(target_path)
# 复制文件夹
try:
shutil.copytree(sub_item_path, target_path)
print(f" 复制到 DCE1: {sub_item} -> DCE1_{folder_name}")
except Exception as e:
print(f" 复制失败: {e}")
def process_twist_parent_folder(folder_path, folder_name, output_path):
"""
处理包含twist目标文件夹的父文件夹
"""
# 收集所有符合条件的文件夹
candidate_folders = []
# # 首先找到包含"twist_dixon_tra_dyn_p4"的文件夹
# twist_folder_path = None
# for sub_item in os.listdir(folder_path):
# sub_item_path = os.path.join(folder_path, sub_item)
#
# if not os.path.isdir(sub_item_path):
# continue
#
# # 找到包含"twist_dixon_tra_dyn_p4"的文件夹
# if "twist_dixon_tra_dyn_p4" in sub_item:
# twist_folder_path = sub_item_path
# break
#
# if not twist_folder_path:
# print(f" 错误: 未找到包含twist_dixon_tra_dyn_p4的文件夹")
# return
# 遍历twist文件夹内的子文件夹
for sub_item in os.listdir(folder_path):
sub_item_path = os.path.join(folder_path, sub_item)
if not os.path.isdir(sub_item_path):
continue
# 检查是否包含目标关键词且以"_W"结尾
if "vibe-twist_dixon_tra_dyn_p4_TTC" in sub_item and sub_item.endswith("_W"):
# 提取TTC值
match = re.search(r'TTC=([\d\.]+)s_', sub_item)
if match:
ttc_value = float(match.group(1))
candidate_folders.append((sub_item, sub_item_path, ttc_value))
print(f" 找到候选文件夹: {sub_item}, TTC={ttc_value}")
if not candidate_folders:
print(f" 未找到符合条件的文件夹")
return
# 按TTC值排序
candidate_folders.sort(key=lambda x: x[2])
# 找出TTC值最小的文件夹用于DCE0
dce0_candidate = candidate_folders[0]
target_path = os.path.join(output_path, "DCE0", f"DCE0_{folder_name}")
# 如果目标路径已存在,先删除
if os.path.exists(target_path):
shutil.rmtree(target_path)
# 复制文件夹到DCE0
try:
shutil.copytree(dce0_candidate[1], target_path)
print(f" 复制到 DCE0: {dce0_candidate[0]} -> DCE0_{folder_name}")
except Exception as e:
print(f" 复制失败: {e}")
# 找出TTC值大于200且最小的文件夹用于DCE1
dce1_candidate = None
for item, item_path, ttc_value in candidate_folders:
if ttc_value > 200:
dce1_candidate = (item, item_path, ttc_value)
break
if dce1_candidate:
target_path = os.path.join(output_path, "DCE1", f"DCE1_{folder_name}")
# 如果目标路径已存在,先删除
if os.path.exists(target_path):
shutil.rmtree(target_path)
# 复制文件夹到DCE1
try:
shutil.copytree(dce1_candidate[1], target_path)
print(f" 复制到 DCE1: {dce1_candidate[0]} -> DCE1_{folder_name}")
except Exception as e:
print(f" 复制失败: {e}")
else:
print(f" 未找到TTC>200的文件夹")
def main():
"""
主函数
"""
# 获取用户输入
print("文件夹处理工具")
print("=" * 50)
# # 输入根目录路径
# while True:
# root_path = input("请输入要遍历的根目录路径: ").strip()
# if os.path.isdir(root_path):
# break
# else:
# print(f"错误: 路径 '{root_path}' 不存在或不是目录")
#
# # 输入输出路径
# while True:
# output_path = input("请输入输出目录路径: ").strip()
# # 尝试创建输出目录
# try:
# os.makedirs(output_path, exist_ok=True)
# break
# except Exception as e:
# print(f"错误: 无法创建输出目录 '{output_path}': {e}")
root_path = "E:\\2pi增强已分期"
output_path = "F:\\pi2_convert"
# 执行处理
print("\n开始处理...")
print("-" * 50)
try:
find_target_folders(root_path, output_path)
print("\n处理完成!")
# 显示结果
dce0_path = os.path.join(output_path, "DCE0")
dce1_path = os.path.join(output_path, "DCE1")
if os.path.exists(dce0_path):
dce0_items = os.listdir(dce0_path)
print(f"\nDCE0文件夹中有 {len(dce0_items)} 个文件夹:")
for item in dce0_items:
print(f" {item}")
if os.path.exists(dce1_path):
dce1_items = os.listdir(dce1_path)
print(f"\nDCE1文件夹中有 {len(dce1_items)} 个文件夹:")
for item in dce1_items:
print(f" {item}")
except Exception as e:
print(f"处理过程中出现错误: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,309 @@
import cv2
import insightface
import numpy as np
import os
import datetime
import argparse
from insightface.app import FaceAnalysis
# 设备配置映射NPU采用用户指定的完整参数
DEVICE_CONFIG = {
"cpu": (['CPUExecutionProvider'], -1),
"gpu": (['CUDAExecutionProvider'], 0),
"npu": (
[
(
"CANNExecutionProvider",
{
"device_id": 1,
"arena_extend_strategy": "kNextPowerOfTwo",
"npu_mem_limit": 16*1024*1024*1024,
"op_select_impl_mode": "high_precision",
"precision_mode": "allow_fp32_to_fp16",
"enable_cann_graph": True,
},
),
"CPUExecutionProvider"
],
0
)
}
#allow_fp32_to_fp16
#核心配置参数
THRESHOLD = 0.65
IMAGE_EXTENSIONS = ('.jpg','.jepg','.png','.bmp','.gif')
NPU_REQUIREMENTS = {
"依赖包": "onnxruntime-cann华为官方+ onnxruntime基础",
"驱动要求": "Ascend CANN Toolkit ≥ 5.0.3",
"硬件要求": "华为昇腾芯片如Ascend 310/910",
"文档链接": "https://onnxruntime.ai/docs/execution-providers/community-maintained/CANN-ExecutionProvider.html"
}
# Initialize face analysis model
#app = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider']) # Use 'CUDAExecutionProvider' for GPU
#app.prepare(ctx_id=-1) # ctx_id=-1 for CPU, 0 for GPU
def parse_args():
"""解析命令行参数"""
parser = argparse.ArgumentParser(description="人脸特征提取与同一人照片分组工具支持CPU/GPU/NPU")
parser.add_argument("-d","--device", type=str, choices=DEVICE_CONFIG.keys(),
default="cpu", help=f"指定运行设备")
parser.add_argument("-t","--threshold", type=float, default=THRESHOLD, help=f"相似度阈值,值越大匹配越严格")
parser.add_argument("--npu-device-id", type=int, default="0", help=f"覆盖NPU设备ID")
parser.add_argument("--npu-mem-limit", type=int, default="16", help=f"NPU内存限制")
args = parser.parse_args()
return args
def get_face_embedding(image_path, app):
"""Extract face embedding from an image"""
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"Could not read image: {image_path}")
faces = app.get(img)
if len(faces) < 1:
raise ValueError("No faces detected in the image")
if len(faces) > 1:
print("Warning: Multiple faces detected. Using first detected face")
return faces[0].embedding
def compare_faces(emb1, emb2, threshold): # Adjust this threshold according to your usecase.
"""Compare two embeddings using cosine similarity"""
similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
return similarity, similarity > threshold
def get_all_images_files(directory="."):
"""获取目录下所有支持的图片文件"""
image_files = []
for filename in os.listdir(directory):
file_path = os.path.abspath(filename)
if filename.lower().endswith(IMAGE_EXTENSIONS) and os.path.isfile(file_path):
image_files.append(file_path)
return sorted(image_files)
def find_same_person_groups(embedding_dict, threshold):
"""基于特征向量分组同一人照片"""
ungrouped_files = list(embedding_dict.keys())
same_person_groups = []
single_groups = []
while ungrouped_files:
current_file =ungrouped_files.pop(0)
current_emb = embedding_dict[current_file]
current_group = [current_file]
to_remove = []
for candidate_file in ungrouped_files:
candidate_emb = embedding_dict[candidate_file]
similarity, is_same = compare_faces(current_emb, candidate_emb, threshold)
if is_same:
current_group.append(candidate_file)
to_remove.append(candidate_file)
print(f"匹配成功:{os.path.basename(current_file)}{os.path.basename(candidate_file)}(相似度:{similarity:.4f}")
for file in to_remove:
ungrouped_files.remove(file)
if len(current_group) >= 2:
same_person_groups.append(current_group)
else:
single_groups.append(current_group)
return same_person_groups, single_groups
def generate_log_filename(device):
"""生成带设备类型和时间戳的日志文件名"""
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
return f"face_embeddings_{device}_{timestamp}.log"
def write_embedding_log(log_entries, args, npu_config, log_filename):
"""写入详细日志"""
with open(log_filename, 'w', encoding='utf-8') as f:
f.write("="*80 + "\n")
f.write(f"人脸特征向量分析日志\n")
f.write(f"生成时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"运行设备:{args.device}\n")
if args.device == "npu":
f.write(f"NPU配置{npu_config}\n")
f.write(f"相似度阈值:{args.threshold}\n")
f.write(f"支持图片格式:{','.join(IMAGE_EXTENSIONS)}\n")
f.write(f"处理图片总数:{len(log_entries)}\n")
f.write("="*80 + "\n\n")
for entry in log_entries:
f.write(entry + "\n" + "-"*60 + "\n\n")
print(f"\n 日志文件已保存至:{os.path.abspath(log_filename)}")
def check_npu_environment():
"""检查NPU环境是否满足要求仅检查核心依赖不验证环境变量"""
try:
import onnxruntime as ort
available_providers = ort.get_available_providers()
if 'CANNExecutionProvider' not in available_providers:
return False, "未检测到CANNExecutionProvider请安装onnxruntime-cann包"
return True,"NPU核心依赖检查通过"
except ImportError:
return False, "未安装onnxruntime基础依赖"
except Exception as e:
return False, f"NPU环境检查失败 {str(e)}"
def main():
args = parse_args()
device = args.device
threshold = args.threshold
npu_device_id = args.npu_device_id
npu_mem_limit = args.npu_mem_limit*1024*1024*1024
#生成带设备和时间戳的日志文件名
log_filename = generate_log_filename(device)
#打印启动信息
print("人脸特征提取与同一人照片分组工具支持CPU/GPU/NPU")
print("="*80)
print(f"核心配置:")
print(f"运行设备:{device}")
print(f"相似度阈值:{threshold}")
print(f"搜索目录:{os.getcwd()}")
print(f"支持格式:{','.join(IMAGE_EXTENSIONS)}")
print(f"日志文件:{log_filename}")
if device == "npu":
print(f"NPU设备ID{npu_device_id}")
print(f"NPU内存限制{args.npu_mem_limit}GB")
print("="*80 + "\n")
#设备环境预检查
if device == "gpu":
available_providers = insightface.utils.get_available_providers()
if 'CUDAExecutionProvider' not in available_providers:
print(" 警告未检测到CUDA环境GPU模式可能运行失败")
print(" 解决方案1.安装CUDA≥11.0 + cuDNN 2.安装onnxruntime-gpu 3. 切换至cpu模式")
elif device == "npu":
npu_ok,npu_msg = check_npu_environment()
print(f" NPU环境检查{npu_msg}")
if not npu_ok:
print(" NPU环境不满足要求建议按以下步骤配置")
for key, value in NPU_REQUIREMENTS.items():
print(f" - {key}:{value}")
return
#初始化模型
try:
providers, ctx_id = DEVICE_CONFIG[device]
npu_config = None
if device == "npu":
npu_provider = list(providers[0])
npu_provider[1]["device_id"] = npu_device_id
npu_provider[1]["npu_mem_limit"] = npu_mem_limit
providers[0] = tuple(npu_provider)
npu_config = providers[0][1]
ctx_id = npu_device_id
app = FaceAnalysis(name='buffalo_l', providers=providers)
else:
app = FaceAnalysis(name='buffalo_l', providers=providers)
app.prepare(ctx_id=ctx_id)
print(f" 模型初始化成功(设备:{device}, ctx_id: {ctx_id}")
if device == "npu":
print(f" NPU最终配置")
for key, value in npu_config.items():
if key == "npu_mem_limit":
print(f" - {key}: {value/(1024*1024*1024)}GB")
else:
print(f" - {key}: {value}")
except Exception as e:
print(f"模型初始化失败!")
print(f"Error:{str(e)}")
print(f" 解决方案:")
if device == "gpu":
print(" 1.确认CUDA驱动已安装 2.确认onnxruntime-gpu版本与CUDA匹配 3.尝试切换CPU模式")
elif device == "npu":
print(" 1.确认CANN Toolkit已正确安装 2.确认onnxruntime-cann版本兼容 3.检查设备ID和内存限制是否合理")
return
#1. 获取所有图片文件
image_files = get_all_images_files()
if not image_files:
print("未找到任何支持的图片文件检查目录下是否有jpg/png等格式图片")
return
print(f" 找到{len(image_files)}个图片文件,开始提取特征向量...\n")
#2. 提取特征向量并记录日志
embedding_dict = {}
log_entries = []
for img_path in image_files:
img_name = os.path.basename(img_path)
try:
emb = get_face_embedding(img_path, app)
embedding_dict[img_name] = emb
log_entry = f"【文件】:{img_name}\n" \
f"【路径】:{img_path}\n" \
f"【状态】:成功\n" \
f"【特征向量维度】:{len(emb)}\n" \
f"【特征向量】:{emb.tolist()}"
log_entries.append(log_entry)
print(f" 处理成功: {img_name}(特征向量维度:{len(emb)})")
except Exception as e:
log_entry = f"【文件】:{img_name}\n" \
f"【路径】:{img_path}\n" \
f"【状态】:失败\n" \
f":【错误信息】:{str(e)}"
log_entries.append(log_entry)
print(f" 处理失败: {img_name} - 原因:{str(e)}")
#3. 写入日志文件(使用动态生成的文件名)
write_embedding_log(log_entries, args, npu_config, log_filename)
#4. 同一人分组分析
if not embedding_dict:
print("\n 没有成功提取到人脸特征向量,无法进行分组分析")
return
print(f"\n 开始分组分析(有效人脸数:{len(embedding_dict)}...")
same_groups, single_groups = find_same_person_groups(embedding_dict, threshold)
#5. 输出分组结果
print("\n" + "="*80)
print("同一人照片分组结果每组≥2张:")
print("="*80)
if same_groups:
for i, group in enumerate(same_groups, 1):
group_names = [os.path.basename(file) for file in group]
print(f"{i}:{', '.join(group_names)}")
else:
print(f" 未发现同一人的多张照片")
print("\n" + "="*80)
print("无匹配的单独照片:")
#print("\n"*80)
if single_groups:
for group in single_groups:
print(f" - {os.path.basename(group[0])}")
else:
print(f"所有照片均已分组(无单独照片)")
print("\n" + "="*80)
print(f" 处理完成!详细日志请查看:{log_filename}")
if __name__ == "__main__":
main()
# Paths to your Indian face images
#image1_path = "path/to/face1.jpg"
#image2_path = "path/to/face2.jpg"
#try:
# Get embeddings
# emb1 = get_face_embedding(image1_path)
# emb2 = get_face_embedding(image2_path)
# Compare faces
# similarity_score, is_same_person = compare_faces(emb1, emb2)
# print(f"Similarity Score: {similarity_score:.4f}")
# print(f"Same person? {'YES' if is_same_person else 'NO'}")
#except Exception as e:
# print(f"Error: {str(e)}")

View File

@@ -0,0 +1,340 @@
# video_face_recognition.py
import cv2
import numpy as np
import time
from insightface.app import FaceAnalysis
from typing import List, Dict, Tuple
import os
class VideoFaceRecognition:
"""
视频人脸识别系统
支持实时视频流和视频文件处理
"""
def __init__(self, model_name: str = 'buffalo_l', use_gpu: bool = True):
# 初始化人脸识别模型
self.app = FaceAnalysis(name=model_name)
self.app.prepare(
ctx_id=0 if use_gpu else -1,
det_thresh=0.3,
det_size=(640, 640)
)
self.target_embedding = None
self.target_id = None
self.similarity_threshold = 0.3
# 性能统计
self.frame_count = 0
self.processing_times = []
print(f"✅ 视频人脸识别系统初始化完成 - GPU: {use_gpu}")
def set_target_face(self, image_path: str, person_id: str = "target") -> bool:
"""设置目标人脸"""
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取目标图像: {image_path}")
return False
faces = self.app.get(img)
if not faces:
print(f"❌ 目标图像中未检测到人脸: {image_path}")
return False
self.target_embedding = faces[0].embedding
self.target_id = person_id
print(f"✅ 目标人脸设置: {person_id}")
return True
def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, List[Dict]]:
"""
处理单帧图像
返回: (处理后的帧, 识别结果列表)
"""
start_time = time.time()
# 人脸检测和识别
faces = self.app.get(frame)
results = []
for face in faces:
similarity = 0.0
if self.target_embedding is not None:
# 计算相似度
emb1 = face.embedding / np.linalg.norm(face.embedding)
emb2 = self.target_embedding / np.linalg.norm(self.target_embedding)
similarity = float(np.dot(emb1, emb2))
result = {
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'is_match': similarity >= self.similarity_threshold,
'gender': 'Male' if face.gender == 1 else 'Female',
'age': int(face.age),
'det_score': float(face.det_score)
}
results.append(result)
# 在帧上绘制结果
frame = self._draw_detection(frame, result)
# 性能统计
processing_time = (time.time() - start_time) * 1000
self.processing_times.append(processing_time)
self.frame_count += 1
return frame, results
def _draw_detection(self, frame: np.ndarray, result: Dict) -> np.ndarray:
"""在帧上绘制检测结果"""
bbox = result['bbox']
similarity = result['similarity']
is_match = result['is_match']
# 选择颜色
if is_match:
color = (0, 255, 0) # 绿色 - 匹配
# elif similarity > self.similarity_threshold/2:
# color = (0, 255, 255) # 黄色 - 中等相似度
else:
color = (0, 0, 255) # 红色 - 不匹配
# 绘制人脸框
x1, y1, x2, y2 = bbox
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# 绘制信息文本
if self.target_id:
status = f"MATCH: {similarity:.3f}" if is_match else f"NO MATCH: {similarity:.3f}"
# text = f"{self.target_id}: {status}"
text = status
else:
text = f"Similarity: {similarity:.3f}"
# 文本背景
text_size = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
cv2.rectangle(frame, (x1, y1 - text_size[1] - 10), (x1 + text_size[0], y1), color, -1)
# 文本
cv2.putText(frame, text, (x1, y1 - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# 详细信息
info_text = f"{result['gender']}/{result['age']}"
cv2.putText(frame, info_text, (x1, y2 + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
return frame
def process_video_file(self, video_path: str, output_path: str = None,
skip_frames: int = 0, show_preview: bool = True):
"""
处理视频文件
Args:
video_path: 输入视频路径
output_path: 输出视频路径
skip_frames: 跳帧数,用于提高处理速度
show_preview: 是否显示实时预览
"""
if not os.path.exists(video_path):
print(f"❌ 视频文件不存在: {video_path}")
return
# 打开视频文件
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"📹 视频信息: {width}x{height}, {fps:.1f}FPS, 总帧数: {total_frames}")
# 设置输出视频
if output_path:
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps/(skip_frames), (width, height))
else:
out = None
# 处理视频帧
frame_index = 0
processed_frames = 0
start_time = time.time()
print("🚀 开始处理视频...")
while True:
ret, frame = cap.read()
if not ret:
break
# 跳帧处理
if skip_frames > 0 and frame_index % (skip_frames + 1) != 0:
frame_index += 1
continue
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 写入输出视频
if out:
out.write(processed_frame)
# 显示预览
if show_preview:
# 添加性能信息
fps_text = f"Frame: {frame_index}/{total_frames} | Faces: {len(results)}"
cv2.putText(processed_frame, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow('Video Face Recognition', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_index += 1
processed_frames += 1
# 进度显示
if frame_index % 30 == 0:
progress = (frame_index / total_frames) * 100
print(f"📊 处理进度: {progress:.1f}% ({frame_index}/{total_frames})")
# 清理资源
cap.release()
if out:
out.release()
if show_preview:
cv2.destroyAllWindows()
# 性能统计
total_time = time.time() - start_time
avg_processing_time = np.mean(self.processing_times) if self.processing_times else 0
print(f"\n🎉 视频处理完成!")
print(f"📊 性能统计:")
print(f" 总处理帧数: {processed_frames}")
print(f" 总耗时: {total_time:.1f}")
print(f" 平均每帧: {avg_processing_time:.1f}ms")
print(f" 实际FPS: {processed_frames / total_time:.1f}")
if output_path:
print(f" 输出视频: {output_path}")
def process_webcam(self, camera_id: int = 0, output_path: str = None):
"""
处理摄像头实时视频流
"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"❌ 无法打开摄像头 {camera_id}")
return
# 设置摄像头分辨率(可选)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置输出视频
if output_path:
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
else:
out = None
print("🎥 开始摄像头实时识别 (按 'q' 退出)...")
while True:
ret, frame = cap.read()
if not ret:
print("❌ 无法读取摄像头帧")
break
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 添加实时信息
current_fps = 1000 / self.processing_times[-1] if self.processing_times else 0
info_text = f"FPS: {current_fps:.1f} | Faces: {len(results)}"
cv2.putText(processed_frame, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 写入输出
if out:
out.write(processed_frame)
# 显示预览
cv2.imshow('Real-time Face Recognition', processed_frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 清理资源
cap.release()
if out:
out.release()
cv2.destroyAllWindows()
print("✅ 摄像头处理结束")
# 使用示例
def main():
# 创建视频识别系统
video_system = VideoFaceRecognition(use_gpu=True)
# 设置目标人脸(可选)
# target_image = "test_data/register/person1.png"
target_image = "test_data/register/sy.jpg"
if os.path.exists(target_image):
video_system.set_target_face(target_image, "目标人物")
# 选择处理模式
print("请选择处理模式:")
# print("1. 处理视频文件")
# print("2. 实时摄像头")
# choice = input("请输入选择 (1 或 2): ").strip()
choice = "1";
if choice == "1":
# 处理视频文件
video_path = "test_data/video/video_1.mp4"
output_path = "test_data/output_video/video_1.mp4"
# 性能优化:跳帧处理
skip_frames = 1 # 每2帧处理1帧提高速度
video_system.process_video_file(
video_path=video_path,
output_path=output_path,
skip_frames=skip_frames,
show_preview=True
)
elif choice == "2":
# 实时摄像头
output_path = "webcam_recording.mp4" # 可选:保存录制
video_system.process_webcam(
camera_id=0,
output_path=output_path
)
else:
print("❌ 无效选择")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,466 @@
# video_face_recognition.py
import cv2
import numpy as np
import time
from insightface.app import FaceAnalysis
from typing import List, Dict, Tuple
import os
class VideoFaceRecognition:
"""
视频人脸识别系统
支持实时视频流和视频文件处理
"""
def __init__(self, model_name: str = 'buffalo_l', use_gpu: bool = True):
# 初始化人脸识别模型
self.app = FaceAnalysis(name=model_name)
self.app.prepare(
ctx_id=0 if use_gpu else -1,
det_thresh=0.3,
det_size=(640, 640)
)
self.target_embedding = None
self.target_id = None
self.similarity_threshold = 0.3
# 性能统计
self.frame_count = 0
self.processing_times = []
print(f"✅ 视频人脸识别系统初始化完成 - GPU: {use_gpu}")
def set_target_face(self, image_path: str, person_id: str = "target") -> bool:
"""设置目标人脸"""
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取目标图像: {image_path}")
return False
faces = self.app.get(img)
if not faces:
print(f"❌ 目标图像中未检测到人脸: {image_path}")
return False
self.target_embedding = faces[0].embedding
self.target_id = person_id
print(f"✅ 目标人脸设置: {person_id}")
return True
def calculate_face_quality(self, face) -> Dict:
"""
计算人脸质量指标
"""
quality_metrics = {}
# 1. 检测置信度
quality_metrics['det_score'] = float(face.det_score)
# 2. 人脸姿态角度 (pitch, yaw, roll)
if hasattr(face, 'pose') and face.pose is not None:
pitch, yaw, roll = face.pose
quality_metrics['pitch'] = float(pitch) # 俯仰角
quality_metrics['yaw'] = float(yaw) # 偏航角
quality_metrics['roll'] = float(roll) # 翻滚角
else:
quality_metrics['pitch'] = 0.0
quality_metrics['yaw'] = 0.0
quality_metrics['roll'] = 0.0
# 3. 人脸边界框信息
bbox = face.bbox
width = bbox[2] - bbox[0]
height = bbox[3] - bbox[1]
quality_metrics['bbox_area'] = width * height
quality_metrics['aspect_ratio'] = width / height if height > 0 else 0
# 4. 关键点质量评估 (基于关键点分布)
if hasattr(face, 'kps') and face.kps is not None:
kps = face.kps
# 计算关键点分布的均匀性
if len(kps) >= 5:
# 计算关键点之间的平均距离
distances = []
for i in range(len(kps)):
for j in range(i + 1, len(kps)):
dist = np.linalg.norm(kps[i] - kps[j])
distances.append(dist)
if distances:
quality_metrics['kps_variance'] = float(np.var(distances))
else:
quality_metrics['kps_variance'] = 0.0
else:
quality_metrics['kps_variance'] = 0.0
else:
quality_metrics['kps_variance'] = 0.0
# 5. 综合质量评分
# 基于检测得分、姿态角度、边界框大小等因素
base_score = quality_metrics['det_score']
# 姿态惩罚 - 角度越大质量分越低
pose_penalty = 0.0
if abs(quality_metrics['yaw']) > 30: # 偏航角大于30度惩罚
pose_penalty += 0.2
if abs(quality_metrics['pitch']) > 20: # 俯仰角大于20度惩罚
pose_penalty += 0.2
quality_metrics['quality_score'] = max(0.1, base_score - pose_penalty)
return quality_metrics
def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, List[Dict]]:
"""
处理单帧图像
返回: (处理后的帧, 识别结果列表)
"""
start_time = time.time()
# 人脸检测和识别
faces = self.app.get(frame)
results = []
for face in faces:
similarity = 0.0
if self.target_embedding is not None:
# 计算相似度
emb1 = face.embedding / np.linalg.norm(face.embedding)
emb2 = self.target_embedding / np.linalg.norm(self.target_embedding)
similarity = float(np.dot(emb1, emb2))
# 计算人脸质量指标
quality_metrics = self.calculate_face_quality(face)
result = {
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'is_match': similarity >= self.similarity_threshold,
'gender': 'Male' if face.gender == 1 else 'Female',
'age': int(face.age),
'det_score': float(face.det_score),
'quality_metrics': quality_metrics # 添加质量指标
}
results.append(result)
# 在帧上绘制结果
frame = self._draw_detection(frame, result)
# 性能统计
processing_time = (time.time() - start_time) * 1000
self.processing_times.append(processing_time)
self.frame_count += 1
return frame, results
def _draw_detection(self, frame: np.ndarray, result: Dict) -> np.ndarray:
"""在帧上绘制检测结果和质量信息"""
bbox = result['bbox']
similarity = result['similarity']
is_match = result['is_match']
quality_metrics = result['quality_metrics']
# 选择颜色
if is_match:
color = (0, 255, 0) # 绿色 - 匹配
else:
color = (0, 0, 255) # 红色 - 不匹配
# 绘制人脸框
x1, y1, x2, y2 = bbox
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# 准备显示文本
text_lines = []
# 第一行:匹配状态和相似度
if self.target_id:
status = f"MATCH: {similarity:.3f}" if is_match else f"NO MATCH: {similarity:.3f}"
text_lines.append(status)
else:
text_lines.append(f"Similarity: {similarity:.3f}")
# 第二行:基础信息
text_lines.append(f"{result['gender']}/{result['age']}")
# 第三行:质量得分和检测得分
text_lines.append(f"Quality: {quality_metrics['quality_score']:.3f}")
text_lines.append(f"DetScore: {quality_metrics['det_score']:.3f}")
# 第四行:姿态角度
text_lines.append(f"Pitch: {quality_metrics['pitch']:.1f}°")
text_lines.append(f"Yaw: {quality_metrics['yaw']:.1f}°")
text_lines.append(f"Roll: {quality_metrics['roll']:.1f}°")
# 第五行:其他质量指标
text_lines.append(f"Area: {quality_metrics['bbox_area']:.0f}")
text_lines.append(f"Aspect: {quality_metrics['aspect_ratio']:.2f}")
# 计算文本区域大小
max_text_width = 0
total_text_height = 0
line_heights = []
for line in text_lines:
(text_width, text_height), baseline = cv2.getTextSize(line, cv2.FONT_HERSHEY_SIMPLEX, 0.4, 1)
max_text_width = max(max_text_width, text_width)
line_heights.append(text_height + baseline)
total_text_height += text_height + baseline + 2 # 2像素行间距
# 绘制文本背景
bg_x1 = x1
bg_y1 = y1 - total_text_height - 10
bg_x2 = x1 + max_text_width + 10
bg_y2 = y1
# 如果背景超出图像顶部,调整到框下方
if bg_y1 < 0:
bg_y1 = y2
bg_y2 = y2 + total_text_height + 10
# 绘制半透明背景
overlay = frame.copy()
cv2.rectangle(overlay, (bg_x1, bg_y1), (bg_x2, bg_y2), (0, 0, 0), -1)
alpha = 0.6 # 透明度
cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame)
# 绘制文本
current_y = bg_y1 + 15
for i, line in enumerate(text_lines):
# 根据内容选择颜色
if i == 0: # 匹配状态行
text_color = (0, 255, 0) if is_match else (0, 0, 255)
elif i in [2, 3]: # 质量得分行
# 根据质量得分调整颜色
quality = quality_metrics['quality_score']
if quality > 0.7:
text_color = (0, 255, 0) # 绿色 - 高质量
elif quality > 0.4:
text_color = (0, 255, 255) # 黄色 - 中等质量
else:
text_color = (0, 0, 255) # 红色 - 低质量
elif i in [4, 5, 6]: # 姿态角度行
# 根据角度大小调整颜色
if abs(quality_metrics['yaw']) > 45 or abs(quality_metrics['pitch']) > 30:
text_color = (0, 0, 255) # 红色 - 角度过大
elif abs(quality_metrics['yaw']) > 30 or abs(quality_metrics['pitch']) > 20:
text_color = (0, 255, 255) # 黄色 - 角度偏大
else:
text_color = (0, 255, 0) # 绿色 - 角度良好
else:
text_color = (255, 255, 255) # 白色 - 普通信息
cv2.putText(frame, line, (x1 + 5, current_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, text_color, 1)
current_y += line_heights[i]
return frame
def process_video_file(self, video_path: str, output_path: str = None,
skip_frames: int = 0, show_preview: bool = True):
"""
处理视频文件
Args:
video_path: 输入视频路径
output_path: 输出视频路径
skip_frames: 跳帧数,用于提高处理速度
show_preview: 是否显示实时预览
"""
if not os.path.exists(video_path):
print(f"❌ 视频文件不存在: {video_path}")
return
# 打开视频文件
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"📹 视频信息: {width}x{height}, {fps:.1f}FPS, 总帧数: {total_frames}")
# 设置输出视频
if output_path:
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps / (skip_frames + 1), (width, height))
else:
out = None
# 处理视频帧
frame_index = 0
processed_frames = 0
start_time = time.time()
print("🚀 开始处理视频...")
while True:
ret, frame = cap.read()
if not ret:
break
# 跳帧处理
if skip_frames > 0 and frame_index % (skip_frames + 1) != 0:
frame_index += 1
continue
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 写入输出视频
if out:
out.write(processed_frame)
# 显示预览
if show_preview:
# 添加性能信息
fps_text = f"Frame: {frame_index}/{total_frames} | Faces: {len(results)}"
cv2.putText(processed_frame, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow('Video Face Recognition', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_index += 1
processed_frames += 1
# 进度显示
if frame_index % 30 == 0:
progress = (frame_index / total_frames) * 100
print(f"📊 处理进度: {progress:.1f}% ({frame_index}/{total_frames})")
# 清理资源
cap.release()
if out:
out.release()
if show_preview:
cv2.destroyAllWindows()
# 性能统计
total_time = time.time() - start_time
avg_processing_time = np.mean(self.processing_times) if self.processing_times else 0
print(f"\n🎉 视频处理完成!")
print(f"📊 性能统计:")
print(f" 总处理帧数: {processed_frames}")
print(f" 总耗时: {total_time:.1f}")
print(f" 平均每帧: {avg_processing_time:.1f}ms")
print(f" 实际FPS: {processed_frames / total_time:.1f}")
if output_path:
print(f" 输出视频: {output_path}")
def process_webcam(self, camera_id: int = 0, output_path: str = None):
"""
处理摄像头实时视频流
"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"❌ 无法打开摄像头 {camera_id}")
return
# 设置摄像头分辨率(可选)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置输出视频
if output_path:
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
else:
out = None
print("🎥 开始摄像头实时识别 (按 'q' 退出)...")
while True:
ret, frame = cap.read()
if not ret:
print("❌ 无法读取摄像头帧")
break
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 添加实时信息
current_fps = 1000 / self.processing_times[-1] if self.processing_times else 0
info_text = f"FPS: {current_fps:.1f} | Faces: {len(results)}"
cv2.putText(processed_frame, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 写入输出
if out:
out.write(processed_frame)
# 显示预览
cv2.imshow('Real-time Face Recognition', processed_frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 清理资源
cap.release()
if out:
out.release()
cv2.destroyAllWindows()
print("✅ 摄像头处理结束")
# 使用示例
def main():
# 创建视频识别系统
video_system = VideoFaceRecognition(use_gpu=True)
# 设置目标人脸(可选)
target_image = "test_data/register/sy.jpg"
if os.path.exists(target_image):
video_system.set_target_face(target_image, "目标人物")
# 选择处理模式
print("请选择处理模式:")
print("1. 处理视频文件")
print("2. 实时摄像头")
choice = input("请输入选择 (1 或 2): ").strip()
if choice == "1":
# 处理视频文件
video_path = "test_data/video/video_1.mp4"
output_path = "test_data/output_video/video_1_quality.mp4"
# 性能优化:跳帧处理
skip_frames = 1 # 每2帧处理1帧提高速度
video_system.process_video_file(
video_path=video_path,
output_path=output_path,
skip_frames=skip_frames,
show_preview=True
)
elif choice == "2":
# 实时摄像头
output_path = "webcam_recording.mp4" # 可选:保存录制
video_system.process_webcam(
camera_id=0,
output_path=output_path
)
else:
print("❌ 无效选择")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,541 @@
# video_face_recognition.py
import cv2
import numpy as np
import time
from insightface.app import FaceAnalysis
from typing import List, Dict, Tuple
import os
class VideoFaceRecognition:
"""
视频人脸识别系统
支持实时视频流和视频文件处理
"""
def __init__(self, model_name: str = 'buffalo_l', use_gpu: bool = True):
# 初始化人脸识别模型
self.app = FaceAnalysis(name=model_name)
self.app.prepare(
ctx_id=0 if use_gpu else -1,
det_thresh=0.2,
det_size=(640, 640)
)
self.target_embedding = None
self.target_id = None
self.similarity_threshold = 0.3
# 质量阈值设置
self.clarity_threshold = 50.0 # 清晰度阈值,低于此值认为人脸模糊
self.min_face_size = 40 # 最小人脸像素尺寸
# 性能统计
self.frame_count = 0
self.processing_times = []
print(f"✅ 视频人脸识别系统初始化完成 - GPU: {use_gpu}")
def set_target_face(self, image_path: str, person_id: str = "target") -> bool:
"""设置目标人脸"""
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取目标图像: {image_path}")
return False
faces = self.app.get(img)
if not faces:
print(f"❌ 目标图像中未检测到人脸: {image_path}")
return False
self.target_embedding = faces[0].embedding
self.target_id = person_id
print(f"✅ 目标人脸设置: {person_id}")
return True
def calculate_clarity(self, face_region: np.ndarray) -> float:
"""
计算人脸区域的清晰度/模糊度
使用拉普拉斯方差方法:值越高表示图像越清晰
"""
if len(face_region.shape) == 3:
gray = cv2.cvtColor(face_region, cv2.COLOR_BGR2GRAY)
else:
gray = face_region
# 计算拉普拉斯算子的方差
laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
return laplacian_var
def is_face_quality_acceptable(self, face, frame: np.ndarray) -> Tuple[bool, Dict]:
"""
综合判断人脸质量是否可接受
返回: (是否可接受, 质量指标字典)
"""
quality_metrics = {}
# 1. 检测置信度
quality_metrics['det_score'] = float(face.det_score)
# 2. 人脸姿态角度
if hasattr(face, 'pose') and face.pose is not None:
pitch, yaw, roll = face.pose
quality_metrics['pitch'] = float(pitch)
quality_metrics['yaw'] = float(yaw)
quality_metrics['roll'] = float(roll)
else:
quality_metrics['pitch'] = 0.0
quality_metrics['yaw'] = 0.0
quality_metrics['roll'] = 0.0
# 3. 人脸边界框信息
bbox = face.bbox
x1, y1, x2, y2 = bbox.astype(int)
width = x2 - x1
height = y2 - y1
quality_metrics['bbox_area'] = width * height
quality_metrics['aspect_ratio'] = width / height if height > 0 else 0
# 4. 图像清晰度检测
# 提取人脸区域
h, w = frame.shape[:2]
x1_clip = max(0, x1)
y1_clip = max(0, y1)
x2_clip = min(w, x2)
y2_clip = min(h, y2)
if x2_clip > x1_clip and y2_clip > y1_clip:
face_region = frame[y1_clip:y2_clip, x1_clip:x2_clip]
clarity_score = self.calculate_clarity(face_region)
quality_metrics['clarity_score'] = clarity_score
else:
quality_metrics['clarity_score'] = 0.0
# 5. 关键点质量评估
if hasattr(face, 'kps') and face.kps is not None:
kps = face.kps
if len(kps) >= 5:
distances = []
for i in range(len(kps)):
for j in range(i + 1, len(kps)):
dist = np.linalg.norm(kps[i] - kps[j])
distances.append(dist)
if distances:
quality_metrics['kps_variance'] = float(np.var(distances))
else:
quality_metrics['kps_variance'] = 0.0
else:
quality_metrics['kps_variance'] = 0.0
else:
quality_metrics['kps_variance'] = 0.0
# 6. 综合质量评分
base_score = quality_metrics['det_score']
# 清晰度惩罚
clarity_penalty = 0.0
if quality_metrics['clarity_score'] < self.clarity_threshold:
clarity_penalty = 0.3 # 清晰度不足严重惩罚
# 姿态惩罚
pose_penalty = 0.0
if abs(quality_metrics['yaw']) > 30:
pose_penalty += 0.2
if abs(quality_metrics['pitch']) > 20:
pose_penalty += 0.2
# 尺寸惩罚
size_penalty = 0.0
if quality_metrics['bbox_area'] < (self.min_face_size ** 2):
size_penalty = 0.2
quality_metrics['quality_score'] = max(0.1, base_score - clarity_penalty - pose_penalty - size_penalty)
# 判断是否可接受
is_acceptable = (
quality_metrics['det_score'] > 0.5 and # 基础检测置信度
quality_metrics['clarity_score'] >= self.clarity_threshold and # 清晰度要求
quality_metrics['bbox_area'] >= (self.min_face_size ** 2) and # 最小尺寸要求
abs(quality_metrics['yaw']) < 60 and # 偏航角限制
abs(quality_metrics['pitch']) < 45 # 俯仰角限制
)
return is_acceptable, quality_metrics
def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, List[Dict]]:
"""
处理单帧图像
返回: (处理后的帧, 识别结果列表)
"""
start_time = time.time()
# 人脸检测和识别
faces = self.app.get(frame)
results = []
for face in faces:
# 检查人脸质量是否可接受
is_acceptable, quality_metrics = self.is_face_quality_acceptable(face, frame)
similarity = 0.0
# 只有在人脸质量可接受时才计算相似度
if is_acceptable and self.target_embedding is not None:
emb1 = face.embedding / np.linalg.norm(face.embedding)
emb2 = self.target_embedding / np.linalg.norm(self.target_embedding)
similarity = float(np.dot(emb1, emb2))
result = {
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'is_match': similarity >= self.similarity_threshold,
'gender': 'Male' if face.gender == 1 else 'Female',
'age': int(face.age),
'det_score': float(face.det_score),
'quality_metrics': quality_metrics,
'is_acceptable': is_acceptable # 新增:是否可接受标志
}
results.append(result)
# 在帧上绘制结果
frame = self._draw_detection(frame, result)
# 性能统计
processing_time = (time.time() - start_time) * 1000
self.processing_times.append(processing_time)
self.frame_count += 1
return frame, results
def _draw_detection(self, frame: np.ndarray, result: Dict) -> np.ndarray:
"""在帧上绘制检测结果和质量信息"""
bbox = result['bbox']
similarity = result['similarity']
is_match = result['is_match']
is_acceptable = result['is_acceptable']
quality_metrics = result['quality_metrics']
# 选择颜色
if not is_acceptable:
color = (128, 128, 128) # 灰色 - 质量不可接受
elif is_match:
color = (0, 255, 0) # 绿色 - 匹配
else:
color = (0, 0, 255) # 红色 - 不匹配
# 绘制人脸框
x1, y1, x2, y2 = bbox
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# 准备显示文本
text_lines = []
# 第一行:质量状态和匹配状态
if not is_acceptable:
text_lines.append("LOW QUALITY")
elif self.target_id:
status = f"MATCH: {similarity:.3f}" if is_match else f"NO MATCH: {similarity:.3f}"
text_lines.append(status)
else:
text_lines.append(f"Similarity: {similarity:.3f}")
# 第二行:基础信息
text_lines.append(f"{result['gender']}/{result['age']}")
# 第三行:质量得分
text_lines.append(f"Quality: {quality_metrics['quality_score']:.3f}")
text_lines.append(f"DetScore: {quality_metrics['det_score']:.3f}")
# 第四行:清晰度(关键指标)
clarity_status = "CLEAR" if quality_metrics['clarity_score'] >= self.clarity_threshold else "BLUR"
text_lines.append(f"Clarity: {clarity_status} ({quality_metrics['clarity_score']:.1f})")
# 第五行:姿态角度
text_lines.append(f"Pitch: {quality_metrics['pitch']:.1f}°")
text_lines.append(f"Yaw: {quality_metrics['yaw']:.1f}°")
text_lines.append(f"Roll: {quality_metrics['roll']:.1f}°")
# 第六行:其他质量指标
text_lines.append(f"Area: {quality_metrics['bbox_area']:.0f}")
# 计算文本区域大小
max_text_width = 0
total_text_height = 0
line_heights = []
for line in text_lines:
(text_width, text_height), baseline = cv2.getTextSize(line, cv2.FONT_HERSHEY_SIMPLEX, 0.4, 1)
max_text_width = max(max_text_width, text_width)
line_heights.append(text_height + baseline)
total_text_height += text_height + baseline + 2
# 绘制文本背景
bg_x1 = x1
bg_y1 = y1 - total_text_height - 10
bg_x2 = x1 + max_text_width + 10
bg_y2 = y1
# 如果背景超出图像顶部,调整到框下方
if bg_y1 < 0:
bg_y1 = y2
bg_y2 = y2 + total_text_height + 10
# 绘制半透明背景
overlay = frame.copy()
cv2.rectangle(overlay, (bg_x1, bg_y1), (bg_x2, bg_y2), (0, 0, 0), -1)
alpha = 0.6
cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame)
# 绘制文本
current_y = bg_y1 + 15
for i, line in enumerate(text_lines):
# 根据内容选择颜色
if i == 0: # 状态行
if not is_acceptable:
text_color = (128, 128, 128) # 灰色 - 质量差
elif is_match:
text_color = (0, 255, 0) # 绿色 - 匹配
else:
text_color = (0, 0, 255) # 红色 - 不匹配
elif i == 3: # 清晰度行
if quality_metrics['clarity_score'] >= self.clarity_threshold:
text_color = (0, 255, 0) # 绿色 - 清晰
else:
text_color = (0, 0, 255) # 红色 - 模糊
elif i in [4, 5, 6]: # 姿态角度行
if abs(quality_metrics['yaw']) > 45 or abs(quality_metrics['pitch']) > 30:
text_color = (0, 0, 255) # 红色 - 角度过大
elif abs(quality_metrics['yaw']) > 30 or abs(quality_metrics['pitch']) > 20:
text_color = (0, 255, 255) # 黄色 - 角度偏大
else:
text_color = (0, 255, 0) # 绿色 - 角度良好
else:
text_color = (255, 255, 255) # 白色
cv2.putText(frame, line, (x1 + 5, current_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, text_color, 1)
current_y += line_heights[i]
return frame
def set_quality_thresholds(self, clarity_threshold: float = None, min_face_size: int = None):
"""设置质量阈值"""
if clarity_threshold is not None:
self.clarity_threshold = clarity_threshold
if min_face_size is not None:
self.min_face_size = min_face_size
print(f"✅ 质量阈值更新 - 清晰度: {self.clarity_threshold}, 最小尺寸: {self.min_face_size}")
# 原有的 process_video_file 和 process_webcam 方法保持不变
def process_video_file(self, video_path: str, output_path: str = None,
skip_frames: int = 0, show_preview: bool = True):
"""
处理视频文件
Args:
video_path: 输入视频路径
output_path: 输出视频路径
skip_frames: 跳帧数,用于提高处理速度
show_preview: 是否显示实时预览
"""
if not os.path.exists(video_path):
print(f"❌ 视频文件不存在: {video_path}")
return
# 打开视频文件
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"📹 视频信息: {width}x{height}, {fps:.1f}FPS, 总帧数: {total_frames}")
# 设置输出视频
if output_path:
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps / (skip_frames + 1), (width, height))
else:
out = None
# 处理视频帧
frame_index = 0
processed_frames = 0
start_time = time.time()
print("🚀 开始处理视频...")
while True:
ret, frame = cap.read()
if not ret:
break
# 跳帧处理
if skip_frames > 0 and frame_index % (skip_frames + 1) != 0:
frame_index += 1
continue
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 写入输出视频
if out:
out.write(processed_frame)
# 显示预览
if show_preview:
# 添加性能信息
fps_text = f"Frame: {frame_index}/{total_frames} | Faces: {len(results)}"
cv2.putText(processed_frame, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow('Video Face Recognition', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_index += 1
processed_frames += 1
# 进度显示
if frame_index % 30 == 0:
progress = (frame_index / total_frames) * 100
print(f"📊 处理进度: {progress:.1f}% ({frame_index}/{total_frames})")
# 清理资源
cap.release()
if out:
out.release()
if show_preview:
cv2.destroyAllWindows()
# 性能统计
total_time = time.time() - start_time
avg_processing_time = np.mean(self.processing_times) if self.processing_times else 0
print(f"\n🎉 视频处理完成!")
print(f"📊 性能统计:")
print(f" 总处理帧数: {processed_frames}")
print(f" 总耗时: {total_time:.1f}")
print(f" 平均每帧: {avg_processing_time:.1f}ms")
print(f" 实际FPS: {processed_frames / total_time:.1f}")
if output_path:
print(f" 输出视频: {output_path}")
def process_webcam(self, camera_id: int = 0, output_path: str = None):
"""
处理摄像头实时视频流
"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"❌ 无法打开摄像头 {camera_id}")
return
# 设置摄像头分辨率(可选)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置输出视频
if output_path:
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
else:
out = None
print("🎥 开始摄像头实时识别 (按 'q' 退出)...")
while True:
ret, frame = cap.read()
if not ret:
print("❌ 无法读取摄像头帧")
break
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 添加实时信息
current_fps = 1000 / self.processing_times[-1] if self.processing_times else 0
info_text = f"FPS: {current_fps:.1f} | Faces: {len(results)}"
cv2.putText(processed_frame, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 写入输出
if out:
out.write(processed_frame)
# 显示预览
cv2.imshow('Real-time Face Recognition', processed_frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 清理资源
cap.release()
if out:
out.release()
cv2.destroyAllWindows()
print("✅ 摄像头处理结束")
# 使用示例
def main():
# 创建视频识别系统
video_system = VideoFaceRecognition(use_gpu=True)
# 设置质量阈值(可根据实际情况调整)
video_system.set_quality_thresholds(
clarity_threshold=50.0, # 清晰度阈值,可能需要根据你的视频调整
min_face_size=40
)
# 设置目标人脸(可选)
target_image = "test_data/register/sy.jpg"
if os.path.exists(target_image):
video_system.set_target_face(target_image, "目标人物")
# 选择处理模式
print("请选择处理模式:")
print("1. 处理视频文件")
print("2. 实时摄像头")
choice = input("请输入选择 (1 或 2): ").strip()
if choice == "1":
# 处理视频文件
video_path = "test_data/video/video_1.mp4"
output_path = "test_data/output_video/video_5_quality.mp4"
# 性能优化:跳帧处理
skip_frames = 1
video_system.process_video_file(
video_path=video_path,
output_path=output_path,
skip_frames=skip_frames,
show_preview=True
)
elif choice == "2":
# 实时摄像头
output_path = "webcam_recording.mp4"
video_system.process_webcam(
camera_id=0,
output_path=output_path
)
else:
print("❌ 无效选择")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,439 @@
# video_face_recognition.py
import cv2
import numpy as np
import time
from insightface.app import FaceAnalysis
from typing import List, Dict, Tuple, Optional
import os
import glob
#黑白名单
class VideoFaceRecognition:
"""
视频人脸识别系统
支持实时视频流和视频文件处理
支持黑名单和白名单模式
"""
def __init__(self, model_name: str = 'buffalo_l', use_gpu: bool = True):
# 初始化人脸识别模型
self.app = FaceAnalysis(name=model_name)
self.app.prepare(
ctx_id=0 if use_gpu else -1,
det_thresh=0.39,
det_size=(640, 640)
)
# 名单相关变量
self.list_mode = "blacklist" # "blacklist" 或 "whitelist"
self.registered_faces = {} # {name: embedding}
self.similarity_threshold = 0.3
# 性能统计
self.frame_count = 0
self.processing_times = []
print(f"✅ 视频人脸识别系统初始化完成 - GPU: {use_gpu}")
def set_list_mode(self, mode: str):
"""设置名单模式"""
if mode.lower() in ["blacklist", "whitelist"]:
self.list_mode = mode.lower()
print(f"✅ 名单模式设置为: {self.list_mode}")
else:
print("❌ 无效的名单模式,请使用 'blacklist''whitelist'")
def load_registered_faces(self, register_dir: str):
"""
从目录加载注册的人脸图片
文件名(去掉后缀)即为人的名字
"""
if not os.path.exists(register_dir):
print(f"❌ 注册目录不存在: {register_dir}")
return False
# 支持的图片格式
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp']
image_files = []
for ext in image_extensions:
image_files.extend(glob.glob(os.path.join(register_dir, ext)))
image_files.extend(glob.glob(os.path.join(register_dir, ext.upper())))
if not image_files:
print(f"❌ 在目录 {register_dir} 中未找到图片文件")
return False
loaded_count = 0
for image_path in image_files:
# 获取文件名(不含扩展名)作为人名
person_name = os.path.splitext(os.path.basename(image_path))[0]
# 读取图片并提取人脸特征
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取图片: {image_path}")
continue
faces = self.app.get(img)
if not faces:
print(f"❌ 图片中未检测到人脸: {image_path}")
continue
# 使用第一张检测到的人脸
self.registered_faces[person_name] = faces[0].embedding
loaded_count += 1
print(f"✅ 加载注册人脸: {person_name}")
print(f"🎉 成功加载 {loaded_count} 张注册人脸")
return loaded_count > 0
def find_best_match(self, embedding: np.ndarray) -> Tuple[Optional[str], float]:
"""
在注册人脸中查找最佳匹配
返回: (匹配的人名, 相似度)
"""
if not self.registered_faces:
return None, 0.0
best_similarity = 0.0
best_name = None
# 归一化查询嵌入
query_emb = embedding / np.linalg.norm(embedding)
for name, registered_embedding in self.registered_faces.items():
# 归一化注册嵌入
reg_emb = registered_embedding / np.linalg.norm(registered_embedding)
# 计算余弦相似度
similarity = float(np.dot(query_emb, reg_emb))
if similarity > best_similarity:
best_similarity = similarity
best_name = name
return best_name, best_similarity
def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, List[Dict]]:
"""
处理单帧图像
返回: (处理后的帧, 识别结果列表)
"""
start_time = time.time()
# 人脸检测和识别
faces = self.app.get(frame)
results = []
for face in faces:
# 查找最佳匹配
best_name, similarity = self.find_best_match(face.embedding)
# 根据名单模式判断是否匹配
if self.list_mode == "blacklist":
# 黑名单模式:在黑名单中即为匹配(需要关注)
is_match = best_name is not None and similarity >= self.similarity_threshold
else: # whitelist
# 白名单模式:在白名单中即为匹配(允许通过)
is_match = best_name is not None and similarity >= self.similarity_threshold
result = {
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'best_match': best_name,
'is_match': is_match,
'gender': 'Male' if face.gender == 1 else 'Female',
'age': int(face.age),
'det_score': float(face.det_score)
}
results.append(result)
# 在帧上绘制结果
frame = self._draw_detection(frame, result)
# 性能统计
processing_time = (time.time() - start_time) * 1000
self.processing_times.append(processing_time)
self.frame_count += 1
return frame, results
def _draw_detection(self, frame: np.ndarray, result: Dict) -> np.ndarray:
"""在帧上绘制检测结果"""
bbox = result['bbox']
similarity = result['similarity']
is_match = result['is_match']
best_match = result['best_match']
# 选择颜色 - 根据名单模式
if self.list_mode == "blacklist":
# 黑名单模式:匹配(在黑名单中)显示红色,不匹配显示绿色
color = (0, 0, 255) if is_match else (0, 255, 0) # 红色-黑名单, 绿色-正常
else: # whitelist
# 白名单模式:匹配(在白名单中)显示绿色,不匹配显示红色
color = (0, 255, 0) if is_match else (0, 0, 255) # 绿色-白名单, 红色-陌生人
# 绘制人脸框
x1, y1, x2, y2 = bbox
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# 构建显示文本
if best_match and similarity >= self.similarity_threshold:
name_text = f"{best_match}: {similarity:.3f}"
else:
name_text = f"Unknown: {similarity:.3f}"
# 添加名单状态
status = "MATCH" if is_match else "NO MATCH"
text = f"{status} | {name_text}"
# 文本背景
text_size = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2)[0]
cv2.rectangle(frame, (x1, y1 - text_size[1] - 10),
(x1 + text_size[0], y1), color, -1)
# 文本
cv2.putText(frame, text, (x1, y1 - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
# 详细信息
info_text = f"{result['gender']}/{result['age']}"
cv2.putText(frame, info_text, (x1, y2 + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
return frame
def process_video_file(self, video_path: str, output_path: str = None,
skip_frames: int = 0, show_preview: bool = True):
"""
处理视频文件
Args:
video_path: 输入视频路径
output_path: 输出视频路径
skip_frames: 跳帧数,用于提高处理速度
show_preview: 是否显示实时预览
"""
if not os.path.exists(video_path):
print(f"❌ 视频文件不存在: {video_path}")
return
# 打开视频文件
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"📹 视频信息: {width}x{height}, {fps:.1f}FPS, 总帧数: {total_frames}")
print(f"🎯 当前模式: {self.list_mode}, 注册人脸数: {len(self.registered_faces)}")
# 设置输出视频
if output_path:
# 确保输出目录存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps / (skip_frames + 1), (width, height))
else:
out = None
# 处理视频帧
frame_index = 0
processed_frames = 0
start_time = time.time()
print("🚀 开始处理视频...")
while True:
ret, frame = cap.read()
if not ret:
break
# 跳帧处理
if skip_frames > 0 and frame_index % (skip_frames + 1) != 0:
frame_index += 1
continue
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 写入输出视频
if out:
out.write(processed_frame)
# 显示预览
if show_preview:
# 添加性能信息
fps_text = f"Frame: {frame_index}/{total_frames} | Faces: {len(results)} | Mode: {self.list_mode}"
cv2.putText(processed_frame, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 添加名单统计
match_count = sum(1 for r in results if r['is_match'])
list_text = f"Match: {match_count}/{len(results)}"
cv2.putText(processed_frame, list_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow('Video Face Recognition', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_index += 1
processed_frames += 1
# 进度显示
if frame_index % 30 == 0:
progress = (frame_index / total_frames) * 100
print(f"📊 处理进度: {progress:.1f}% ({frame_index}/{total_frames})")
# 清理资源
cap.release()
if out:
out.release()
if show_preview:
cv2.destroyAllWindows()
# 性能统计
total_time = time.time() - start_time
avg_processing_time = np.mean(self.processing_times) if self.processing_times else 0
print(f"\n🎉 视频处理完成!")
print(f"📊 性能统计:")
print(f" 总处理帧数: {processed_frames}")
print(f" 总耗时: {total_time:.1f}")
print(f" 平均每帧: {avg_processing_time:.1f}ms")
print(f" 实际FPS: {processed_frames / total_time:.1f}")
if output_path:
print(f" 输出视频: {output_path}")
def process_webcam(self, camera_id: int = 0, output_path: str = None):
"""
处理摄像头实时视频流
"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"❌ 无法打开摄像头 {camera_id}")
return
# 设置摄像头分辨率(可选)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置输出视频
if output_path:
# 确保输出目录存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
else:
out = None
print(f"🎥 开始摄像头实时识别 - 模式: {self.list_mode} (按 'q' 退出)...")
print(f"📋 注册人脸数: {len(self.registered_faces)}")
while True:
ret, frame = cap.read()
if not ret:
print("❌ 无法读取摄像头帧")
break
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 添加实时信息
current_fps = 1000 / self.processing_times[-1] if self.processing_times else 0
info_text = f"FPS: {current_fps:.1f} | Faces: {len(results)} | Mode: {self.list_mode}"
cv2.putText(processed_frame, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 添加名单统计
match_count = sum(1 for r in results if r['is_match'])
list_text = f"Match: {match_count}/{len(results)}"
cv2.putText(processed_frame, list_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 写入输出
if out:
out.write(processed_frame)
# 显示预览
cv2.imshow('Real-time Face Recognition', processed_frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 清理资源
cap.release()
if out:
out.release()
cv2.destroyAllWindows()
print("✅ 摄像头处理结束")
# 使用示例
def main():
# 创建视频识别系统
video_system = VideoFaceRecognition(use_gpu=True)
# 设置名单模式
# video_system.set_list_mode("blacklist") # 黑名单模式
video_system.set_list_mode("whitelist") # 白名单模式
# 加载注册人脸
register_dir = "test_data/register" # 注册图片目录
if os.path.exists(register_dir):
video_system.load_registered_faces(register_dir)
else:
print(f"⚠️ 注册目录不存在: {register_dir}")
# # 选择处理模式
# print("请选择处理模式:")
# print("1. 处理视频文件")
# print("2. 实时摄像头")
#
# choice = input("请输入选择 (1 或 2): ").strip()
choice = "1"
if choice == "1":
# 处理视频文件
video_path = "test_data/video/video_1.mp4"
output_path = "test_data/output_video/video_1_white.mp4"
# 性能优化:跳帧处理
skip_frames = 1 # 每2帧处理1帧提高速度
video_system.process_video_file(
video_path=video_path,
output_path=output_path,
skip_frames=skip_frames,
show_preview=True
)
elif choice == "2":
# 实时摄像头
output_path = "webcam_recording.mp4" # 可选:保存录制
video_system.process_webcam(
camera_id=0,
output_path=output_path
)
else:
print("❌ 无效选择")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,571 @@
# video_face_recognition.py
import cv2
import numpy as np
import time
from insightface.app import FaceAnalysis
from typing import List, Dict, Tuple
import os
from sympy import false
#改进后的人脸质量显示
class VideoFaceRecognition:
"""
视频人脸识别系统
支持实时视频流和视频文件处理
"""
def __init__(self, model_name: str = 'buffalo_l', use_gpu: bool = True):
# 质量阈值设置
self.det_size = 640 # 320快速 640中等 1280慢
self.det_threshold = 0.7 # 人脸置信度
self.clarity_threshold = 1000.0 # 清晰度阈值,低于此值认为人脸模糊
self.min_face_size = 30 # 最小人脸像素尺寸
self.pitch_threshold = 30 # 人脸置信度
self.yaw_threshold = 20 # 人脸置信度
self.quality_threshold = 0.6 # 质量得分阈值
self.similarity_threshold = 0.1
# 初始化人脸识别模型
self.app = FaceAnalysis(name=model_name)
self.app.prepare(
ctx_id=0 if use_gpu else -1,
det_thresh=self.det_threshold,
det_size=(640, 640)
)
self.target_embedding = None
self.target_id = None
# 性能统计
self.frame_count = 0
self.processing_times = []
print(f"✅ 视频人脸识别系统初始化完成 - GPU: {use_gpu}")
def set_target_face(self, image_path: str, person_id: str = "target") -> bool:
"""设置目标人脸"""
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取目标图像: {image_path}")
return False
faces = self.app.get(img)
if not faces:
print(f"❌ 目标图像中未检测到人脸: {image_path}")
return False
self.target_embedding = faces[0].embedding
self.target_id = person_id
print(f"✅ 目标人脸设置: {person_id}")
return True
def calculate_clarity(self, face_region: np.ndarray) -> float:
"""
计算人脸区域的清晰度/模糊度
使用拉普拉斯方差方法:值越高表示图像越清晰
"""
if len(face_region.shape) == 3:
gray = cv2.cvtColor(face_region, cv2.COLOR_BGR2GRAY)
else:
gray = face_region
# 计算拉普拉斯算子的方差
laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
return laplacian_var
def is_face_quality_acceptable(self, face, frame: np.ndarray) -> Tuple[bool, Dict]:
"""
综合判断人脸质量是否可接受
返回: (是否可接受, 质量指标字典)
"""
quality_metrics = {}
is_acceptable = True
# 1. 检测置信度
quality_metrics['det_score'] = float(face.det_score)
# 2. 人脸姿态角度
if hasattr(face, 'pose') and face.pose is not None:
pitch, yaw, roll = face.pose
quality_metrics['pitch'] = float(pitch)
quality_metrics['yaw'] = float(yaw)
quality_metrics['roll'] = float(roll)
else:
quality_metrics['pitch'] = 100.0
quality_metrics['yaw'] = 100.0
quality_metrics['roll'] = 100.0
# 3. 人脸边界框信息
bbox = face.bbox
x1, y1, x2, y2 = bbox.astype(int)
width = x2 - x1
height = y2 - y1
quality_metrics['bbox_width'] = width
quality_metrics['bbox_height'] = height
quality_metrics['bbox_area'] = width * height
quality_metrics['aspect_ratio'] = width / height if height > 0 else 0
# 4. 图像清晰度检测
# 提取人脸区域
h, w = frame.shape[:2]
x1_clip = max(0, x1)
y1_clip = max(0, y1)
x2_clip = min(w, x2)
y2_clip = min(h, y2)
if x2_clip > x1_clip and y2_clip > y1_clip:
face_region = frame[y1_clip:y2_clip, x1_clip:x2_clip]
clarity_score = self.calculate_clarity(face_region)
quality_metrics['clarity_score'] = clarity_score
else:
quality_metrics['clarity_score'] = 0.0
# 5. 综合质量评分
base_score = quality_metrics['det_score']
# 清晰度惩罚
clarity_penalty = 0.0
if quality_metrics['clarity_score'] < self.clarity_threshold:
clarity_penalty = 0.3 # 清晰度不足严重惩罚
is_acceptable = False
# 姿态惩罚
pose_penalty = 0.0
if abs(quality_metrics['yaw']) > self.yaw_threshold:
pose_penalty += 0.2
is_acceptable = False
if abs(quality_metrics['pitch']) > self.pitch_threshold:
pose_penalty += 0.2
is_acceptable = False
# 尺寸惩罚
size_penalty = 0.0
# if quality_metrics['bbox_area'] < (self.min_face_size ** 2):
# size_penalty = 0.2
if min(width, height) < self.min_face_size:
is_acceptable = False
size_penalty = 0.2
quality_metrics['quality_score'] = max(0.1, base_score - clarity_penalty - pose_penalty - size_penalty)
# # 判断是否可接受
# is_acceptable = (
# quality_metrics['det_score'] > 0.5 and # 基础检测置信度
# quality_metrics['clarity_score'] >= self.clarity_threshold and # 清晰度要求
# quality_metrics['bbox_area'] >= (self.min_face_size ** 2) and # 最小尺寸要求
# abs(quality_metrics['yaw']) < 60 and # 偏航角限制
# abs(quality_metrics['pitch']) < 45 # 俯仰角限制
# )
return is_acceptable, quality_metrics
def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, List[Dict]]:
"""
处理单帧图像
返回: (处理后的帧, 识别结果列表)
"""
start_time = time.time()
# 人脸检测和识别
faces = self.app.get(frame)
results = []
for face in faces:
# 检查人脸质量是否可接受
is_acceptable, quality_metrics = self.is_face_quality_acceptable(face, frame)
similarity = 0.0
# 只有在人脸质量可接受时才计算相似度
if is_acceptable and self.target_embedding is not None:
emb1 = face.embedding / np.linalg.norm(face.embedding)
emb2 = self.target_embedding / np.linalg.norm(self.target_embedding)
similarity = float(np.dot(emb1, emb2))
result = {
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'is_match': similarity >= self.similarity_threshold,
'gender': 'Male' if face.gender == 1 else 'Female',
'age': int(face.age),
'det_score': float(face.det_score),
'quality_metrics': quality_metrics,
'is_acceptable': is_acceptable # 新增:是否可接受标志
}
results.append(result)
# 在帧上绘制结果
frame = self._draw_detection(frame, result)
# 性能统计
processing_time = (time.time() - start_time) * 1000
self.processing_times.append(processing_time)
self.frame_count += 1
return frame, results
def _draw_detection(self, frame: np.ndarray, result: Dict) -> np.ndarray:
"""在帧上绘制检测结果和质量信息"""
bbox = result['bbox']
similarity = result['similarity']
is_match = result['is_match']
is_acceptable = result['is_acceptable']
quality_metrics = result['quality_metrics']
# 选择颜色
if not is_acceptable:
color = (128, 128, 128) # 灰色 - 质量不可接受
elif is_match:
color = (0, 255, 0) # 绿色 - 匹配
else:
color = (0, 0, 255) # 红色 - 不匹配
# 绘制人脸框
x1, y1, x2, y2 = bbox
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# 准备显示文本 - 只保留关键信息
text_lines = []
# 第一行:匹配状态
if not is_acceptable:
text_lines.append("LOW QUALITY")
elif self.target_id:
status = f"MATCH: {similarity:.3f}" if is_match else f"NO MATCH: {similarity:.3f}"
text_lines.append(status)
else:
text_lines.append(f"Similarity: {similarity:.3f}")
# 第二行:质量得分(根据阈值显示颜色)
text_lines.append(f"Quality: {quality_metrics['quality_score']:.3f}")
# 第三行:检测得分
text_lines.append(f"DetScore: {quality_metrics['det_score']:.3f}")
# 第四行:清晰度
text_lines.append(f"Clarity: {quality_metrics['clarity_score']:.1f}")
# 第五行:姿态角度
text_lines.append(f"Pitch: {quality_metrics['pitch']:.1f}°")
text_lines.append(f"Yaw: {quality_metrics['yaw']:.1f}°")
# text_lines.append(f"Roll: {quality_metrics['roll']:.1f}°")
text_lines.append(f"Width: {quality_metrics['bbox_width']:.1f}")
text_lines.append(f"Height: {quality_metrics['bbox_height']:.1f}")
# 计算文本区域大小
max_text_width = 0
total_text_height = 0
line_heights = []
for line in text_lines:
(text_width, text_height), baseline = cv2.getTextSize(line, cv2.FONT_HERSHEY_SIMPLEX, 0.4, 1)
max_text_width = max(max_text_width, text_width)
line_heights.append(text_height + baseline)
total_text_height += text_height + baseline + 2
# 绘制文本背景
bg_x1 = x1
bg_y1 = y1 - total_text_height - 10
bg_x2 = x1 + max_text_width + 10
bg_y2 = y1
# 如果背景超出图像顶部,调整到框下方
if bg_y1 < 0:
bg_y1 = y2
bg_y2 = y2 + total_text_height + 10
# 绘制半透明背景
overlay = frame.copy()
cv2.rectangle(overlay, (bg_x1, bg_y1), (bg_x2, bg_y2), (0, 0, 0), -1)
alpha = 0.6
cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame)
# 绘制文本
current_y = bg_y1 + 15
for i, line in enumerate(text_lines):
# 根据内容选择颜色 - 按照你的要求简化颜色规则
if i == 0: # 状态行
if not is_acceptable:
text_color = (128, 128, 128) # 灰色 - 质量差
elif is_match:
text_color = (0, 255, 0) # 绿色 - 匹配
else:
text_color = (0, 0, 255) # 红色 - 不匹配
# elif i == 1: # 质量得分行
# # 质量得分:低于阈值红色,大于等于阈值绿色
# if quality_metrics['quality_score'] >= self.quality_threshold:
# text_color = (0, 255, 0) # 绿色 - 高质量
# else:
# text_color = (0, 0, 255) # 红色 - 低质量
elif i == 3: # 清晰度行
# 清晰度:低于阈值红色,大于等于阈值绿色
if quality_metrics['clarity_score'] >= self.clarity_threshold:
text_color = (255, 255, 255)
else:
text_color = (0, 0, 255)
elif i == 4: # pitch
if abs(quality_metrics['pitch']) > self.pitch_threshold:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 5: # yaw
if abs(quality_metrics['yaw']) > self.yaw_threshold:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 6: #
if quality_metrics['bbox_width'] < self.min_face_size:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 7: #
if quality_metrics['bbox_height'] < self.min_face_size:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
else:
text_color = (255, 255, 255) # 白色 - 其他信息
cv2.putText(frame, line, (x1 + 5, current_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, text_color, 1)
current_y += line_heights[i]
return frame
def set_quality_thresholds(self, clarity_threshold: float = None,
quality_threshold: float = None,
min_face_size: int = None):
"""设置质量阈值"""
if clarity_threshold is not None:
self.clarity_threshold = clarity_threshold
if quality_threshold is not None:
self.quality_threshold = quality_threshold
if min_face_size is not None:
self.min_face_size = min_face_size
print(
f"✅ 质量阈值更新 - 清晰度: {self.clarity_threshold}, 质量得分: {self.quality_threshold}, 最小尺寸: {self.min_face_size}")
def process_video_file(self, video_path: str, output_path: str = None,
skip_frames: int = 0, show_preview: bool = True):
"""
处理视频文件
Args:
video_path: 输入视频路径
output_path: 输出视频路径
skip_frames: 跳帧数,用于提高处理速度
show_preview: 是否显示实时预览
"""
if not os.path.exists(video_path):
print(f"❌ 视频文件不存在: {video_path}")
return
# 打开视频文件
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"📹 视频信息: {width}x{height}, {fps:.1f}FPS, 总帧数: {total_frames}")
# 设置输出视频
if output_path:
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps / (skip_frames + 1), (width, height))
else:
out = None
# 处理视频帧
frame_index = 0
processed_frames = 0
start_time = time.time()
print("🚀 开始处理视频...")
while True:
ret, frame = cap.read()
if not ret:
break
# 跳帧处理
if skip_frames > 0 and frame_index % (skip_frames + 1) != 0:
frame_index += 1
continue
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 写入输出视频
if out:
out.write(processed_frame)
# 显示预览
if show_preview:
# 添加性能信息
fps_text = f"Frame: {frame_index}/{total_frames} | Faces: {len(results)}"
cv2.putText(processed_frame, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow('Video Face Recognition', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_index += 1
processed_frames += 1
# 进度显示
if frame_index % 30 == 0:
progress = (frame_index / total_frames) * 100
print(f"📊 处理进度: {progress:.1f}% ({frame_index}/{total_frames})")
# 清理资源
cap.release()
if out:
out.release()
if show_preview:
cv2.destroyAllWindows()
# 性能统计
total_time = time.time() - start_time
avg_processing_time = np.mean(self.processing_times) if self.processing_times else 0
print(f"\n🎉 视频处理完成!")
print(f"📊 性能统计:")
print(f" 总处理帧数: {processed_frames}")
print(f" 总耗时: {total_time:.1f}")
print(f" 平均每帧: {avg_processing_time:.1f}ms")
print(f" 实际FPS: {processed_frames / total_time:.1f}")
if output_path:
print(f" 输出视频: {output_path}")
def process_webcam(self, camera_id: int = 0, output_path: str = None):
"""
处理摄像头实时视频流
"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"❌ 无法打开摄像头 {camera_id}")
return
# 设置摄像头分辨率(可选)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置输出视频
if output_path:
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
else:
out = None
print("🎥 开始摄像头实时识别 (按 'q' 退出)...")
while True:
ret, frame = cap.read()
if not ret:
print("❌ 无法读取摄像头帧")
break
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 添加实时信息
current_fps = 1000 / self.processing_times[-1] if self.processing_times else 0
info_text = f"FPS: {current_fps:.1f} | Faces: {len(results)}"
cv2.putText(processed_frame, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 写入输出
if out:
out.write(processed_frame)
# 显示预览
cv2.imshow('Real-time Face Recognition', processed_frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 清理资源
cap.release()
if out:
out.release()
cv2.destroyAllWindows()
print("✅ 摄像头处理结束")
# 使用示例
def main():
# 创建视频识别系统
video_system = VideoFaceRecognition(use_gpu=True)
# 设置质量阈值(可根据实际情况调整)
video_system.set_quality_thresholds(
clarity_threshold=1000.0, # 清晰度阈值
quality_threshold=0.6, # 质量得分阈值
min_face_size=30
)
# 设置目标人脸(可选)
target_image = "test_data/register/sy.jpg"
if os.path.exists(target_image):
video_system.set_target_face(target_image, "目标人物")
# # 选择处理模式
# print("请选择处理模式:")
# print("1. 处理视频文件")
# print("2. 实时摄像头")
#
# choice = input("请输入选择 (1 或 2): ").strip()
choice = "1"
if choice == "1":
# 处理视频文件
video_path = "test_data/video/video_1.mp4"
output_path = "test_data/output_video/video_6_quality.mp4"
# 性能优化:跳帧处理
skip_frames = 1
video_system.process_video_file(
video_path=video_path,
output_path=output_path,
skip_frames=skip_frames,
show_preview=True
)
elif choice == "2":
# 实时摄像头
output_path = "webcam_recording.mp4"
video_system.process_webcam(
camera_id=0,
output_path=output_path
)
else:
print("❌ 无效选择")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,684 @@
# video_face_recognition.py
import cv2
import numpy as np
import time
from insightface.app import FaceAnalysis
from typing import List, Dict, Tuple, Optional
import os
import glob
#改进后的人脸质量显示
class VideoFaceRecognition:
"""
视频人脸识别系统
支持实时视频流和视频文件处理
支持黑名单和白名单模式
"""
# buffalo_l buffalo_sc
def __init__(self, model_name: str = 'buffalo_l', use_gpu: bool = True):
# 质量阈值设置
self.det_size = 640 # 320快速 640中等 1280慢
#white
self.list_mode = "whitelist" # "blacklist" 或 "whitelist"
self.det_threshold = 0.7 # 人脸置信度
self.clarity_threshold = 1000.0 # 清晰度阈值,低于此值认为人脸模糊
self.min_face_size = 30 # 最小人脸像素尺寸
self.pitch_threshold = 40 #
self.yaw_threshold = 40 #
self.quality_threshold = 0.6 # 质量得分阈值
self.similarity_threshold = 0.13
# #black
# self.list_mode = "blacklist" # "blacklist" 或 "whitelist"
# self.det_threshold = 0.5 # 人脸置信度
# self.clarity_threshold = 100.0 # 清晰度阈值,低于此值认为人脸模糊
# self.min_face_size = 20 # 最小人脸像素尺寸
# self.pitch_threshold = 90 #
# self.yaw_threshold = 90 #
# self.quality_threshold = 0.6 # 质量得分阈值
# self.similarity_threshold = 0.3
# 初始化人脸识别模型
self.app = FaceAnalysis(name=model_name)
self.app.prepare(
ctx_id=0 if use_gpu else -1,
det_thresh=self.det_threshold,
det_size=(self.det_size,self. det_size)
)
# 名单相关变量
self.registered_faces = {} # {name: embedding}
# 性能统计
self.frame_count = 0
self.processing_times = []
print(f"✅ 视频人脸识别系统初始化完成 - GPU: {use_gpu}")
def set_list_mode(self, mode: str):
"""设置名单模式"""
if mode.lower() in ["blacklist", "whitelist"]:
self.list_mode = mode.lower()
print(f"✅ 名单模式设置为: {self.list_mode}")
else:
print("❌ 无效的名单模式,请使用 'blacklist''whitelist'")
def load_registered_faces(self, register_dir: str):
"""
从目录加载注册的人脸图片
文件名(去掉后缀)即为人的名字
"""
if not os.path.exists(register_dir):
print(f"❌ 注册目录不存在: {register_dir}")
return False
# 支持的图片格式
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp']
image_files = []
for ext in image_extensions:
image_files.extend(glob.glob(os.path.join(register_dir, ext)))
image_files.extend(glob.glob(os.path.join(register_dir, ext.upper())))
if not image_files:
print(f"❌ 在目录 {register_dir} 中未找到图片文件")
return False
loaded_count = 0
for image_path in image_files:
# 获取文件名(不含扩展名)作为人名
person_name = os.path.splitext(os.path.basename(image_path))[0]
# 读取图片并提取人脸特征
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取图片: {image_path}")
continue
faces = self.app.get(img)
if not faces:
print(f"❌ 图片中未检测到人脸: {image_path}")
continue
# 使用第一张检测到的人脸
self.registered_faces[person_name] = faces[0].embedding
loaded_count += 1
print(f"✅ 加载注册人脸: {person_name}")
print(f"🎉 成功加载 {loaded_count} 张注册人脸")
return loaded_count > 0
def find_best_match(self, embedding: np.ndarray) -> Tuple[Optional[str], float]:
"""
在注册人脸中查找最佳匹配
返回: (匹配的人名, 相似度)
"""
if not self.registered_faces:
return None, 0.0
best_similarity = 0.0
best_name = None
# 归一化查询嵌入
query_emb = embedding / np.linalg.norm(embedding)
for name, registered_embedding in self.registered_faces.items():
# 归一化注册嵌入
reg_emb = registered_embedding / np.linalg.norm(registered_embedding)
# 计算余弦相似度
similarity = float(np.dot(query_emb, reg_emb))
if similarity > best_similarity:
best_similarity = similarity
best_name = name
return best_name, best_similarity
def calculate_clarity(self, face_region: np.ndarray) -> float:
"""
计算人脸区域的清晰度/模糊度
使用拉普拉斯方差方法:值越高表示图像越清晰
"""
if len(face_region.shape) == 3:
gray = cv2.cvtColor(face_region, cv2.COLOR_BGR2GRAY)
else:
gray = face_region
# 计算拉普拉斯算子的方差
laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
return laplacian_var
def is_face_quality_acceptable(self, face, frame: np.ndarray) -> Tuple[bool, Dict]:
"""
综合判断人脸质量是否可接受
返回: (是否可接受, 质量指标字典)
"""
quality_metrics = {}
is_acceptable = True
# 1. 检测置信度
quality_metrics['det_score'] = float(face.det_score)
# 2. 人脸姿态角度
if hasattr(face, 'pose') and face.pose is not None:
pitch, yaw, roll = face.pose
quality_metrics['pitch'] = float(pitch)
quality_metrics['yaw'] = float(yaw)
quality_metrics['roll'] = float(roll)
else:
quality_metrics['pitch'] = 100.0
quality_metrics['yaw'] = 100.0
quality_metrics['roll'] = 100.0
# 3. 人脸边界框信息
bbox = face.bbox
x1, y1, x2, y2 = bbox.astype(int)
width = x2 - x1
height = y2 - y1
quality_metrics['bbox_width'] = width
quality_metrics['bbox_height'] = height
quality_metrics['bbox_area'] = width * height
quality_metrics['aspect_ratio'] = width / height if height > 0 else 0
# 4. 图像清晰度检测
# 提取人脸区域
h, w = frame.shape[:2]
x1_clip = max(0, x1)
y1_clip = max(0, y1)
x2_clip = min(w, x2)
y2_clip = min(h, y2)
if x2_clip > x1_clip and y2_clip > y1_clip:
face_region = frame[y1_clip:y2_clip, x1_clip:x2_clip]
clarity_score = self.calculate_clarity(face_region)
quality_metrics['clarity_score'] = clarity_score
else:
quality_metrics['clarity_score'] = 0.0
# 5. 综合质量评分
base_score = quality_metrics['det_score']
# 清晰度惩罚
clarity_penalty = 0.0
if quality_metrics['clarity_score'] < self.clarity_threshold:
clarity_penalty = 0.3 # 清晰度不足严重惩罚
is_acceptable = False
# 姿态惩罚
pose_penalty = 0.0
if abs(quality_metrics['yaw']) > self.yaw_threshold:
pose_penalty += 0.2
is_acceptable = False
if abs(quality_metrics['pitch']) > self.pitch_threshold:
pose_penalty += 0.2
is_acceptable = False
# 尺寸惩罚
size_penalty = 0.0
# if quality_metrics['bbox_area'] < (self.min_face_size ** 2):
# size_penalty = 0.2
if min(width, height) < self.min_face_size:
is_acceptable = False
size_penalty = 0.2
quality_metrics['quality_score'] = max(0.1, base_score - clarity_penalty - pose_penalty - size_penalty)
# # 判断是否可接受
# is_acceptable = (
# quality_metrics['det_score'] > 0.5 and # 基础检测置信度
# quality_metrics['clarity_score'] >= self.clarity_threshold and # 清晰度要求
# quality_metrics['bbox_area'] >= (self.min_face_size ** 2) and # 最小尺寸要求
# abs(quality_metrics['yaw']) < 60 and # 偏航角限制
# abs(quality_metrics['pitch']) < 45 # 俯仰角限制
# )
return is_acceptable, quality_metrics
def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, List[Dict]]:
"""
处理单帧图像
返回: (处理后的帧, 识别结果列表)
"""
start_time = time.time()
# 人脸检测和识别
faces = self.app.get(frame)
results = []
for face in faces:
# 检查人脸质量是否可接受
is_acceptable, quality_metrics = self.is_face_quality_acceptable(face, frame)
# 查找最佳匹配
best_name, similarity = self.find_best_match(face.embedding)
# 根据名单模式判断是否匹配
if self.list_mode == "blacklist":
# 黑名单模式:在黑名单中即为匹配(需要关注)
is_match = best_name is not None and similarity >= self.similarity_threshold
else: # whitelist
# 白名单模式:在白名单中即为匹配(允许通过)
is_match = best_name is not None and similarity >= self.similarity_threshold
result = {
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'best_match': best_name,
'is_match': is_match,
# 'gender': 'Male' if face.gender == 1 else 'Female',
# 'age': int(face.age),
'det_score': float(face.det_score),
'quality_metrics': quality_metrics,
'is_acceptable': is_acceptable # 新增:是否可接受标志
}
results.append(result)
# 在帧上绘制结果
frame = self._draw_detection(frame, result)
# 性能统计
processing_time = (time.time() - start_time) * 1000
self.processing_times.append(processing_time)
self.frame_count += 1
return frame, results
def _draw_detection(self, frame: np.ndarray, result: Dict) -> np.ndarray:
"""在帧上绘制检测结果和质量信息"""
bbox = result['bbox']
similarity = result['similarity']
is_match = result['is_match']
is_acceptable = result['is_acceptable']
quality_metrics = result['quality_metrics']
best_match = result['best_match']
# 选择颜色
if not is_acceptable:
color = (128, 128, 128) # 灰色 - 质量不可接受
else:
# 选择颜色 - 根据名单模式
if self.list_mode == "blacklist":
# 黑名单模式:匹配(在黑名单中)显示红色,不匹配显示绿色
color = (0, 0, 255) if is_match else (0, 255, 0) # 红色-黑名单, 绿色-正常
else: # whitelist
# 白名单模式:匹配(在白名单中)显示绿色,不匹配显示红色
color = (0, 255, 0) if is_match else (0, 0, 255) # 绿色-白名单, 红色-陌生人
# 绘制人脸框
x1, y1, x2, y2 = bbox
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# 准备显示文本 - 只保留关键信息
text_lines = []
# 第一行:匹配状态
if not is_acceptable:
text_lines.append("LOW QUALITY")
else:
status = f"MATCH: {best_match}: {similarity:.3f}" if is_match else f"NO MATCH: {similarity:.3f}"
text_lines.append(status)
# 第二行:质量得分(根据阈值显示颜色)
text_lines.append(f"Quality: {quality_metrics['quality_score']:.3f}")
# 第三行:检测得分
text_lines.append(f"DetScore: {quality_metrics['det_score']:.3f}")
# 第四行:清晰度
text_lines.append(f"Clarity: {quality_metrics['clarity_score']:.1f}")
# 第五行:姿态角度
text_lines.append(f"Pitch: {quality_metrics['pitch']:.1f}°")
text_lines.append(f"Yaw: {quality_metrics['yaw']:.1f}°")
# text_lines.append(f"Roll: {quality_metrics['roll']:.1f}°")
text_lines.append(f"Width: {quality_metrics['bbox_width']:.1f}")
text_lines.append(f"Height: {quality_metrics['bbox_height']:.1f}")
# 计算文本区域大小
max_text_width = 0
total_text_height = 0
line_heights = []
for line in text_lines:
(text_width, text_height), baseline = cv2.getTextSize(line, cv2.FONT_HERSHEY_SIMPLEX, 0.4, 1)
max_text_width = max(max_text_width, text_width)
line_heights.append(text_height + baseline)
total_text_height += text_height + baseline + 2
# 绘制文本背景
bg_x1 = x1
bg_y1 = y1 - total_text_height - 10
bg_x2 = x1 + max_text_width + 10
bg_y2 = y1
# 如果背景超出图像顶部,调整到框下方
if bg_y1 < 0:
bg_y1 = y2
bg_y2 = y2 + total_text_height + 10
# 绘制半透明背景
overlay = frame.copy()
cv2.rectangle(overlay, (bg_x1, bg_y1), (bg_x2, bg_y2), (0, 0, 0), -1)
alpha = 0.6
cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame)
# 绘制文本
current_y = bg_y1 + 15
for i, line in enumerate(text_lines):
# 根据内容选择颜色 - 按照你的要求简化颜色规则
if i == 0: # 状态行
if not is_acceptable:
text_color = (128, 128, 128) # 灰色 - 质量差
elif is_match:
text_color = (0, 255, 0) # 绿色 - 匹配
else:
text_color = (0, 0, 255) # 红色 - 不匹配
# elif i == 1: # 质量得分行
# # 质量得分:低于阈值红色,大于等于阈值绿色
# if quality_metrics['quality_score'] >= self.quality_threshold:
# text_color = (0, 255, 0) # 绿色 - 高质量
# else:
# text_color = (0, 0, 255) # 红色 - 低质量
elif i == 3: # 清晰度行
# 清晰度:低于阈值红色,大于等于阈值绿色
if quality_metrics['clarity_score'] >= self.clarity_threshold:
text_color = (255, 255, 255)
else:
text_color = (0, 0, 255)
elif i == 4: # pitch
if abs(quality_metrics['pitch']) > self.pitch_threshold:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 5: # yaw
if abs(quality_metrics['yaw']) > self.yaw_threshold:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 6: #
if quality_metrics['bbox_width'] < self.min_face_size:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 7: #
if quality_metrics['bbox_height'] < self.min_face_size:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
else:
text_color = (255, 255, 255) # 白色 - 其他信息
cv2.putText(frame, line, (x1 + 5, current_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, text_color, 1)
current_y += line_heights[i]
return frame
def set_quality_thresholds(self, clarity_threshold: float = None,
quality_threshold: float = None,
min_face_size: int = None):
"""设置质量阈值"""
if clarity_threshold is not None:
self.clarity_threshold = clarity_threshold
if quality_threshold is not None:
self.quality_threshold = quality_threshold
if min_face_size is not None:
self.min_face_size = min_face_size
print(
f"✅ 质量阈值更新 - 清晰度: {self.clarity_threshold}, 质量得分: {self.quality_threshold}, 最小尺寸: {self.min_face_size}")
def process_video_file(self, video_path: str, output_path: str = None,
skip_frames: int = 0, show_preview: bool = True):
"""
处理视频文件
Args:
video_path: 输入视频路径
output_path: 输出视频路径
skip_frames: 跳帧数,用于提高处理速度
show_preview: 是否显示实时预览
"""
if not os.path.exists(video_path):
print(f"❌ 视频文件不存在: {video_path}")
return
# 打开视频文件
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"📹 视频信息: {width}x{height}, {fps:.1f}FPS, 总帧数: {total_frames}")
print(f"🎯 当前模式: {self.list_mode}, 注册人脸数: {len(self.registered_faces)}")
# 设置输出视频
if output_path:
# 确保输出目录存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps / (skip_frames), (width, height))
else:
out = None
# 处理视频帧
frame_index = 0
processed_frames = 0
start_time = time.time()
print("🚀 开始处理视频...")
while True:
ret, frame = cap.read()
if not ret:
break
# 跳帧处理
if skip_frames > 0 and frame_index % (skip_frames + 1) != 0:
frame_index += 1
continue
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 写入输出视频
if out:
out.write(processed_frame)
# 显示预览
if show_preview:
# 添加性能信息
fps_text = f"Frame: {frame_index}/{total_frames} | Faces: {len(results)} | Mode: {self.list_mode}"
cv2.putText(processed_frame, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 添加名单统计
match_count = sum(1 for r in results if r['is_match'])
list_text = f"Match: {match_count}/{len(results)}"
cv2.putText(processed_frame, list_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow('Video Face Recognition', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_index += 1
processed_frames += 1
# 进度显示
if frame_index % 30 == 0:
progress = (frame_index / total_frames) * 100
print(f"📊 处理进度: {progress:.1f}% ({frame_index}/{total_frames})")
# 清理资源
cap.release()
if out:
out.release()
if show_preview:
cv2.destroyAllWindows()
# 性能统计
total_time = time.time() - start_time
avg_processing_time = np.mean(self.processing_times) if self.processing_times else 0
print(f"\n🎉 视频处理完成!")
print(f"📊 性能统计:")
print(f" 总处理帧数: {processed_frames}")
print(f" 总耗时: {total_time:.1f}")
print(f" 平均每帧: {avg_processing_time:.1f}ms")
print(f" 实际FPS: {processed_frames / total_time:.1f}")
if output_path:
print(f" 输出视频: {output_path}")
def process_webcam(self, camera_id: int = 0, output_path: str = None):
"""
处理摄像头实时视频流
"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"❌ 无法打开摄像头 {camera_id}")
return
# 设置摄像头分辨率(可选)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置输出视频
if output_path:
# 确保输出目录存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
else:
out = None
print(f"🎥 开始摄像头实时识别 - 模式: {self.list_mode} (按 'q' 退出)...")
print(f"📋 注册人脸数: {len(self.registered_faces)}")
while True:
ret, frame = cap.read()
if not ret:
print("❌ 无法读取摄像头帧")
break
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 添加实时信息
current_fps = 1000 / self.processing_times[-1] if self.processing_times else 0
info_text = f"FPS: {current_fps:.1f} | Faces: {len(results)} | Mode: {self.list_mode}"
cv2.putText(processed_frame, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 添加名单统计
match_count = sum(1 for r in results if r['is_match'])
list_text = f"Match: {match_count}/{len(results)}"
cv2.putText(processed_frame, list_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 写入输出
if out:
out.write(processed_frame)
# 显示预览
cv2.imshow('Real-time Face Recognition', processed_frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 清理资源
cap.release()
if out:
out.release()
cv2.destroyAllWindows()
print("✅ 摄像头处理结束")
# 使用示例
def main():
# 创建视频识别系统
video_system = VideoFaceRecognition(use_gpu=True)
# 设置名单模式
# video_system.set_list_mode("blacklist") # 黑名单模式
# video_system.set_list_mode("whitelist") # 白名单模式
# 加载注册人脸
register_dir = "test_data/register" # 注册图片目录
if os.path.exists(register_dir):
video_system.load_registered_faces(register_dir)
else:
print(f"⚠️ 注册目录不存在: {register_dir}")
#
# # 设置质量阈值(可根据实际情况调整)
# video_system.set_quality_thresholds(
# clarity_threshold=1000.0, # 清晰度阈值
# quality_threshold=0.6, # 质量得分阈值
# min_face_size=30
# )
# # 选择处理模式
# print("请选择处理模式:")
# print("1. 处理视频文件")
# print("2. 实时摄像头")
#
# choice = input("请输入选择 (1 或 2): ").strip()
choice = "1"
if choice == "1":
# 处理视频文件
video_path = "test_data/video/video_2.mp4"
output_path = "test_data/output_video/video_2_white_7_gpu.mp4"
# output_path = "test_data/output_video/video_2_black_2.mp4"
# 性能优化:跳帧处理
skip_frames = 2
video_system.process_video_file(
video_path=video_path,
output_path=output_path,
skip_frames=skip_frames,
show_preview=False
)
elif choice == "2":
# 实时摄像头
output_path = "webcam_recording.mp4"
video_system.process_webcam(
camera_id=0,
output_path=output_path
)
else:
print("❌ 无效选择")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,720 @@
# video_face_recognition.py
import cv2
import numpy as np
import time
from insightface.app import FaceAnalysis
from typing import List, Dict, Tuple, Optional
import os
import glob
# 改进后的人脸质量显示
class VideoFaceRecognition:
"""
视频人脸识别系统
支持实时视频流和视频文件处理
支持黑名单和白名单模式
"""
# buffalo_l buffalo_sc
def __init__(self, model_name: str = 'buffalo_l', use_gpu: bool = True, use_npu: bool = False,
npu_device_id: int = 0):
# 设备配置映射NPU采用华为指定的完整参数
self.DEVICE_CONFIG = {
"cpu": (['CPUExecutionProvider'], -1),
"gpu": (['CUDAExecutionProvider'], 0),
"npu": (
[
(
"CANNExecutionProvider",
{
"device_id": npu_device_id,
"arena_extend_strategy": "kNextPowerOfTwo",
"npu_mem_limit": 16 * 1024 * 1024 * 1024,
"op_select_impl_mode": "high_precision",
"precision_mode": "allow_fp32_to_fp16",
"enable_cann_graph": True,
},
),
"CPUExecutionProvider"
],
npu_device_id
)
}
# 质量阈值设置
self.det_size = 640 # 320快速 640中等 1280慢
# # white
# self.list_mode = "whitelist" # "blacklist" 或 "whitelist"
# self.det_threshold = 0.7 # 人脸置信度
# self.clarity_threshold = 1000.0 # 清晰度阈值,低于此值认为人脸模糊
# self.min_face_size = 30 # 最小人脸像素尺寸
# self.pitch_threshold = 40 #
# self.yaw_threshold = 40 #
# self.quality_threshold = 0.6 # 质量得分阈值
# self.similarity_threshold = 0.13
#black
self.list_mode = "blacklist" # "blacklist" 或 "whitelist"
self.det_threshold = 0.5 # 人脸置信度
self.clarity_threshold = 100.0 # 清晰度阈值,低于此值认为人脸模糊
self.min_face_size = 20 # 最小人脸像素尺寸
self.pitch_threshold = 90 # 角度
self.yaw_threshold = 90 # 角度
self.quality_threshold = 0.6 # 质量得分阈值
self.similarity_threshold = 0.3
# 根据设备类型选择配置
if use_npu:
device_type = "npu"
print(f"✅ 使用NPU设备设备ID: {npu_device_id}")
elif use_gpu:
device_type = "gpu"
print("✅ 使用GPU设备")
else:
device_type = "cpu"
print("✅ 使用CPU设备")
providers, ctx_id = self.DEVICE_CONFIG[device_type]
# 初始化人脸识别模型
self.app = FaceAnalysis(name=model_name, providers=providers)
self.app.prepare(
ctx_id=ctx_id,
det_thresh=self.det_threshold,
det_size=(self.det_size, self.det_size)
)
# 名单相关变量
self.registered_faces = {} # {name: embedding}
# 性能统计
self.frame_count = 0
self.processing_times = []
print(f"✅ 视频人脸识别系统初始化完成 - 设备: {device_type.upper()}")
def set_list_mode(self, mode: str):
"""设置名单模式"""
if mode.lower() in ["blacklist", "whitelist"]:
self.list_mode = mode.lower()
print(f"✅ 名单模式设置为: {self.list_mode}")
else:
print("❌ 无效的名单模式,请使用 'blacklist''whitelist'")
def load_registered_faces(self, register_dir: str):
"""
从目录加载注册的人脸图片
文件名(去掉后缀)即为人的名字
"""
if not os.path.exists(register_dir):
print(f"❌ 注册目录不存在: {register_dir}")
return False
# 支持的图片格式
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp']
image_files = []
for ext in image_extensions:
image_files.extend(glob.glob(os.path.join(register_dir, ext)))
image_files.extend(glob.glob(os.path.join(register_dir, ext.upper())))
if not image_files:
print(f"❌ 在目录 {register_dir} 中未找到图片文件")
return False
loaded_count = 0
for image_path in image_files:
# 获取文件名(不含扩展名)作为人名
person_name = os.path.splitext(os.path.basename(image_path))[0]
# 读取图片并提取人脸特征
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取图片: {image_path}")
continue
faces = self.app.get(img)
if not faces:
print(f"❌ 图片中未检测到人脸: {image_path}")
continue
# 使用第一张检测到的人脸
self.registered_faces[person_name] = faces[0].embedding
loaded_count += 1
print(f"✅ 加载注册人脸: {person_name}")
print(f"🎉 成功加载 {loaded_count} 张注册人脸")
return loaded_count > 0
def find_best_match(self, embedding: np.ndarray) -> Tuple[Optional[str], float]:
"""
在注册人脸中查找最佳匹配
返回: (匹配的人名, 相似度)
"""
if not self.registered_faces:
return None, 0.0
best_similarity = 0.0
best_name = None
# 归一化查询嵌入
query_emb = embedding / np.linalg.norm(embedding)
for name, registered_embedding in self.registered_faces.items():
# 归一化注册嵌入
reg_emb = registered_embedding / np.linalg.norm(registered_embedding)
# 计算余弦相似度
similarity = float(np.dot(query_emb, reg_emb))
if similarity > best_similarity:
best_similarity = similarity
best_name = name
return best_name, best_similarity
def calculate_clarity(self, face_region: np.ndarray) -> float:
"""
计算人脸区域的清晰度/模糊度
使用拉普拉斯方差方法:值越高表示图像越清晰
"""
if len(face_region.shape) == 3:
gray = cv2.cvtColor(face_region, cv2.COLOR_BGR2GRAY)
else:
gray = face_region
# 计算拉普拉斯算子的方差
laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
return laplacian_var
def is_face_quality_acceptable(self, face, frame: np.ndarray) -> Tuple[bool, Dict]:
"""
综合判断人脸质量是否可接受
返回: (是否可接受, 质量指标字典)
"""
quality_metrics = {}
is_acceptable = True
# 1. 检测置信度
quality_metrics['det_score'] = float(face.det_score)
# 2. 人脸姿态角度
if hasattr(face, 'pose') and face.pose is not None:
pitch, yaw, roll = face.pose
quality_metrics['pitch'] = float(pitch)
quality_metrics['yaw'] = float(yaw)
quality_metrics['roll'] = float(roll)
else:
quality_metrics['pitch'] = 100.0
quality_metrics['yaw'] = 100.0
quality_metrics['roll'] = 100.0
# 3. 人脸边界框信息
bbox = face.bbox
x1, y1, x2, y2 = bbox.astype(int)
width = x2 - x1
height = y2 - y1
quality_metrics['bbox_width'] = width
quality_metrics['bbox_height'] = height
quality_metrics['bbox_area'] = width * height
quality_metrics['aspect_ratio'] = width / height if height > 0 else 0
# 4. 图像清晰度检测
# 提取人脸区域
h, w = frame.shape[:2]
x1_clip = max(0, x1)
y1_clip = max(0, y1)
x2_clip = min(w, x2)
y2_clip = min(h, y2)
if x2_clip > x1_clip and y2_clip > y1_clip:
face_region = frame[y1_clip:y2_clip, x1_clip:x2_clip]
clarity_score = self.calculate_clarity(face_region)
quality_metrics['clarity_score'] = clarity_score
else:
quality_metrics['clarity_score'] = 0.0
# 5. 综合质量评分
base_score = quality_metrics['det_score']
# 清晰度惩罚
clarity_penalty = 0.0
if quality_metrics['clarity_score'] < self.clarity_threshold:
clarity_penalty = 0.3 # 清晰度不足严重惩罚
is_acceptable = False
# 姿态惩罚
pose_penalty = 0.0
if abs(quality_metrics['yaw']) > self.yaw_threshold:
pose_penalty += 0.2
is_acceptable = False
if abs(quality_metrics['pitch']) > self.pitch_threshold:
pose_penalty += 0.2
is_acceptable = False
# 尺寸惩罚
size_penalty = 0.0
# if quality_metrics['bbox_area'] < (self.min_face_size ** 2):
# size_penalty = 0.2
if min(width, height) < self.min_face_size:
is_acceptable = False
size_penalty = 0.2
quality_metrics['quality_score'] = max(0.1, base_score - clarity_penalty - pose_penalty - size_penalty)
# # 判断是否可接受
# is_acceptable = (
# quality_metrics['det_score'] > 0.5 and # 基础检测置信度
# quality_metrics['clarity_score'] >= self.clarity_threshold and # 清晰度要求
# quality_metrics['bbox_area'] >= (self.min_face_size ** 2) and # 最小尺寸要求
# abs(quality_metrics['yaw']) < 60 and # 偏航角限制
# abs(quality_metrics['pitch']) < 45 # 俯仰角限制
# )
return is_acceptable, quality_metrics
def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, List[Dict]]:
"""
处理单帧图像
返回: (处理后的帧, 识别结果列表)
"""
start_time = time.time()
# 人脸检测和识别
faces = self.app.get(frame)
results = []
for face in faces:
# 检查人脸质量是否可接受
is_acceptable, quality_metrics = self.is_face_quality_acceptable(face, frame)
# 查找最佳匹配
best_name, similarity = self.find_best_match(face.embedding)
# 根据名单模式判断是否匹配
if self.list_mode == "blacklist":
# 黑名单模式:在黑名单中即为匹配(需要关注)
is_match = best_name is not None and similarity >= self.similarity_threshold
else: # whitelist
# 白名单模式:在白名单中即为匹配(允许通过)
is_match = best_name is not None and similarity >= self.similarity_threshold
result = {
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'best_match': best_name,
'is_match': is_match,
# 'gender': 'Male' if face.gender == 1 else 'Female',
# 'age': int(face.age),
'det_score': float(face.det_score),
'quality_metrics': quality_metrics,
'is_acceptable': is_acceptable # 新增:是否可接受标志
}
results.append(result)
# 在帧上绘制结果
frame = self._draw_detection(frame, result)
# 性能统计
processing_time = (time.time() - start_time) * 1000
self.processing_times.append(processing_time)
self.frame_count += 1
return frame, results
def _draw_detection(self, frame: np.ndarray, result: Dict) -> np.ndarray:
"""在帧上绘制检测结果和质量信息"""
bbox = result['bbox']
similarity = result['similarity']
is_match = result['is_match']
is_acceptable = result['is_acceptable']
quality_metrics = result['quality_metrics']
best_match = result['best_match']
# 选择颜色
if not is_acceptable:
color = (128, 128, 128) # 灰色 - 质量不可接受
else:
# 选择颜色 - 根据名单模式
if self.list_mode == "blacklist":
# 黑名单模式:匹配(在黑名单中)显示红色,不匹配显示绿色
color = (0, 0, 255) if is_match else (0, 255, 0) # 红色-黑名单, 绿色-正常
else: # whitelist
# 白名单模式:匹配(在白名单中)显示绿色,不匹配显示红色
color = (0, 255, 0) if is_match else (0, 0, 255) # 绿色-白名单, 红色-陌生人
# 绘制人脸框
x1, y1, x2, y2 = bbox
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# 准备显示文本 - 只保留关键信息
text_lines = []
# 第一行:匹配状态
if not is_acceptable:
text_lines.append("LOW QUALITY")
else:
status = f"MATCH: {best_match}: {similarity:.3f}" if is_match else f"NO MATCH: {similarity:.3f}"
text_lines.append(status)
# 第二行:质量得分(根据阈值显示颜色)
text_lines.append(f"Quality: {quality_metrics['quality_score']:.3f}")
# 第三行:检测得分
text_lines.append(f"DetScore: {quality_metrics['det_score']:.3f}")
# 第四行:清晰度
text_lines.append(f"Clarity: {quality_metrics['clarity_score']:.1f}")
# 第五行:姿态角度
text_lines.append(f"Pitch: {quality_metrics['pitch']:.1f}°")
text_lines.append(f"Yaw: {quality_metrics['yaw']:.1f}°")
# text_lines.append(f"Roll: {quality_metrics['roll']:.1f}°")
text_lines.append(f"Width: {quality_metrics['bbox_width']:.1f}")
text_lines.append(f"Height: {quality_metrics['bbox_height']:.1f}")
# 计算文本区域大小
max_text_width = 0
total_text_height = 0
line_heights = []
for line in text_lines:
(text_width, text_height), baseline = cv2.getTextSize(line, cv2.FONT_HERSHEY_SIMPLEX, 0.4, 1)
max_text_width = max(max_text_width, text_width)
line_heights.append(text_height + baseline)
total_text_height += text_height + baseline + 2
# 绘制文本背景
bg_x1 = x1
bg_y1 = y1 - total_text_height - 10
bg_x2 = x1 + max_text_width + 10
bg_y2 = y1
# 如果背景超出图像顶部,调整到框下方
if bg_y1 < 0:
bg_y1 = y2
bg_y2 = y2 + total_text_height + 10
# 绘制半透明背景
overlay = frame.copy()
cv2.rectangle(overlay, (bg_x1, bg_y1), (bg_x2, bg_y2), (0, 0, 0), -1)
alpha = 0.6
cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame)
# 绘制文本
current_y = bg_y1 + 15
for i, line in enumerate(text_lines):
# 根据内容选择颜色 - 按照你的要求简化颜色规则
if i == 0: # 状态行
if not is_acceptable:
text_color = (128, 128, 128) # 灰色 - 质量差
elif is_match:
text_color = (0, 255, 0) # 绿色 - 匹配
else:
text_color = (0, 0, 255) # 红色 - 不匹配
# elif i == 1: # 质量得分行
# # 质量得分:低于阈值红色,大于等于阈值绿色
# if quality_metrics['quality_score'] >= self.quality_threshold:
# text_color = (0, 255, 0) # 绿色 - 高质量
# else:
# text_color = (0, 0, 255) # 红色 - 低质量
elif i == 3: # 清晰度行
# 清晰度:低于阈值红色,大于等于阈值绿色
if quality_metrics['clarity_score'] >= self.clarity_threshold:
text_color = (255, 255, 255)
else:
text_color = (0, 0, 255)
elif i == 4: # pitch
if abs(quality_metrics['pitch']) > self.pitch_threshold:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 5: # yaw
if abs(quality_metrics['yaw']) > self.yaw_threshold:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 6: #
if quality_metrics['bbox_width'] < self.min_face_size:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 7: #
if quality_metrics['bbox_height'] < self.min_face_size:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
else:
text_color = (255, 255, 255) # 白色 - 其他信息
cv2.putText(frame, line, (x1 + 5, current_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, text_color, 1)
current_y += line_heights[i]
return frame
def set_quality_thresholds(self, clarity_threshold: float = None,
quality_threshold: float = None,
min_face_size: int = None):
"""设置质量阈值"""
if clarity_threshold is not None:
self.clarity_threshold = clarity_threshold
if quality_threshold is not None:
self.quality_threshold = quality_threshold
if min_face_size is not None:
self.min_face_size = min_face_size
print(
f"✅ 质量阈值更新 - 清晰度: {self.clarity_threshold}, 质量得分: {self.quality_threshold}, 最小尺寸: {self.min_face_size}")
def process_video_file(self, video_path: str, output_path: str = None,
skip_frames: int = 0, show_preview: bool = True):
"""
处理视频文件
Args:
video_path: 输入视频路径
output_path: 输出视频路径
skip_frames: 跳帧数,用于提高处理速度
show_preview: 是否显示实时预览
"""
if not os.path.exists(video_path):
print(f"❌ 视频文件不存在: {video_path}")
return
# 打开视频文件
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"📹 视频信息: {width}x{height}, {fps:.1f}FPS, 总帧数: {total_frames}")
print(f"🎯 当前模式: {self.list_mode}, 注册人脸数: {len(self.registered_faces)}")
# 设置输出视频
if output_path:
# 确保输出目录存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps / (skip_frames), (width, height))
else:
out = None
# 处理视频帧
frame_index = 0
processed_frames = 0
start_time = time.time()
print("🚀 开始处理视频...")
while True:
ret, frame = cap.read()
if not ret:
break
# 跳帧处理
if skip_frames > 0 and frame_index % (skip_frames + 1) != 0:
frame_index += 1
continue
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 写入输出视频
if out:
out.write(processed_frame)
# 显示预览
if show_preview:
# 添加性能信息
fps_text = f"Frame: {frame_index}/{total_frames} | Faces: {len(results)} | Mode: {self.list_mode}"
cv2.putText(processed_frame, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 添加名单统计
match_count = sum(1 for r in results if r['is_match'])
list_text = f"Match: {match_count}/{len(results)}"
cv2.putText(processed_frame, list_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow('Video Face Recognition', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_index += 1
processed_frames += 1
# 进度显示
if frame_index % 30 == 0:
progress = (frame_index / total_frames) * 100
print(f"📊 处理进度: {progress:.1f}% ({frame_index}/{total_frames})")
# 清理资源
cap.release()
if out:
out.release()
if show_preview:
cv2.destroyAllWindows()
# 性能统计
total_time = time.time() - start_time
avg_processing_time = np.mean(self.processing_times) if self.processing_times else 0
print(f"\n🎉 视频处理完成!")
print(f"📊 性能统计:")
print(f" 总处理帧数: {processed_frames}")
print(f" 总耗时: {total_time:.1f}")
print(f" 平均每帧: {avg_processing_time:.1f}ms")
print(f" 实际FPS: {processed_frames / total_time:.1f}")
if output_path:
print(f" 输出视频: {output_path}")
def process_webcam(self, camera_id: int = 0, output_path: str = None):
"""
处理摄像头实时视频流
"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"❌ 无法打开摄像头 {camera_id}")
return
# 设置摄像头分辨率(可选)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置输出视频
if output_path:
# 确保输出目录存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
else:
out = None
print(f"🎥 开始摄像头实时识别 - 模式: {self.list_mode} (按 'q' 退出)...")
print(f"📋 注册人脸数: {len(self.registered_faces)}")
while True:
ret, frame = cap.read()
if not ret:
print("❌ 无法读取摄像头帧")
break
# 处理当前帧
processed_frame, results = self.process_frame(frame)
# 添加实时信息
current_fps = 1000 / self.processing_times[-1] if self.processing_times else 0
info_text = f"FPS: {current_fps:.1f} | Faces: {len(results)} | Mode: {self.list_mode}"
cv2.putText(processed_frame, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 添加名单统计
match_count = sum(1 for r in results if r['is_match'])
list_text = f"Match: {match_count}/{len(results)}"
cv2.putText(processed_frame, list_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 写入输出
if out:
out.write(processed_frame)
# 显示预览
cv2.imshow('Real-time Face Recognition', processed_frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 清理资源
cap.release()
if out:
out.release()
cv2.destroyAllWindows()
print("✅ 摄像头处理结束")
# 使用示例
def main():
# 创建视频识别系统
# 使用NPU
# video_system = VideoFaceRecognition(use_gpu=False, use_npu=True, npu_device_id=0)
# 使用GPU
# video_system = VideoFaceRecognition(use_gpu=True, use_npu=False)
# 使用CPU
video_system = VideoFaceRecognition(use_gpu=False, use_npu=False)
# video_system = VideoFaceRecognition(use_gpu=True, use_npu=False) # 默认使用GPU
# 设置名单模式
# video_system.set_list_mode("blacklist") # 黑名单模式
# video_system.set_list_mode("whitelist") # 白名单模式
# 加载注册人脸
register_dir = "test_data/register" # 注册图片目录
if os.path.exists(register_dir):
video_system.load_registered_faces(register_dir)
else:
print(f"⚠️ 注册目录不存在: {register_dir}")
#
# # 设置质量阈值(可根据实际情况调整)
# video_system.set_quality_thresholds(
# clarity_threshold=1000.0, # 清晰度阈值
# quality_threshold=0.6, # 质量得分阈值
# min_face_size=30
# )
# # 选择处理模式
# print("请选择处理模式:")
# print("1. 处理视频文件")
# print("2. 实时摄像头")
#
# choice = input("请输入选择 (1 或 2): ").strip()
choice = "1"
if choice == "1":
# 处理视频文件
video_path = "test_data/video/video_2.mp4"
output_path = "test_data/output_video/video_2_white_8_gpu.mp4"
# output_path = "test_data/output_video/video_2_black_2.mp4"
# 性能优化:跳帧处理
skip_frames = 2
video_system.process_video_file(
video_path=video_path,
output_path=output_path,
skip_frames=skip_frames,
show_preview=False
)
elif choice == "2":
# 实时摄像头
output_path = "webcam_recording.mp4"
video_system.process_webcam(
camera_id=0,
output_path=output_path
)
else:
print("❌ 无效选择")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,730 @@
# video_face_recognition.py
import cv2
import numpy as np
import time
from insightface.app import FaceAnalysis
from typing import List, Dict, Tuple, Optional
import os
import glob
# 改进后的人脸质量显示
class VideoFaceRecognition:
"""
视频人脸识别系统
支持实时视频流和视频文件处理
支持黑名单和白名单模式
"""
# buffalo_l buffalo_sc
def __init__(self, model_name: str = 'buffalo_l', use_gpu: bool = True, use_npu: bool = False,
npu_device_id: int = 0):
# 设备配置映射NPU采用华为指定的完整参数
self.DEVICE_CONFIG = {
"cpu": (['CPUExecutionProvider'], -1),
"gpu": (['CUDAExecutionProvider'], 0),
"npu": (
[
(
"CANNExecutionProvider",
{
"device_id": npu_device_id,
"arena_extend_strategy": "kNextPowerOfTwo",
"npu_mem_limit": 16 * 1024 * 1024 * 1024,
"op_select_impl_mode": "high_precision",
"precision_mode": "allow_fp32_to_fp16",
"enable_cann_graph": True,
},
),
"CPUExecutionProvider"
],
npu_device_id
)
}
# 质量阈值设置
self.det_size = 640 # 320快速 640中等 1280慢
# # white
# self.list_mode = "whitelist" # "blacklist" 或 "whitelist"
# self.det_threshold = 0.7 # 人脸置信度
# self.clarity_threshold = 1000.0 # 清晰度阈值,低于此值认为人脸模糊
# self.min_face_size = 30 # 最小人脸像素尺寸
# self.pitch_threshold = 40 #
# self.yaw_threshold = 40 #
# self.quality_threshold = 0.6 # 质量得分阈值
# self.similarity_threshold = 0.13
# black
self.list_mode = "blacklist" # "blacklist" 或 "whitelist"
self.det_threshold = 0.5 # 人脸置信度
self.clarity_threshold = 100.0 # 清晰度阈值,低于此值认为人脸模糊
self.min_face_size = 20 # 最小人脸像素尺寸
self.pitch_threshold = 90 #
self.yaw_threshold = 90 #
self.quality_threshold = 0.6 # 质量得分阈值
self.similarity_threshold = 0.3
# 根据设备类型选择配置
if use_npu:
device_type = "npu"
print(f"✅ 使用NPU设备设备ID: {npu_device_id}")
elif use_gpu:
device_type = "gpu"
print("✅ 使用GPU设备")
else:
device_type = "cpu"
print("✅ 使用CPU设备")
providers, ctx_id = self.DEVICE_CONFIG[device_type]
# 初始化人脸识别模型
self.app = FaceAnalysis(name=model_name, providers=providers)
self.app.prepare(
ctx_id=ctx_id,
det_thresh=self.det_threshold,
det_size=(self.det_size, self.det_size)
)
# 名单相关变量
self.registered_faces = {} # {name: embedding}
# 性能统计
self.frame_count = 0
self.processing_times = []
print(f"✅ 视频人脸识别系统初始化完成 - 设备: {device_type.upper()}")
def set_list_mode(self, mode: str):
"""设置名单模式"""
if mode.lower() in ["blacklist", "whitelist"]:
self.list_mode = mode.lower()
print(f"✅ 名单模式设置为: {self.list_mode}")
else:
print("❌ 无效的名单模式,请使用 'blacklist''whitelist'")
def load_registered_faces(self, register_dir: str):
"""
从目录加载注册的人脸图片
文件名(去掉后缀)即为人的名字
"""
if not os.path.exists(register_dir):
print(f"❌ 注册目录不存在: {register_dir}")
return False
# 支持的图片格式
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp']
image_files = []
for ext in image_extensions:
image_files.extend(glob.glob(os.path.join(register_dir, ext)))
image_files.extend(glob.glob(os.path.join(register_dir, ext.upper())))
if not image_files:
print(f"❌ 在目录 {register_dir} 中未找到图片文件")
return False
loaded_count = 0
for image_path in image_files:
# 获取文件名(不含扩展名)作为人名
person_name = os.path.splitext(os.path.basename(image_path))[0]
# 读取图片并提取人脸特征
img = cv2.imread(image_path)
if img is None:
print(f"❌ 无法读取图片: {image_path}")
continue
faces = self.app.get(img)
if not faces:
print(f"❌ 图片中未检测到人脸: {image_path}")
continue
# 使用第一张检测到的人脸
self.registered_faces[person_name] = faces[0].embedding
loaded_count += 1
print(f"✅ 加载注册人脸: {person_name}")
print(f"🎉 成功加载 {loaded_count} 张注册人脸")
return loaded_count > 0
def find_best_match(self, embedding: np.ndarray) -> Tuple[Optional[str], float]:
"""
在注册人脸中查找最佳匹配
返回: (匹配的人名, 相似度)
"""
if not self.registered_faces:
return None, 0.0
best_similarity = 0.0
best_name = None
# 归一化查询嵌入
query_emb = embedding / np.linalg.norm(embedding)
for name, registered_embedding in self.registered_faces.items():
# 归一化注册嵌入
reg_emb = registered_embedding / np.linalg.norm(registered_embedding)
# 计算余弦相似度
similarity = float(np.dot(query_emb, reg_emb))
if similarity > best_similarity:
best_similarity = similarity
best_name = name
return best_name, best_similarity
def calculate_clarity(self, face_region: np.ndarray) -> float:
"""
计算人脸区域的清晰度/模糊度
使用拉普拉斯方差方法:值越高表示图像越清晰
"""
if len(face_region.shape) == 3:
gray = cv2.cvtColor(face_region, cv2.COLOR_BGR2GRAY)
else:
gray = face_region
# 计算拉普拉斯算子的方差
laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
return laplacian_var
def is_face_quality_acceptable(self, face, frame: np.ndarray) -> Tuple[bool, Dict]:
"""
综合判断人脸质量是否可接受
返回: (是否可接受, 质量指标字典)
"""
quality_metrics = {}
is_acceptable = True
# 1. 检测置信度
quality_metrics['det_score'] = float(face.det_score)
# 2. 人脸姿态角度
if hasattr(face, 'pose') and face.pose is not None:
pitch, yaw, roll = face.pose
quality_metrics['pitch'] = float(pitch)
quality_metrics['yaw'] = float(yaw)
quality_metrics['roll'] = float(roll)
else:
quality_metrics['pitch'] = 100.0
quality_metrics['yaw'] = 100.0
quality_metrics['roll'] = 100.0
# 3. 人脸边界框信息
bbox = face.bbox
x1, y1, x2, y2 = bbox.astype(int)
width = x2 - x1
height = y2 - y1
quality_metrics['bbox_width'] = width
quality_metrics['bbox_height'] = height
quality_metrics['bbox_area'] = width * height
quality_metrics['aspect_ratio'] = width / height if height > 0 else 0
# 4. 图像清晰度检测
# 提取人脸区域
h, w = frame.shape[:2]
x1_clip = max(0, x1)
y1_clip = max(0, y1)
x2_clip = min(w, x2)
y2_clip = min(h, y2)
if x2_clip > x1_clip and y2_clip > y1_clip:
face_region = frame[y1_clip:y2_clip, x1_clip:x2_clip]
clarity_score = self.calculate_clarity(face_region)
quality_metrics['clarity_score'] = clarity_score
else:
quality_metrics['clarity_score'] = 0.0
# 5. 综合质量评分
base_score = quality_metrics['det_score']
# 清晰度惩罚
clarity_penalty = 0.0
if quality_metrics['clarity_score'] < self.clarity_threshold:
clarity_penalty = 0.3 # 清晰度不足严重惩罚
is_acceptable = False
# 姿态惩罚
pose_penalty = 0.0
if abs(quality_metrics['yaw']) > self.yaw_threshold:
pose_penalty += 0.2
is_acceptable = False
if abs(quality_metrics['pitch']) > self.pitch_threshold:
pose_penalty += 0.2
is_acceptable = False
# 尺寸惩罚
size_penalty = 0.0
# if quality_metrics['bbox_area'] < (self.min_face_size ** 2):
# size_penalty = 0.2
if min(width, height) < self.min_face_size:
is_acceptable = False
size_penalty = 0.2
quality_metrics['quality_score'] = max(0.1, base_score - clarity_penalty - pose_penalty - size_penalty)
# # 判断是否可接受
# is_acceptable = (
# quality_metrics['det_score'] > 0.5 and # 基础检测置信度
# quality_metrics['clarity_score'] >= self.clarity_threshold and # 清晰度要求
# quality_metrics['bbox_area'] >= (self.min_face_size ** 2) and # 最小尺寸要求
# abs(quality_metrics['yaw']) < 60 and # 偏航角限制
# abs(quality_metrics['pitch']) < 45 # 俯仰角限制
# )
return is_acceptable, quality_metrics
def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, List[Dict]]:
"""
处理单帧图像
返回: (原始帧, 识别结果列表)
"""
start_time = time.time()
# 人脸检测和识别
faces = self.app.get(frame)
results = []
for face in faces:
# 检查人脸质量是否可接受
is_acceptable, quality_metrics = self.is_face_quality_acceptable(face, frame)
# 查找最佳匹配
best_name, similarity = self.find_best_match(face.embedding)
# 根据名单模式判断是否匹配
if self.list_mode == "blacklist":
# 黑名单模式:在黑名单中即为匹配(需要关注)
is_match = best_name is not None and similarity >= self.similarity_threshold
else: # whitelist
# 白名单模式:在白名单中即为匹配(允许通过)
is_match = best_name is not None and similarity >= self.similarity_threshold
result = {
'bbox': face.bbox.astype(int).tolist(),
'similarity': similarity,
'best_match': best_name,
'is_match': is_match,
# 'gender': 'Male' if face.gender == 1 else 'Female',
# 'age': int(face.age),
'det_score': float(face.det_score),
'quality_metrics': quality_metrics,
'is_acceptable': is_acceptable # 新增:是否可接受标志
}
results.append(result)
# 性能统计
processing_time = (time.time() - start_time) * 1000
self.processing_times.append(processing_time)
self.frame_count += 1
# 返回原始帧和结果列表,不进行绘制
return frame, results
def _draw_detection(self, frame: np.ndarray, result: Dict) -> np.ndarray:
"""在帧上绘制检测结果和质量信息"""
bbox = result['bbox']
similarity = result['similarity']
is_match = result['is_match']
is_acceptable = result['is_acceptable']
quality_metrics = result['quality_metrics']
best_match = result['best_match']
# 选择颜色
if not is_acceptable:
color = (128, 128, 128) # 灰色 - 质量不可接受
else:
# 选择颜色 - 根据名单模式
if self.list_mode == "blacklist":
# 黑名单模式:匹配(在黑名单中)显示红色,不匹配显示绿色
color = (0, 0, 255) if is_match else (0, 255, 0) # 红色-黑名单, 绿色-正常
else: # whitelist
# 白名单模式:匹配(在白名单中)显示绿色,不匹配显示红色
color = (0, 255, 0) if is_match else (0, 0, 255) # 绿色-白名单, 红色-陌生人
# 绘制人脸框
x1, y1, x2, y2 = bbox
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
# 准备显示文本 - 只保留关键信息
text_lines = []
# 第一行:匹配状态
if not is_acceptable:
text_lines.append("LOW QUALITY")
else:
status = f"MATCH: {best_match}: {similarity:.3f}" if is_match else f"NO MATCH: {similarity:.3f}"
text_lines.append(status)
# 第二行:质量得分(根据阈值显示颜色)
text_lines.append(f"Quality: {quality_metrics['quality_score']:.3f}")
# 第三行:检测得分
text_lines.append(f"DetScore: {quality_metrics['det_score']:.3f}")
# 第四行:清晰度
text_lines.append(f"Clarity: {quality_metrics['clarity_score']:.1f}")
# 第五行:姿态角度
text_lines.append(f"Pitch: {quality_metrics['pitch']:.1f}°")
text_lines.append(f"Yaw: {quality_metrics['yaw']:.1f}°")
# text_lines.append(f"Roll: {quality_metrics['roll']:.1f}°")
text_lines.append(f"Width: {quality_metrics['bbox_width']:.1f}")
text_lines.append(f"Height: {quality_metrics['bbox_height']:.1f}")
# 计算文本区域大小
max_text_width = 0
total_text_height = 0
line_heights = []
for line in text_lines:
(text_width, text_height), baseline = cv2.getTextSize(line, cv2.FONT_HERSHEY_SIMPLEX, 0.4, 1)
max_text_width = max(max_text_width, text_width)
line_heights.append(text_height + baseline)
total_text_height += text_height + baseline + 2
# 绘制文本背景
bg_x1 = x1
bg_y1 = y1 - total_text_height - 10
bg_x2 = x1 + max_text_width + 10
bg_y2 = y1
# 如果背景超出图像顶部,调整到框下方
if bg_y1 < 0:
bg_y1 = y2
bg_y2 = y2 + total_text_height + 10
# 绘制半透明背景
overlay = frame.copy()
cv2.rectangle(overlay, (bg_x1, bg_y1), (bg_x2, bg_y2), (0, 0, 0), -1)
alpha = 0.6
cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame)
# 绘制文本
current_y = bg_y1 + 15
for i, line in enumerate(text_lines):
# 根据内容选择颜色 - 按照你的要求简化颜色规则
if i == 0: # 状态行
if not is_acceptable:
text_color = (128, 128, 128) # 灰色 - 质量差
elif is_match:
text_color = (0, 255, 0) # 绿色 - 匹配
else:
text_color = (0, 0, 255) # 红色 - 不匹配
# elif i == 1: # 质量得分行
# # 质量得分:低于阈值红色,大于等于阈值绿色
# if quality_metrics['quality_score'] >= self.quality_threshold:
# text_color = (0, 255, 0) # 绿色 - 高质量
# else:
# text_color = (0, 0, 255) # 红色 - 低质量
elif i == 3: # 清晰度行
# 清晰度:低于阈值红色,大于等于阈值绿色
if quality_metrics['clarity_score'] >= self.clarity_threshold:
text_color = (255, 255, 255)
else:
text_color = (0, 0, 255)
elif i == 4: # pitch
if abs(quality_metrics['pitch']) > self.pitch_threshold:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 5: # yaw
if abs(quality_metrics['yaw']) > self.yaw_threshold:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 6: #
if quality_metrics['bbox_width'] < self.min_face_size:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
elif i == 7: #
if quality_metrics['bbox_height'] < self.min_face_size:
text_color = (0, 0, 255) # 红色
else:
text_color = (255, 255, 255)
else:
text_color = (255, 255, 255) # 白色 - 其他信息
cv2.putText(frame, line, (x1 + 5, current_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, text_color, 1)
current_y += line_heights[i]
return frame
def draw_detections(self, frame: np.ndarray, results: List[Dict]) -> np.ndarray:
"""绘制所有检测结果"""
for result in results:
frame = self._draw_detection(frame, result)
return frame
def set_quality_thresholds(self, clarity_threshold: float = None,
quality_threshold: float = None,
min_face_size: int = None):
"""设置质量阈值"""
if clarity_threshold is not None:
self.clarity_threshold = clarity_threshold
if quality_threshold is not None:
self.quality_threshold = quality_threshold
if min_face_size is not None:
self.min_face_size = min_face_size
print(
f"✅ 质量阈值更新 - 清晰度: {self.clarity_threshold}, 质量得分: {self.quality_threshold}, 最小尺寸: {self.min_face_size}")
def process_video_file(self, video_path: str, output_path: str = None,
skip_frames: int = 0, show_preview: bool = True):
"""
处理视频文件
Args:
video_path: 输入视频路径
output_path: 输出视频路径
skip_frames: 跳帧数,用于提高处理速度
show_preview: 是否显示实时预览
"""
if not os.path.exists(video_path):
print(f"❌ 视频文件不存在: {video_path}")
return
# 打开视频文件
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"❌ 无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"📹 视频信息: {width}x{height}, {fps:.1f}FPS, 总帧数: {total_frames}")
print(f"🎯 当前模式: {self.list_mode}, 注册人脸数: {len(self.registered_faces)}")
# 设置输出视频
if output_path:
# 确保输出目录存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps / (skip_frames), (width, height))
else:
out = None
# 处理视频帧
frame_index = 0
processed_frames = 0
start_time = time.time()
print("🚀 开始处理视频...")
while True:
ret, frame = cap.read()
if not ret:
break
# 跳帧处理
if skip_frames > 0 and frame_index % (skip_frames + 1) != 0:
frame_index += 1
continue
# 处理当前帧 - 只获取结果,不绘制
processed_frame, results = self.process_frame(frame)
# 在帧上绘制检测结果
processed_frame = self.draw_detections(processed_frame, results)
# 写入输出视频
if out:
out.write(processed_frame)
# 显示预览
if show_preview:
# 添加性能信息
fps_text = f"Frame: {frame_index}/{total_frames} | Faces: {len(results)} | Mode: {self.list_mode}"
cv2.putText(processed_frame, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 添加名单统计
match_count = sum(1 for r in results if r['is_match'])
list_text = f"Match: {match_count}/{len(results)}"
cv2.putText(processed_frame, list_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow('Video Face Recognition', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_index += 1
processed_frames += 1
# 进度显示
if frame_index % 30 == 0:
progress = (frame_index / total_frames) * 100
print(f"📊 处理进度: {progress:.1f}% ({frame_index}/{total_frames})")
# 清理资源
cap.release()
if out:
out.release()
if show_preview:
cv2.destroyAllWindows()
# 性能统计
total_time = time.time() - start_time
avg_processing_time = np.mean(self.processing_times) if self.processing_times else 0
print(f"\n🎉 视频处理完成!")
print(f"📊 性能统计:")
print(f" 总处理帧数: {processed_frames}")
print(f" 总耗时: {total_time:.1f}")
print(f" 平均每帧: {avg_processing_time:.1f}ms")
print(f" 实际FPS: {processed_frames / total_time:.1f}")
if output_path:
print(f" 输出视频: {output_path}")
def process_webcam(self, camera_id: int = 0, output_path: str = None):
"""
处理摄像头实时视频流
"""
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"❌ 无法打开摄像头 {camera_id}")
return
# 设置摄像头分辨率(可选)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置输出视频
if output_path:
# 确保输出目录存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
else:
out = None
print(f"🎥 开始摄像头实时识别 - 模式: {self.list_mode} (按 'q' 退出)...")
print(f"📋 注册人脸数: {len(self.registered_faces)}")
while True:
ret, frame = cap.read()
if not ret:
print("❌ 无法读取摄像头帧")
break
# 处理当前帧 - 只获取结果,不绘制
processed_frame, results = self.process_frame(frame)
# 在帧上绘制检测结果
processed_frame = self.draw_detections(processed_frame, results)
# 添加实时信息
current_fps = 1000 / self.processing_times[-1] if self.processing_times else 0
info_text = f"FPS: {current_fps:.1f} | Faces: {len(results)} | Mode: {self.list_mode}"
cv2.putText(processed_frame, info_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 添加名单统计
match_count = sum(1 for r in results if r['is_match'])
list_text = f"Match: {match_count}/{len(results)}"
cv2.putText(processed_frame, list_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 写入输出
if out:
out.write(processed_frame)
# 显示预览
cv2.imshow('Real-time Face Recognition', processed_frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 清理资源
cap.release()
if out:
out.release()
cv2.destroyAllWindows()
print("✅ 摄像头处理结束")
# 使用示例
def main():
# 创建视频识别系统
# 使用NPU
# video_system = VideoFaceRecognition(use_gpu=False, use_npu=True, npu_device_id=0)
# 使用GPU
video_system = VideoFaceRecognition(use_gpu=True, use_npu=False)
# 使用CPU
# video_system = VideoFaceRecognition(use_gpu=False, use_npu=False)
# video_system = VideoFaceRecognition(use_gpu=True, use_npu=False) # 默认使用GPU
# 设置名单模式
# video_system.set_list_mode("blacklist") # 黑名单模式
# video_system.set_list_mode("whitelist") # 白名单模式
# 加载注册人脸
register_dir = "test_data/register" # 注册图片目录
if os.path.exists(register_dir):
video_system.load_registered_faces(register_dir)
else:
print(f"⚠️ 注册目录不存在: {register_dir}")
#
# # 设置质量阈值(可根据实际情况调整)
# video_system.set_quality_thresholds(
# clarity_threshold=1000.0, # 清晰度阈值
# quality_threshold=0.6, # 质量得分阈值
# min_face_size=30
# )
# # 选择处理模式
# print("请选择处理模式:")
# print("1. 处理视频文件")
# print("2. 实时摄像头")
#
# choice = input("请输入选择 (1 或 2): ").strip()
choice = "1"
if choice == "1":
# 处理视频文件
video_path = "test_data/video/video_2.mp4"
output_path = "test_data/output_video/video_2_white_8_gpu.mp4"
# output_path = "test_data/output_video/video_2_black_2.mp4"
# 性能优化:跳帧处理
skip_frames = 2
video_system.process_video_file(
video_path=video_path,
output_path=output_path,
skip_frames=skip_frames,
show_preview=False
)
elif choice == "2":
# 实时摄像头
output_path = "webcam_recording.mp4"
video_system.process_webcam(
camera_id=0,
output_path=output_path
)
else:
print("❌ 无效选择")
if __name__ == "__main__":
main()