import cv2
import dlib
import numpy as np
import pandas as pd
import os
import concurrent.futures
from pathlib import Path
class FaceRecognizer:
def __init__(self, tolerance=0.6, model_dir="models"):
self.tolerance = tolerance
self.known_encodings = []
self.known_names = []
self._models_loaded = False
self.detector = None
self.sp = None
self.facerec = None
self.attendance = set()
self.model_dir = model_dir # 模型路径参数化
def _load_models(self):
"""延迟加载模型并检查路径有效性"""
if not self._models_loaded:
model_paths = {
"sp": Path(self.model_dir) / "D:/python/Lib/site-packages/face_recognition_models/models/shape_predictor_5_face_landmarks.dat",
"facerec": Path(self.model_dir) / "D:/python/Lib/site-packages/face_recognition_models/models/dlib_face_recognition_resnet_model_v1.dat"
}
# 检查模型文件是否存在
for key, path in model_paths.items():
if not path.exists():
raise FileNotFoundError(f"模型文件 {path} 不存在")
self.detector = dlib.get_frontal_face_detector()
self.sp = dlib.shape_predictor(str(model_paths["sp"]))
self.facerec = dlib.face_recognition_model_v1(str(model_paths["facerec"]))
self._models_loaded = True
def load_faces(self, image_folder):
"""加载人脸库(自动缓存编码)"""
self.known_encodings = []
self.known_names = []
image_folder = Path(image_folder)
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
for img_path in image_folder.rglob('*'):
if img_path.suffix.lower() in ('.jpg', '.jpeg', '.png', '.bmp', '.tif', '.tiff'):
futures.append(executor.submit(self._process_image, img_path))
for future in concurrent.futures.as_completed(futures):
enc, name = future.result()
if enc is not None:
self.known_encodings.append(enc)
self.known_names.append(name)
print(f"已加载 {len(self.known_names)} 个人脸数据")
def _process_image(self, image_path):
"""处理单张图像并返回编码+姓名"""
image_path = Path(image_path)
encoding_file = image_path.with_suffix('.npy')
# 存在预计算编码则直接加载
if encoding_file.exists():
try:
enc = np.load(encoding_file)
return enc, image_path.stem
except Exception as e:
print(f"加载缓存编码失败: {encoding_file}, 错误: {e}")
# 计算新编码
self._load_models()
image = cv2.imread(str(image_path))
if image is None:
print(f"无法读取图像: {image_path}")
return None, None
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
faces = self.detector(rgb, 1)
if not faces:
print(f"未检测到人脸: {image_path}")
return None, None
# 仅使用最大的人脸(假设每图一人)
face = max(faces, key=lambda rect: rect.width() * rect.height())
shape = self.sp(rgb, face)
enc = np.array(self.facerec.compute_face_descriptor(rgb, shape))
# 保存编码
try:
np.save(encoding_file, enc)
except Exception as e:
print(f"保存编码失败: {encoding_file}, 错误: {e}")
return enc, image_path.stem
def process_frame(self, frame):
"""处理视频帧"""
self._load_models() # 确保模型已加载
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
faces = self.detector(rgb, 1)
for face in faces:
shape = self.sp(rgb, face)
encoding = self.facerec.compute_face_descriptor(rgb, shape)
name = "Unknown"
if self.known_encodings:
distances = np.linalg.norm(self.known_encodings - np.array(encoding), axis=1)
min_index = np.argmin(distances)
if distances[min_index] <= self.tolerance:
name = self.known_names[min_index]
self.attendance.add(name)
self._draw_face_info(frame, face, name)
return frame
def _draw_face_info(self, frame, face, name):
"""绘制人脸框和姓名"""
top, right, bottom, left = face.top(), face.right(), face.bottom(), face.left()
cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2)
cv2.putText(frame, name, (left, top - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
def save_attendance(self, filename="attendance.xlsx"):
"""保存考勤记录"""
df = pd.DataFrame({
"姓名": list(self.attendance),
"状态": ["出席"] * len(self.attendance),
"时间": [pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S")] * len(self.attendance)
})
df.to_excel(filename, index=False)
print(f"考勤结果已保存到 {filename}")
if __name__ == "__main__":
# 使用相对路径示例
recognizer = FaceRecognizer(
tolerance=0.55,
model_dir="models" # 模型存放目录
)
recognizer.load_faces("face_database") # 人脸库目录
cap = cv2.VideoCapture(0)
try:
while True:
ret, frame = cap.read()
if not ret:
break
processed_frame = recognizer.process_frame(frame)
cv2.imshow("renliankaoqinxitong", processed_frame)
key = cv2.waitKey(1)
if key == 27: # ESC退出
break
elif key == ord('s'):
recognizer.save_attendance()
finally:
cap.release()
cv2.destroyAllWindows()
recognizer.save_attendance()显示检测0个人脸
最新发布