简介这是一套基于OpenCV的人脸识别门禁系统完整项目面向计算机视觉学习者、嵌入式开发爱好者及课程设计、毕业设计学生资源以可运行门禁终端为载体完整覆盖人脸采集、检测、特征提取、识别认证、权限控制与日志记录源码采用C与Qt编写融合Haar级联与LBPH算法并通过串口库控制门禁硬件。压缩包共205个文件、约31.4MB以cpp/h源文件、jpg人脸样本、exe可执行程序及txt说明文档为主并含obj、tlog等编译中间文件。目前已有644人学习下载具备实际参考价值。对快速上手者可运行exe或打开工程直接调试对想深入原理者可借助源码与文档掌握人脸检测、特征提取、串口通信及界面集成的实现思路并在此基础改造自己的门禁系统。1. 基于OpenCV的人脸识别门禁系统不是高端AI是一套能落地的小工程要把基于OpenCV的人脸识别门禁系统跑起来难点从来不在“认脸”这一个环节而是把检测、比对、控制三条链路串在一起。用OpenCV加Python做门禁摄像头负责抓拍Haar或LBP级联负责定位人脸LBPH模型负责特征比对通过之后触发继电器或串口指令开锁整个过程本地运算、不依赖云端。这套方案解决的是办公室、实验室、小型机房这类中低并发通行控制问题适合刚接触人脸识别门禁系统设计的开发者也适合需要交付演示项目的在校生和集成商。下面按我实际落地过的方案把原理选型、可复现代码和踩过的坑一次讲透。2. 门禁系统设计与识别原理把“认脸”拆成三步判断一套用OpenCV搭起来的人脸门禁表面上看就一个动作识别成功就开门但代码里其实是三件事人脸检测、特征提取、特征比对。这三个模块顺序执行任何一个环节判断错了后面都不会有正确结果。我在写代码前一般先画一张数据流向图摄像头取帧检测到人脸区域把区域裁出来统一尺寸和灰度送进识别器预测返回置信度和标签门禁逻辑根据置信度决定动作。这张图看似简单但它决定了整个工程怎么组织。刚开始做门禁的人最常犯的错是把检测和识别混在一起。检测是找“这里有一张脸”识别是判断“这张脸是谁”。OpenCV里前者用CascadeClassifier类后者用LBPHFaceRecognizer或深度学习模型。检测的输出是一个矩形框识别的输出是标签加置信度两者分开训练、分开调用。如果上来就想用一个模型同时干这两件事你会发现训练样本不够用参数也互相打架最后连排错都不知道从哪里下手。2.1 人脸检测Haar级联与LBP级联选型看算力与场景OpenCV人脸检测最常用的是Haar级联分类器和LBP级联分类器对应haarcascade_frontalface_default.xml和lbpcascade_frontalface.xml两个文件。两者都是AdaBoost训练出的级联分类器差别在特征类型。Haar特征基于矩形区域的灰度差浮点运算多但对轮廓细节表达更细LBP特征基于局部二值模式主要是整数运算速度明显更快。在普通PC上这两者差距不大放到树莓派、RK3399这类嵌入式板子上就能拉开帧率。实际调参时抓住三个关键参数排错优先看这里。scaleFactor检测窗口每次缩放比例。默认1.1越小检测越精细计算量成倍上升到1.05时速度肉眼可见变慢。minNeighbors候选区域至少被连续确认多少次才算人脸。值越小误检越多值越大漏检越多。室内场景5到6比较合理室外场景可以调高到7到8。minSize最小人脸尺寸。低于这个尺寸的检测结果直接丢弃既能过滤远处的误检也能提升速度。室内灯光可控的门禁场景我一般默认用Haar部署在低功耗设备上就换LBP同时把检测帧从1080p缩到640x480帧率能从个位数提升到20帧以上。无论用哪种级联检测框都不能直接喂给识别器需要先用haarcascade_eye.xml检测眼睛坐标把人脸旋转到水平再裁剪。没有做对齐这一步识别率会大打折扣。对比项HaarLBP特征计算浮点灰度差整数纹理编码检测精度略高略低CPU开销高低侧脸与遮挡鲁棒性较好一般适用设备笔记本、工控机树莓派、嵌入式板在做对齐的过程中常用做法是在检测到的人脸区域里再跑一次眼睛检测拿到两个眼睛中心后计算旋转角用仿射变换把人脸摆正。代码骨架是这样import cv2 face_cascade cv2.CascadeClassifier(haarcascades/haarcascade_frontalface_default.xml) eye_cascade cv2.CascadeClassifier(haarcascades/haarcascade_eye.xml) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.1, 5, minSize(80, 80)) for (x, y, w, h) in faces: roi_gray gray[y:y h, x:x w] eyes eye_cascade.detectMultiScale(roi_gray, 1.1, 5) if len(eyes) 2: # 根据两只眼睛坐标求旋转角度再做仿射变换 angle calc_rotation_angle(eyes) aligned rotate_and_crop(roi_gray, angle) # aligned 才是送给识别器的输入这段代码里眼睛数量少于2就跳过当前人脸宁可不识别也不要喂一个歪脸进模型。对齐的目标是让双眼保持水平LBPH把图像分块后统计纹理直方图如果人脸倾斜网格划分出来的局部特征就和训练时的分布对不上。2.2 特征提取与比对LBPH为什么是门禁场景的默认选择OpenCV的face模块提供三种内置识别器EigenFaces、FisherFaces和LBPH很多教程会把它们一起介绍但门禁项目里真正稳得住的只有LBPH。EigenFaces用PCA把整张脸压缩成若干特征脸再用欧氏距离判断相似度。它对光照特别敏感同一个人的脸只要光线角度变了投影后的特征就漂移门禁这种一天里光照不断变化的场景基本是翻车状态。FisherFaces用LDA做类间判别把不同人的特征距离拉开效果比EigenFaces明显好但要求每个ID的训练样本数量接近否则判别方向会被样本多的ID带偏。LBPH的处理方式完全不同。它先把人脸图分成grid_x乘grid_y个小块每块计算一个局部二值模式直方图再拼成一个大特征向量最后用Chi-square距离比较两个特征向量输出一个置信度。这个设计让LBPH对整体光照变化不敏感对样本量的要求也低每个人15到30张正脸就能训练出可用模型。门禁场景的小样本、非受控光照、实时性要求LBPH正好都踩在点上。关于置信度我要多说一句LBPH输出的confidence是距离不是概率数值越小代表越相似。把“置信度大于80就通过”这种写法套进来的人十个有九个会翻车阈值方向搞反。第4章里我会给一个正确的判断写法。2.3 门禁控制状态机识别通过之后锁怎么开识别结果出来后不能直接驱动锁。摄像头每帧都会产生结果如果某一帧识别成功就开锁下一帧置信度波动判失败继电器就会在几秒内反复吸合断开机械触点和电源都会被拖坏。正确做法是加一个最简状态机识别成功一次后把开门状态置为True并记录时间接下来至少保持5到10秒这段窗口内不执行关锁指令窗口结束后复位状态重新进入检测循环。这个状态机不需要引入额外库一个布尔变量加一个time.time()时间戳就够。逻辑上用以下方式组织import time door_open False open_start 0 hold_seconds 8 if confidence threshold and label in valid_ids: if not door_open: open_door() door_open True open_start time.time() else: if door_open and time.time() - open_start hold_seconds: door_open False真正把锁打开根据硬件选型通常走两条路PC或树莓派上用串口发一条指令给继电器模块或者直接用GPIO引脚拉高电平驱动固态继电器。道逻辑上要保证识别成功和开锁动作之间是单向触发不要把检测结果直接映射到IO口上。从工程交付角度看识别通过后还要做三件事写入通行日志、记录人脸截图、对连续识别失败做计数告警。日志是门禁系统最容易被忽视的部分也是项目文档说明里最该写清楚的部分。没有日志后期想回溯谁在什么时间刷过门就只能靠翻监控那这个门禁就失去了大部分管理价值。3. 环境搭建与最小跑通OpenCV安装教程绕不开的包与依赖问题人脸识别门禁项目的第一步不是写识别逻辑而是把OpenCV环境装对。这一步看似简单却是首次跑通项目时最拖进度的环节报错集中在pip包选择、摄像头索引、级联文件路径三处。先把环境稳定下来后面训练和识别才有排错的基础。3.1 OpenCV安装教程pip包与contrib模块的区别在Python里安装OpenCV最常见的命令是pip install opencv-python但只装这一个包后面训练人脸模型时import cv2.face大概率直接报错因为LBPHFaceRecognizer、EigenFaceRecognizer这些类从OpenCV 3.x开始被挪进了opencv-contrib-python包。选型建议不是两个都装而是直接装contrib版。pip install opencv-contrib-python装完验证一下版本python -c import cv2; print(cv2.__version__)如果你机器上之前装过opencv-python建议先卸载两个包再装contrib否则两个包的文件残留可能互相覆盖表现为一部分模块存在、一部分模块报找不到属性。卸载命令是pip uninstall -y opencv-python opencv-contrib-python pip install opencv-contrib-python再补充两个细节。OpenCV 4.x要求Python 3.7以上如果你还在用比较老的解释器建议先从官网下新版本Python重装否则numpy版本和轮子兼容性都会变成拦路虎。另外安装时如果遇到numpy版本冲突把numpy升级到最新再装opencv即可这是OpenCV相关安装里最常见的兼容问题。很多教程会推荐conda install opencvconda源里的OpenCV往往比pip上的旧而且和pip安装的包混在一起容易出现动态库冲突。我一般习惯用虚拟环境加pip包管理干净、好卸载、后续出问题容易复现。如果你用VSCode做开发装完包后记得在VSCode右下角把Python解释器切到当前虚拟环境否则终端里import cv2成功编辑器里照样报No module named cv2这不是环境问题是解释器选错了。3.2 最小项目结构与摄像头实时人脸检测代码环境准备好之后先不急着写训练脚本而是建立一个干净的项目目录。我平时做门禁项目的最小结构是这样face_access/ ├── haarcascades/ # 级联分类器XML │ ├── haarcascade_frontalface_default.xml │ └── haarcascade_eye.xml ├── dataset/ # 人脸样本库 │ ├── person_1/ │ └── person_2/ ├── models/ # 训练出的模型文件 ├── capture_face.py ├── train_model.py └── access_control.py三个脚本各干一件事采集样本、训练模型、门禁识别。分开写的好处是任何一个环节出问题都能独立调试。比如采集到的样本质量不行只需要重跑采集脚本不需要动后面两个脚本。别把三个功能写进一个文件里后期不管是加用户还是改阈值都会很痛苦。下面这个脚本先验证摄像头和级联检测是否正常不加识别逻辑import cv2 cap cv2.VideoCapture(0) # 0 是第一个摄像头多摄像头时可改成 1 或 2 face_cascade cv2.CascadeClassifier(haarcascades/haarcascade_frontalface_default.xml) while True: ret, frame cap.read() if not ret: break # 读不到帧说明摄像头被占用或索引错了 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.1, 5, minSize(80, 80)) for (x, y, w, h) in faces: cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imshow(face_detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()代码逻辑是固定的四步读取摄像头帧、转灰度、检测人脸、画框显示。灰度转换是因为detectMultiScale只接受单通道输入彩色图传进去会被OpenCV内部自动转一遍白白浪费一次转换开销。detectMultiScale用scaleFactor1.1、minNeighbors5、minSize(80, 80)这个组合在室内普通摄像头下比较均衡。如果这一步跑通了说明环境、摄像头、级联文件三个关键点都没问题。如果没出画面先看ret是否为False再检查CascadeClassifier加载是否成功。这里有个很隐蔽的坑CascadeClassifier加载不存在的路径时不会抛异常而是静默返回空分类器detectMultiScale执行时会报“error: (-215:Assertion failed) !empty()”。新人第一次遇见这个报错十有八九会以为是OpenCV坏了其实是XML路径写错或者文件没放到指定目录。3.3 摄像头调用与逐帧检测的性能边界VideoCapture(0)看起来简单但摄像头索引不是永远从0开始。笔记本自带摄像头加USB摄像头时有的系统把USB摄像头编成0自带摄像头变成1需要逐个试。另外cap.read()是阻塞读取如果摄像头分辨率被调得过高比如4K单帧读取延迟就会明显拖慢循环。实际运行时还要注意detectMultiScale在1080p帧上做全尺寸检测CPU占用很高。常见做法是先把帧缩放到640x480再做检测检测框坐标按缩放比例映射回原图。我一般这样处理scale 0.5 small_frame cv2.resize(frame, (0, 0), fxscale, fyscale) faces face_cascade.detectMultiScale(small_frame, 1.1, 5, minSize(60, 60)) for (x, y, w, h) in faces: x, y, w, h int(x / scale), int(y / scale), int(w / scale), int(h / scale)用缩放帧做检测速度能提升三四倍代价是远处的小脸可能检不到。门禁场景人脸距离一般在一米到两米以内这个代价可以接受。还有人会加“隔帧检测”策略上一帧检测到人脸就先沿用旧坐标两三帧后再重新检测一次这能进一步压低CPU占用但要注意人脸快速移动时会画出滞后框适合固定点位门禁不适合大范围动态场景。4. 训练自己的门禁人脸库样本采集、LBPH模型训练与门禁联动环境通了检测能出框接下来就是门禁系统的核心部分给每个用户建档采集他的多张人脸样本训练一个专属识别模型再把这个模型接到门禁逻辑上。LBPH不要求海量数据但要求样本覆盖角度合理这一步的质量直接决定识别到底能用还是不能用。4.1 人脸样本采集脚本录入环节的关键参数采集样本的过程本质上是给每个用户拍一套灰度证件照。脚本做的事很简单摄像头取帧、检测人脸、裁剪人脸、统一尺寸、把灰度图存到以ID命名的目录下。import cv2 import os face_cascade cv2.CascadeClassifier(haarcascades/haarcascade_frontalface_default.xml) person_id 1 # 每个用户固定一个编号训练和识别都靠这个编号 save_dir fdataset/person_{person_id} os.makedirs(save_dir, exist_okTrue) cap cv2.VideoCapture(0) count 0 while count 50: ret, frame cap.read() if not ret: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.3, 5, minSize(100, 100)) for (x, y, w, h) in faces: face gray[y:y h, x:x w] face cv2.resize(face, (200, 200)) cv2.imwrite(os.path.join(save_dir, f{count}.jpg), face) count 1 print(fsaved {count})几个参数要解释清楚。为什么用灰度图LBPH训练本来就是基于灰度纹理特征彩色信息用不上提前转灰度能省一半内存和计算量。为什么要resize成200x200因为训练和识别阶段读入的图片尺寸必须一致否则特征向量长度对不上predict会报错或者结果没意义。为什么采50张而不是5张不是为了凑数量而是为了覆盖人脸角度变化转头、抬头、微笑、换眼镜都要采进去后面识别时才不会因为姿势稍有变化就拒识。采集时有一个容易忽略的点让用户坐在门禁机位置上头部在画面里缓慢左右转动每次转动停顿一两秒再保存这样样本里既包含正面也有轻微侧面。如果只是站在摄像头前连续保存50张正脸效果和保存5张没有本质区别模型一点泛化能力都没学到。样本质量比数量重要得多。4.2 训练LBPH模型与调参radius、neighbors、grid参数的边界样本采完就可以训练。训练脚本把dataset目录下所有person_x子目录扫一遍子目录名里的数字作为标签读取里面的灰度图整理成训练数据和标签列表然后创建LBPH识别器并训练。import cv2 import os import numpy as np faces [] labels [] for person_dir in os.listdir(dataset): if not person_dir.startswith(person_): continue label int(person_dir.split(_)[1]) person_path os.path.join(dataset, person_dir) for file_name in os.listdir(person_path): if not file_name.endswith(.jpg): continue img cv2.imread(os.path.join(person_path, file_name), cv2.IMREAD_GRAYSCALE) faces.append(img) labels.append(label) recognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8 ) recognizer.train(faces, np.array(labels)) recognizer.save(models/lbph_model.yml) print(model saved)LBPH创建时的四个参数是调优关键逐个说边界。radius1LBP采样的邻域半径。默认值是1捕捉的是紧邻像素的纹理变化改成2覆盖范围更大对模糊更鲁棒但细节纹理丢失我实测下来还是1稳。neighbors8每个像素采样的邻域点数。8对应标准LBP的8邻域点数越多直方图越复杂样本量少时降到6能减少过拟合但特征区分度会下降。grid_x和grid_y把图像分成多少块计算局部直方图。默认8x8是均衡选择分块越多对局部细节越敏感但受对齐误差影响也越大如果人脸对齐做得好可以用10x10否则维持8x8。模型训练完保存成YAML文件识别阶段用recognizer.read()加载不用每次启动都重新训练。如果你只训练了一个用户训练能跑通但识别器没有任何“其他人”的概念任何人靠近都会判成这个用户。门禁至少要有两个用户以上做交叉验证并在正式接入前收集一些陌生人样本专门测试误识别比例这部分在第6章会讲。数据是黑匣子训练出来的模型一定要保留原始采集样本。后期如果识别效果不对十有八九要回到样本层排查删了样本等于给自己断了后路后悔药都没得吃。4.3 识别与门禁联动置信度阈值与继电器控制训练完成后识别脚本加载模型对画面里的每张人脸调用predict得到标签和置信度再把置信度跟阈值比较。这里再强调一次LBPH的置信度是距离数值越小代表越相似不是概率别把它当成“置信度大于80就通过”。import cv2 recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(models/lbph_model.yml) face_cascade cv2.CascadeClassifier(haarcascades/haarcascade_frontalface_default.xml) valid_ids {1, 2} # 允许通行的用户编号 threshold 80 # 置信度阈值越小越严格 def open_door(): # 串口发指令或GPIO拉高具体看硬件 print(door opened) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.1, 5, minSize(80, 80)) for (x, y, w, h) in faces: face gray[y:y h, x:x w] face cv2.resize(face, (200, 200)) label, confidence recognizer.predict(face) if confidence threshold and label in valid_ids: cv2.putText(frame, fPASS {label}, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) open_door() else: cv2.putText(frame, DENIED, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.imshow(access_control, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()predict的输入和训练时一样必须是灰度图并且已经resize到200x200。valid_ids过滤是必须写的训练时有几个标签predict就可能输出几个标签不校验的话任何被模型记住的用户都能开门。阈值80是我在室内恒定灯光下常用的起点实际部署要按第6章的方法统计校准别拿一个固定阈值走天下。这里还要注意代码里的open_door被调用后状态复位要交给第2.3节的状态机处理。如果直接把open_door塞在识别成功分支里每帧都会触发开锁门禁继电器很快就会被烧坏。采集、训练、识别三个循环的逻辑边界清晰排错时才能快速定位是哪一环节出了问题。5. 门禁落地避坑指南5个让系统翻车的现场与修复代码能跑通只是第一步真正让门禁系统显得不专业的往往是下面这些运行期问题。我按实际踩过的顺序写每一条都按现象、原因、解决的顺序来做。5.1 摄像头画面卡顿帧率掉到个位数现象打开识别窗口后画面明显不跟手人一走动全是残影CPU占用接近100%。把scaleFactor调成1.05后卡顿尤其明显。原因detectMultiScale在每一帧上执行多尺度滑窗检测1080p图像配合精细缩放计算量呈指数级上升。门禁脚本里如果有多个独立循环同时做检测和识别CPU压力会翻倍。解决把检测帧缩放到640x480再检测同时不要每帧都跑检测用隔帧检测策略。定义一个frame_count变量取模3等于0才执行detectMultiScale中间几帧沿用上一次的检测框。实测把1080p缩小到640x480后CPU占用能降一半隔帧策略再省三分之一帧率稳定在25帧以上。5.2 同一人识别置信度忽高忽低现象同一个用户站在同一个位置confidence一会是50一会跳到120表现就是偶尔能开门偶尔不能。原因检测框大小在抖人脸在画面里稍微移动裁剪出的区域就包含不同比例的额头和下巴。更关键的是没做对齐稍微侧脸的人脸在LBPH特征空间里跟正面样本差距很大。解决先做眼睛检测根据两只眼睛的坐标把人脸旋转到水平再统一裁剪到200x200。这一步不是可选项是LBPH识别的基本前提训练样本里还要覆盖左右转头约15度的姿态单纯的正脸数据集扛不住门禁现场的姿势变化。5.3 逆光场景下完全认不出人现象门禁装在窗边或朝走廊的墙面上下午阳光照进走廊时人脸区域过曝识别成功率从95%掉到30%以下有时连人脸检测都直接漏掉。原因灰度图在高光比环境下人脸暗部细节被压缩LBP纹理特征几乎丢失。这个问题和识别算法本身关系不大问题出在前端图像质量。解决在送入检测和识别前对灰度图做CLAHE对比度受限的自适应直方图均衡化把局部对比度拉出来。clahe cv2.createCLAHE(clipLimit3.0, tileGridSize(8, 8)) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray clahe.apply(gray)clipLimit控制增强强度2到3比较安全过大会把噪声一起放大。这一步是室外光线场景最值得先做的改动成本几乎为零收益却很明显。5.4 照片和手机屏幕也能开门现象把打印的照片或手机里的照片对准摄像头直接识别通过。LBPH做识别时这个现象非常普遍因为特征距离和真人几乎一样。原因LBPH只比较静态纹理特征没有活体判断能力它无法区分眼前是真实人脸还是一张平面照片。解决低成本方案是加眨眼检测第6章展开。更省事的方案是让门禁机和用户做一次交互识别通过后要求用户在窗口内眨一下眼或缓慢摇头用关键点变化判断动作是否真实发生。再往上走就是加红外或ToF深度摄像头成本高出数倍一般用在金融级场景普通项目没必要。5.5 cv2.face模块不存在现象import cv2.face报错ModuleNotFoundError: No module named cv2.face或者AttributeError: module cv2 has no attribute face。原因装的是opencv-python而不是opencv-contrib-pythonface模块不在主包里。也有情况是两个包同时装了文件相互覆盖导致模块时有时无。解决先卸载两个包再重装contrib版。pip uninstall -y opencv-python opencv-contrib-python pip install opencv-contrib-python装完重启Python解释器再跑脚本。如果用了VSCode还要确认解释器已经切到当前虚拟环境之前遇到过终端里import正常、编辑器里报红的场景最后发现是解释器路径没对上跟包安装本身没关系。6. 进阶验证与阈值校准让系统从“能跑”到“敢用”门禁系统最怕的不是技术不够炫而是误放行和拒真。做过真实验收的人都知道现场试三两次通过不代表能用。这一章讲两个提升可靠性的实践眨眼活体检测和阈值校准。6.1 眨眼活体检测用EAR算法拦住照片EAR是眼睛纵横比的缩写定义为眼睛关键点垂直距离与水平距离的比值。人眼睁开时EAR在0.25到0.3之间闭眼时会降到0.1以下。连续几帧检测到EAR低于0.2就记为一次眨眼。这个比值不依赖关键点的绝对尺度解析出眼睛轮廓就能用。在OpenCV框架里做EAR先用dlib或OpenCV自带的68点人脸关键点检测器取出眼睛轮廓的6个关键点代入公式计算。实现时我只在识别通过后再等待一次眨眼动作连续50帧内检测到两次眨眼才触发开锁。打印照片是静止的不会产生眨眼序列这一步能把照片攻击挡住一大半。6.2 阈值校准用正负样本找FRR与FAR的平衡点threshold设多少直接决定系统偏严还是偏松。误识率FAR和拒真率FRR相互拉扯threshold压得越低陌生人越容易被拒但本人也可能被拒threshold放得越宽本人容易通过陌生人混进来的概率也变大。我用的校准方法是先收集数据再画数轴让三个本人在正常光照下各刷20次记录所有通过样本的置信度区间再让三个访客各刷20次记录所有拒绝样本的置信度区间。如果两个区间有重叠就在重叠区域里取中间值如果完全分开就取两个区间中点偏严格的一侧。pass_conf_values [] # 本人样本的置信度列表 stranger_conf_values [] # 陌生人样本的置信度列表 max_pass max(pass_conf_values) min_stranger min(stranger_conf_values) if max_pass min_stranger: threshold int((max_pass min_stranger) / 2) else: threshold int(min_stranger) # 宁可多拒也不误放这套统计方法比靠感觉拍阈值可靠得多。我最后一次调门禁阈值时吃过一次亏一开始把threshold拍脑袋设成60结果实验室几个同事频繁刷不开门后来老老实实统计了一百次正负样本发现最优值应该在75到85之间。从那之后再接门禁项目我都会先花半小时把样本统计做完再写正式阈值这个流程现在也写进了项目文档说明的验收清单里。希望帮到你。本文还有配套的精品资源点击获取