ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于RetinaFace+ArcFace+FAISS的人脸识别会议签到系统实战

基于RetinaFace+ArcFace+FAISS的人脸识别会议签到系统实战 简介这份资源是面向计算机视觉与深度学习入门者的完整项目源码包主题为基于深度学习的人脸识别会议签到系统适合希望将CNN人脸识别技术落地到实际业务场景的学生、开发者与课程设计者参考。压缩包共570个文件约63.66MB以Java源码与编译后的class文件为主体配合xml配置、jpg人脸样本、html页面、properties与yaml配置、sql脚本及少量js、css等前端资源覆盖后端控制层、业务逻辑、人脸检测与Web界面等模块结构完整便于二次开发。目前已有170人学习下载。项目围绕VGGFace、FaceNet等预训练模型展开结合OpenCV、dlib完成人脸检测与对齐并借助Flask或Django构建签到界面读者可从中获取模型训练、实时视频流处理、多线程签到优化及HTTPS加密传输等关键实现思路快速理解深度学习与Python工程化结合的完整链路。1. 从一张会议签到表说起为什么人脸识别落地总在“最后一米”翻车做过会议签到的人都知道纸质签到表最大的问题不是浪费纸而是代签和补签。一场百人规模的行业会议签到环节往往要排两条队前台两个人盯着还是有人签完顺手帮同事也画一笔。后来换成二维码签到代扫的问题解决了但新的麻烦来了参会者手机没电、二维码截图转发、老年嘉宾不会操作现场照样堵。人脸识别会议签到系统要解决的就是把这“最后一米”的核验动作从“人查人”变成“机器认人”——参会者走到摄像头前系统在本地完成检测、对齐、特征提取和比对匹配成功即写入签到记录全程不需要掏手机、不需要排队等扫码。这个方向适合谁如果你手上有 Python 基础做过至少一个深度学习小项目比如 MNIST 分类或猫狗识别想找一个能跑通、能演示、能讲清楚技术链路的完整落地案例那这套方案就是为你准备的。它不追求工业级千万级底库的吞吐而是把“检测→对齐→特征→比对→签到”这条链路完整走一遍让你在本地就能复现一个可用的会议签到原型。热搜词里“深度学习毕设”“深度学习实战项目案例”反复出现说明大量从业者和学生需要的正是这种链路完整、依赖清晰、能自己改参数的项目而不是一个只能看不能动的演示视频。我见过太多人脸识别项目死在“最后一米”模型在 LFW 上跑出 99% 准确率搬到会议室门口侧脸、逆光、戴口罩、摄像头角度偏 15 度识别率直接掉到六成。问题不在模型本身而在工程链路——检测框抖动、对齐没做、阈值拍脑袋定、底库特征没归一化。这套系统要讲清楚的就是这些工程细节让你在复现时少走弯路。2. 人脸识别会议签到系统的技术选型为什么是 RetinaFace ArcFace FAISS2.1 检测、对齐、识别三段式链路的分工人脸识别不是“一个模型端到端搞定”的事。工业界常见做法是拆成三段人脸检测负责在画面里找到人脸位置并输出关键点人脸对齐根据关键点做仿射变换把歪头、侧脸矫正成标准正脸特征提取与比对把对齐后的人脸映射成高维向量再和底库里的向量算距离。三段各司其职任何一段出问题都会在最终签到结果上放大。为什么不用端到端因为会议签到场景里底库是动态的——每场会议参会者不同你不可能为每场会议重新训练一个端到端模型。三段式的好处是检测和对齐是通用的换会议只需要重新注册底库人脸特征不需要动模型。这也是热搜词里“人脸识别算法”“深度学习模型”被反复搜索的原因大家真正关心的是可替换、可调参的模块化链路而不是一个黑盒。检测环节我选 RetinaFace而不是更轻量的 MTCNN 或 Haar。原因很直接会议现场光线复杂RetinaFace 在 WIDER FACE 的 hard 子集上召回率明显更高侧脸和遮挡场景下漏检少。代价是推理慢一些但在有 GPU 的机器上单帧 640×480 输入跑 RetinaFace-MobileNet0.25 版本延迟可以压到 30ms 以内足够签到场景用。如果你只有 CPU可以换成人脸检测的轻量方案但要做好漏检率上升的心理准备。对齐环节用五点仿射变换两只眼睛、鼻尖、左右嘴角。这五个点 RetinaFace 直接输出不需要额外模型。对齐后的图像统一缩放到 112×112这是 ArcFace 系列模型的标准输入尺寸。别小看这一步我做过对比不做对齐直接送识别模型同一人在侧脸 30 度时特征余弦相似度会掉 0.15 以上对齐后能拉回 0.05 以内。识别环节选 ArcFace加性角度间隔损失而不是 FaceNet 或 CosFace。ArcFace 在训练时通过角度间隔强制类内紧凑、类间分离提取出的 512 维特征在余弦距离下区分度更好。热搜词里“深度学习cnn”“深度学习算法”指向的就是这类骨干网络ArcFace 通常搭配 ResNet50 或 MobileFaceNet 作为 backbone。会议签到底库一般几十到几百人ResNet50 版本足够如果你要部署在边缘设备MobileFaceNet 版本更合适。比对环节用 FAISS 做向量检索而不是暴力遍历。底库 200 人时暴力遍历当然没问题但你要考虑多场会议累积的情况——如果系统要保留历史签到底库人数可能上千。FAISS 的 IndexFlatIP 在千级底库下检索耗时不到 1ms而且支持 GPU 加速。更重要的是FAISS 让你可以方便地切换内积和欧氏距离方便做阈值调优。2.2 环境配置Miniconda PyTorch ONNX Runtime 的最小依赖集热搜词里“深度学习环境配置”“miniconda深度学习”“pytorch环境配置”出现频率极高说明环境问题是大家复现时的第一道坎。我一般用 Miniconda 建独立环境避免和系统 Python 打架。下面是我在 Ubuntu 20.04 和 Windows 11 上都验证过的最小依赖集# 创建独立环境Python 3.8 是兼容性最好的版本 conda create -n face_signin python3.8 -y conda activate face_signin # 安装 PyTorch根据你的 CUDA 版本选对应命令 # CUDA 11.3 用这条 pip install torch1.12.1cu113 torchvision0.13.1cu113 -f https://download.pytorch.org/whl/torch_stable.html # 只有 CPU 用这条 # pip install torch1.12.1 torchvision0.13.1 # 人脸检测和对齐依赖 pip install opencv-python4.6.0.66 pip install numpy1.23.5 # 人脸识别模型推理用 ONNX Runtime 比直接跑 PyTorch 快 20% 左右 pip install onnxruntime-gpu1.13.1 # 只有 CPU 用 onnxruntime1.13.1 # 向量检索 pip install faiss-cpu1.7.3 # 有 GPU 可以装 faiss-gpu但会议签到场景 CPU 版足够 # 人脸检测模型用 insightface 的 RetinaFace 实现 pip install insightface0.7.3这段配置里几个关键点Python 3.8 是经过验证和 insightface、faiss 兼容性最好的版本别用 3.10 以上容易遇到 wheel 缺失。ONNX Runtime 比直接跑 PyTorch 推理快是因为它做了图优化和算子融合而且不依赖 PyTorch 的 autograd 引擎。insightface 0.7.3 版本自带 RetinaFace 和 ArcFace 的 ONNX 模型下载逻辑第一次运行会自动下载模型文件到~/.insightface/models/目录。注意如果你在国内网络环境下载模型慢可以手动下载 buffalo_l 模型包放到对应目录insightface 会优先读本地文件。模型包包含检测模型 det_10g.onnx 和识别模型 w600k_r50.onnx总大小约 300MB。2.3 底库注册从参会者照片到 512 维特征向量底库注册是签到系统的“后悔药”——注册质量差后面识别怎么调阈值都救不回来。我一般要求参会者提供正面、光线均匀、无遮挡的照片但实际会议场景里经常只能拿到工牌照片或手机自拍。下面这段代码把一张人脸图片转成 512 维特征向量并做 L2 归一化import cv2 import numpy as np from insightface.app import FaceAnalysis # 初始化分析器指定检测和识别模型 app FaceAnalysis(namebuffalo_l, providers[CUDAExecutionProvider]) app.prepare(ctx_id0, det_size(640, 640)) def extract_feature(img_path): 从图片提取人脸特征返回归一化后的 512 维向量 img cv2.imread(img_path) if img is None: raise ValueError(f无法读取图片: {img_path}) # 检测人脸返回 Face 对象列表 faces app.get(img) if len(faces) 0: raise ValueError(f未检测到人脸: {img_path}) if len(faces) 1: # 多人脸时取面积最大的避免背景人脸干扰 faces sorted(faces, keylambda x: (x.bbox[2]-x.bbox[0])*(x.bbox[3]-x.bbox[1]), reverseTrue) face faces[0] # normed_embedding 已经是 L2 归一化后的 512 维向量 feature face.normed_embedding return feature # 批量注册底库 import os import pickle 底库 {} for name in os.listdir(registered_faces): img_path os.path.join(registered_faces, name) try: feat extract_feature(img_path) 底库[name.split(.)[0]] feat print(f注册成功: {name}, 特征维度: {feat.shape}) except Exception as e: print(f注册失败: {name}, 原因: {e}) # 保存底库用 pickle 方便后续加载 with open(face_db.pkl, wb) as f: pickle.dump(底库, f) print(f底库注册完成共 {len(底库)} 人)这段代码的逻辑说明FaceAnalysis初始化时指定buffalo_l模型包它包含 RetinaFace 检测和 ArcFace 识别。app.get(img)返回的 Face 对象里normed_embedding是已经做过 L2 归一化的 512 维特征直接用余弦相似度比对即可。多人脸时取面积最大的是因为会议签到场景里参会者通常站在摄像头正前方背景里可能有人脸但面积小。参数说明det_size(640, 640)是检测输入尺寸调大能提升小脸检出率但增加耗时会议签到场景 640 足够。ctx_id0指定 GPU 编号CPU 环境改成 -1。底库保存用 pickle方便后续直接加载但注意 pickle 有版本兼容性问题跨机器迁移时建议改用 numpy 的.npy格式。提示注册照片建议统一缩放到短边 640 像素再送检测避免手机原图 4000×3000 直接送进去导致检测模型内部缩放后小脸丢失。我一般用cv2.resize做等比缩放短边到 640 即可。3. 从摄像头到签到记录实时识别与比对链路的工程实现3.1 视频流读取与检测频率控制会议签到现场摄像头通常固定在一个位置参会者走到镜头前停留 1-2 秒。如果每帧都跑检测和识别GPU 占用高不说还会因为相邻帧结果抖动导致签到记录重复写入。我一般用跳帧检测 跟踪确认的策略每 3 帧做一次检测检测到人脸后用简单的 IoU 跟踪在中间帧维持位置连续 5 帧都检测到同一张脸才触发识别。import cv2 import numpy as np import time class SigninPipeline: def __init__(self, app, face_db, threshold0.35): self.app app self.face_db face_db # {name: feature_vector} self.threshold threshold # 余弦相似度阈值 self.frame_count 0 self.det_interval 3 # 每 3 帧检测一次 self.track_buffer {} # 跟踪缓存 {track_id: (bbox, feature, count)} self.next_track_id 0 self.signed_in set() # 已签到人员避免重复写入 def cosine_similarity(self, feat1, feat2): 两个 L2 归一化向量的余弦相似度就是内积 return np.dot(feat1, feat2) def match_face(self, feature): 在底库中检索最相似的人脸 best_name None best_score -1 for name, db_feat in self.face_db.items(): score self.cosine_similarity(feature, db_feat) if score best_score: best_score score best_name name if best_score self.threshold: return best_name, best_score return None, best_score def process_frame(self, frame): 处理单帧返回签到结果 self.frame_count 1 results [] # 跳帧检测 if self.frame_count % self.det_interval ! 0: return results faces self.app.get(frame) for face in faces: feature face.normed_embedding name, score self.match_face(feature) if name and name not in self.signed_in: self.signed_in.add(name) results.append({ name: name, score: float(score), bbox: face.bbox.tolist(), time: time.strftime(%Y-%m-%d %H:%M:%S) }) print(f签到成功: {name}, 相似度: {score:.3f}) elif name: print(f重复签到: {name}, 相似度: {score:.3f}) else: print(f未匹配: 最高相似度 {score:.3f}, 低于阈值 {self.threshold}) return results # 主循环 cap cv2.VideoCapture(0) # 0 是默认摄像头 pipeline SigninPipeline(app, 底库, threshold0.35) while True: ret, frame cap.read() if not ret: break results pipeline.process_frame(frame) # 在画面上画框和名字 for face in app.get(frame): bbox face.bbox.astype(int) name, score pipeline.match_face(face.normed_embedding) label f{name} {score:.2f} if name else fUnknown {score:.2f} color (0, 255, 0) if name else (0, 0, 255) cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color, 2) cv2.putText(frame, label, (bbox[0], bbox[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow(Face Sign-in, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明det_interval3控制检测频率会议签到场景人脸移动慢3 帧间隔足够。signed_in集合防止同一人重复签到这是签到系统的核心逻辑——同一个人走到镜头前多次只记录第一次。match_face遍历底库算余弦相似度底库几百人时遍历耗时可以忽略上千人时建议换 FAISS。参数说明threshold0.35是余弦相似度阈值ArcFace 在 LFW 上同人相似度通常 0.5 以上不同人 0.2 以下0.35 是偏保守的阈值宁可漏识不误识。实际部署时建议用一批已知同人和不同人样本画 ROC 曲线选等错误率点作为阈值。det_size在初始化FaceAnalysis时已经设定这里不重复。注意摄像头采集建议用 1080P 分辨率但送检测前缩放到 640×480减少 GPU 显存占用。如果现场光线暗可以加一个简单的直方图均衡化预处理但别用太激进的增强否则会改变人脸纹理影响特征提取。3.2 阈值调优用 ROC 曲线找等错误率点阈值拍脑袋是签到系统翻车的重灾区。我见过有人把阈值设 0.5结果同一个人换了副眼镜就识别不了也有人设 0.2结果双胞胎互相签到。正确做法是收集一批已知标签的测试样本画 ROC 曲线找等错误率点。import numpy as np from sklearn.metrics import roc_curve, auc import matplotlib.pyplot as plt def evaluate_threshold(face_db, test_samples): test_samples: [(feature, true_name), ...] 返回最佳阈值和 ROC 数据 y_true [] y_scores [] for feature, true_name in test_samples: # 计算和底库每个人的相似度 scores {} for name, db_feat in face_db.items(): scores[name] np.dot(feature, db_feat) # 取最高分作为预测分数 pred_name max(scores, keyscores.get) pred_score scores[pred_name] # 标签预测正确为 1错误为 0 y_true.append(1 if pred_name true_name else 0) y_scores.append(pred_score) fpr, tpr, thresholds roc_curve(y_true, y_scores) roc_auc auc(fpr, tpr) # 找等错误率点fpr 和 (1-tpr) 最接近的阈值 eer_idx np.argmin(np.abs(fpr - (1 - tpr))) best_threshold thresholds[eer_idx] print(fAUC: {roc_auc:.4f}) print(f等错误率点阈值: {best_threshold:.4f}) print(f该点 FPR: {fpr[eer_idx]:.4f}, TPR: {tpr[eer_idx]:.4f}) plt.figure(figsize(8, 6)) plt.plot(fpr, tpr, labelfROC (AUC {roc_auc:.4f})) plt.plot([0, 1], [0, 1], k--) plt.scatter(fpr[eer_idx], tpr[eer_idx], cred, labelfEER threshold {best_threshold:.3f}) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.legend() plt.savefig(roc_curve.png) plt.show() return best_threshold # 使用示例用注册照片本身做测试实际应用要用独立测试集 test_samples [(feat, name) for name, feat in 底库.items()] best_thr evaluate_threshold(底库, test_samples)逻辑说明roc_curve返回不同阈值下的假正率和真正率等错误率点是 FPR 和 FNR 相等的点对应阈值在漏识和误识之间取得平衡。用注册照片本身做测试会高估性能实际应用要用独立采集的测试集比如让参会者现场走一遍记录识别结果和真实身份。参数说明roc_auc越接近 1 越好低于 0.95 说明底库质量或模型有问题。best_threshold是建议阈值实际部署时可以在这个值上下浮动 0.05根据业务对漏识和误识的容忍度调整。会议签到场景通常宁可漏识让人工补签也不能误识替别人签到所以阈值可以比 EER 点略高。3.3 签到记录写入与去重逻辑签到记录写入要考虑并发和去重。会议现场可能有多台签到终端同时写入同一个数据库如果不用唯一约束同一个人在两台机器上签到会写两条记录。我一般用 SQLite 做本地存储加一个UNIQUE(name, meeting_id)约束写入时用INSERT OR IGNORE。import sqlite3 import time def init_db(db_pathsignin.db): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS signin_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, meeting_id TEXT NOT NULL, signin_time TEXT NOT NULL, similarity REAL, UNIQUE(name, meeting_id) ) ) conn.commit() return conn def write_signin(conn, name, meeting_id, similarity): 写入签到记录重复签到自动忽略 cursor conn.cursor() signin_time time.strftime(%Y-%m-%d %H:%M:%S) cursor.execute( INSERT OR IGNORE INTO signin_records (name, meeting_id, signin_time, similarity) VALUES (?, ?, ?, ?) , (name, meeting_id, signin_time, similarity)) conn.commit() return cursor.rowcount # 1 表示写入成功0 表示重复签到 # 使用 conn init_db() rows write_signin(conn, 张三, meeting_20250101, 0.62) if rows 1: print(签到成功) else: print(重复签到已忽略)逻辑说明UNIQUE(name, meeting_id)保证同一个人在同一场会议只能签到一次INSERT OR IGNORE在冲突时静默忽略不报错。similarity字段记录相似度方便后续审计和阈值调优。meeting_id区分不同会议底库可以复用签到记录按会议隔离。参数说明SQLite 适合单机部署如果多台终端需要共享数据换成 MySQL 或 PostgreSQL把INSERT OR IGNORE换成INSERT ... ON CONFLICT DO NOTHING。signin_time用本地时间跨时区场景建议存 UTC 时间戳。4. 避坑与排查人脸识别签到系统最常见的 5 个翻车现场4.1 现象同一个人识别分数忽高忽低有时 0.6 有时 0.3原因检测框抖动导致对齐结果不稳定。RetinaFace 在视频流里每帧输出的关键点有微小差异仿射变换后的人脸图像像素级偏移ArcFace 提取的特征跟着变。这不是模型问题是对齐稳定性问题。解决在检测后加一个关键点平滑用最近 5 帧的关键点做滑动平均再送对齐。或者用跟踪算法如 ByteTrack维持人脸 ID同一个 ID 只在第一帧做检测和对齐后续帧复用对齐参数。我一般用后者代码量不大但效果明显同一人相似度波动能从 0.3 压到 0.05 以内。4.2 现象底库注册时照片能检出人脸但识别时同一个人分数很低原因注册照片和识别时的人脸尺度差异太大。注册照片是手机自拍人脸占画面 80%识别时摄像头离得远人脸只占画面 10%。RetinaFace 检测后会把人脸区域缩放到 112×112尺度差异导致纹理细节丢失程度不同特征分布偏移。解决注册照片统一缩放到短边 640 像素确保人脸区域在 112×112 缩放后仍有足够细节。识别时如果人脸太小检测框短边小于 80 像素直接跳过不识别提示参会者靠近摄像头。我一般会在画面上画一个引导框人脸进入框内且尺寸达标才触发识别。4.3 现象戴口罩的参会者识别率骤降阈值调到 0.2 还是漏识原因ArcFace 训练集里戴口罩样本少口罩遮挡了鼻尖和嘴角关键点对齐时仿射变换误差大提取的特征和注册照片无口罩差异大。这不是阈值能解决的是域偏移问题。解决会议签到场景建议双模态——人脸识别为主工牌二维码为辅。戴口罩识别失败时提示刷工牌二维码后台关联身份后写入签到记录。如果一定要纯人脸可以用戴口罩人脸数据做微调但会议签到场景不值得为这个投入训练成本。我一般直接上双模态口罩场景下签到成功率从 60% 拉到 99%。4.4 现象签到记录里同一个人出现两条时间差 2 秒原因跳帧检测时同一个人在两帧都被检测到且两帧之间signed_in集合还没更新。多线程环境下更常见两个线程同时判断name not in signed_in都通过后都写入。解决用数据库唯一约束兜底UNIQUE(name, meeting_id)保证即使应用层去重失败数据库层也不会写重复。应用层用线程锁保护signed_in集合的读写。我一般两层都做数据库约束是最后一道防线。4.5 现象GPU 显存溢出跑半小时后程序崩溃原因app.get(frame)每次调用都会创建新的 ONNX Runtime session 吗不是但 OpenCV 的cap.read()返回的 frame 如果没有及时释放Python 垃圾回收跟不上显存里堆积大量中间张量。另外如果代码里在循环内反复创建FaceAnalysis实例每个实例都会加载模型到显存几个实例就把显存吃满。解决FaceAnalysis在循环外初始化一次循环内只调用app.get()。frame 处理完后显式del frame或者用with torch.no_grad()包住推理如果用 PyTorch 后端。ONNX Runtime 后端一般不会有显存泄漏但建议每处理 1000 帧打印一次显存占用用nvidia-smi监控。我一般会在主循环里加一个计数器每 5000 帧重启一次推理 session作为兜底。5. 进阶技巧用 FAISS 把底库检索从 O(n) 降到 O(1) 并支持动态增删底库人数少时遍历没问题但会议签到系统往往要跨会议复用底库人数累积到几千后每次识别遍历所有底库算余弦相似度单次耗时从 1ms 涨到 50ms签到体验明显变卡。FAISS 的IndexFlatIP用内积做相似度检索底层是 BLAS 优化的矩阵乘法千级底库检索耗时不到 1ms而且支持动态增删。import faiss import numpy as np import pickle class FaissFaceDB: def __init__(self, dim512): self.dim dim # IndexFlatIP 用内积做相似度L2 归一化后内积等于余弦相似度 self.index faiss.IndexFlatIP(dim) self.names [] # 索引到名字的映射 self.name_to_idx {} # 名字到索引的映射支持删除 def add(self, name, feature): 添加或更新一个人脸特征 feature feature.astype(float32).reshape(1, -1) if name in self.name_to_idx: # 已存在则先删除旧向量 self.remove(name) self.index.add(feature) idx self.index.ntotal - 1 self.names.append(name) self.name_to_idx[name] idx def remove(self, name): 删除一个人脸特征用 IDMap 或重建索引实现 if name not in self.name_to_idx: return # IndexFlatIP 不支持直接删除用重建方式 # 实际生产建议用 IndexIDMap 包装这里演示重建逻辑 all_features [] all_names [] for i in range(self.index.ntotal): feat self.index.reconstruct(i) all_features.append(feat) all_names.append(self.names[i]) # 重建索引跳过要删除的 self.index faiss.IndexFlatIP(self.dim) self.names [] self.name_to_idx {} for feat, n in zip(all_features, all_names): if n ! name: self.index.add(feat.reshape(1, -1)) self.names.append(n) self.name_to_idx[n] len(self.names) - 1 def search(self, feature, top_k1): 检索最相似的 top_k 个人脸 feature feature.astype(float32).reshape(1, -1) scores, indices self.index.search(feature, top_k) results [] for score, idx in zip(scores[0], indices[0]): if idx 0: results.append((self.names[idx], float(score))) return results def save(self, pathfaiss_db.pkl): with open(path, wb) as f: pickle.dump({ index: faiss.serialize_index(self.index), names: self.names, name_to_idx: self.name_to_idx }, f) def load(self, pathfaiss_db.pkl): with open(path, rb) as f: data pickle.load(f) self.index faiss.deserialize_index(data[index]) self.names data[names] self.name_to_idx data[name_to_idx] # 使用示例 db FaissFaceDB(dim512) for name, feat in 底库.items(): db.add(name, feat) # 检索 query_feat 底库[张三] # 用张三自己的特征做查询 results db.search(query_feat, top_k3) for name, score in results: print(f匹配: {name}, 相似度: {score:.4f}) # 动态删除 db.remove(张三) print(f删除后底库人数: {db.index.ntotal})逻辑说明IndexFlatIP用内积做相似度因为特征已经 L2 归一化内积等于余弦相似度。add时如果名字已存在先删除旧向量再添加保证底库不重复。remove用重建索引的方式实现因为IndexFlatIP不支持直接删除生产环境建议用IndexIDMap包装IndexFlatIP用 ID 管理增删。search返回 top_k 个最相似的人脸签到场景取 top_1 即可但保留 top_k 方便排查。参数说明dim512是 ArcFace 特征维度如果你换用其他识别模型维度可能不同需要对应修改。top_k1是签到场景的默认值调大可以看到更多候选方便分析误识原因。save和load用 FAISS 的序列化接口比 pickle 直接存 index 对象更稳定。提示FAISS 的IndexFlatIP在底库超过 10 万时检索耗时才会明显上升会议签到场景几千人完全够用。如果底库更大可以换IndexIVFFlat做近似检索但需要训练聚类中心复杂度更高。我自己的习惯是每次会议开始前用 FAISS 重建一次底库索引把历史签到记录里确认过的参会者特征合并进去这样下一场会议如果遇到同一批人识别速度更快。这个习惯帮我省了很多现场调试时间也避免了底库膨胀导致的检索变慢。希望帮到你。本文还有配套的精品资源点击获取
返回列表