OpenCV face_recognition多图训练单人特征编码合并问题
问题背景
基于face_recognition模块搭建人脸识别系统时,需要支持同一人员使用多张照片训练:known_faces目录下包含4个以人员命名的子文件夹(示例命名为A、B、C、D),每个子文件夹内存放对应人员的10张照片。
原有实现代码如下:
import face_recognition import os import cv2 import numpy as np from numpy import load KNOWN_FACES_DIR = 'known_faces' TOLERANCE = 0.4 FRAME_THICKNESS = 3 FONT_THICKNESS = 2 MODEL = 'cnn' video = cv2.VideoCapture(0) print('Encoding faces...') known_faces = [] known_names = [] for name in os.listdir(KNOWN_FACES_DIR): for filename in os.listdir(f'{KNOWN_FACES_DIR}/{name}'): image = face_recognition.load_image_file(f'{KNOWN_FACES_DIR}/{name}/{filename}') box = face_recognition.face_locations(image,model=MODEL)[0] encoding = face_recognition.face_encodings(image,box)[0] known_faces.append(encoding) known_names.append(name) print(known_faces) np.save('face_repr.npy', known_faces) np.save('labels.npy', known_names)
运行代码后实际打印的是known_names列表(属于变量打印错误,known_faces存储的是128维人脸编码向量),输出为40个名称项,按顺序重复10次A、10次B、10次C、10次D。原有逻辑为40张图像分别生成独立编码,被误认为会识别出40个不同人员,实际期望是最终对应4个有效人员身份。
核心认知误区:同一人员对应多个人脸编码不会被判定为多个不同身份,只要匹配逻辑正确,所有关联同个名字的编码都会映射到同一人员。
可选解决方案
方案1:保留多编码逻辑(推荐,识别准确率更高)
该方案不需要修改现有编码存储结构,仅需要补全异常判断、修正识别阶段的匹配规则即可。同一人员不同角度、不同光线、不同表情的编码都能作为匹配参考,识别准确率更高。
编码阶段修正
原代码直接取face_locations和face_encodings返回结果的第0项,若某张训练图未检测到人脸会直接触发索引报错,修正后代码:
print('Encoding faces...') known_faces = [] known_names = [] for name in os.listdir(KNOWN_FACES_DIR): person_dir = os.path.join(KNOWN_FACES_DIR, name) # 跳过目录下的非文件夹文件 if not os.path.isdir(person_dir): continue for filename in os.listdir(person_dir): img_path = os.path.join(person_dir, filename) try: image = face_recognition.load_image_file(img_path) # 检测人脸位置,未检测到人脸跳过当前图片 face_locations = face_recognition.face_locations(image, model=MODEL) if not face_locations: print(f"未在{img_path}检测到人脸,已跳过") continue encoding = face_recognition.face_encodings(image, known_face_locations=[face_locations[0]])[0] known_faces.append(encoding) known_names.append(name) except Exception as e: print(f"处理{img_path}出错:{str(e)},已跳过") np.save('face_repr.npy', known_faces) np.save('labels.npy', known_names)
识别阶段匹配逻辑
匹配时取与当前人脸距离最小的已知编码作为匹配项,只要距离小于阈值即可判定为对应人员,参考代码:
while True: ret, frame = video.read() if not ret: break rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 检测当前帧人脸位置和编码 face_locations = face_recognition.face_locations(rgb_frame, model=MODEL) face_encodings = face_recognition.face_encodings(rgb_frame, face_locations) for face_encoding, (top, right, bottom, left) in zip(face_encodings, face_locations): # 计算当前人脸和所有已知编码的距离 face_distances = face_recognition.face_distance(known_faces, face_encoding) # 取距离最小的匹配项 best_match_idx = np.argmin(face_distances) if face_distances[best_match_idx] <= TOLERANCE: match_name = known_names[best_match_idx] # 画人脸框 cv2.rectangle(frame, (left, top), (right, bottom), (0,255,0), FRAME_THICKNESS) # 写人名标签 cv2.putText(frame, match_name, (left, top-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), FONT_THICKNESS) cv2.imshow('Face Recognition', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break video.release() cv2.destroyAllWindows()
方案2:生成单人员平均编码(匹配速度更快)
如果希望每个人员仅对应1组编码,最终存储4组编码对应4个人员,可以对同一个人的所有人脸编码求平均值,用平均向量作为该人员的特征代表。该方案匹配时计算量更小,运行速度更快,符合最初“4名人员生成4组对应编码”的预期。
编码阶段修改为:
print('Encoding faces...') known_faces = [] known_names = [] for name in os.listdir(KNOWN_FACES_DIR): person_dir = os.path.join(KNOWN_FACES_DIR, name) if not os.path.isdir(person_dir): continue person_encodings = [] for filename in os.listdir(person_dir): img_path = os.path.join(person_dir, filename) try: image = face_recognition.load_image_file(img_path) face_locations = face_recognition.face_locations(image, model=MODEL) if not face_locations: print(f"未在{img_path}检测到人脸,已跳过") continue encoding = face_recognition.face_encodings(image, known_face_locations=[face_locations[0]])[0] person_encodings.append(encoding) except Exception as e: print(f"处理{img_path}出错:{str(e)},已跳过") # 对当前人员的所有编码求平均,得到单组代表编码 if person_encodings: avg_encoding = np.mean(person_encodings, axis=0) known_faces.append(avg_encoding) known_names.append(name) np.save('face_repr.npy', known_faces) np.save('labels.npy', known_names)
该方案最终生成的known_faces长度为4,known_names为['A','B','C','D'],识别阶段逻辑和单图训练的实现完全一致即可。
注意事项
- 当前设置的
TOLERANCE = 0.4阈值过于严格,face_recognition默认阈值为0.6,阈值越低判定越严格,越容易出现漏识别,建议根据实际测试效果调整到0.45-0.55区间。 - 训练图尽量覆盖不同光线、角度、表情,避免使用模糊、遮挡严重的图片,能有效提升识别准确率。
- 如果设备没有独立GPU,建议将
MODEL参数改为hog,CPU运行速度会比cnn模型快很多。
内容的提问来源于stack exchange,提问作者Nikhil Anand

