简介:这份资源面向具备Python基础、希望入门机器视觉与人脸识别的开发者与学习者,以OpenCV为核心工具,围绕明星人脸检测与识别这一具体场景展开实战。压缩包共120个文件,以110张jpg与5张jpeg人脸样本图片为主体,另含2个Haar级联分类器xml模型文件、1个Python源码脚本、1个说明txt和1张png,整体约51.99MB,图片用于训练与测试,xml提供预训练检测模型,py脚本串联完整流程。内容覆盖Haar特征与级联分类器原理、灰度化与直方图均衡化等预处理、detectMultiScale人脸定位,以及EigenFace、FisherFace、LBPH等特征提取与匹配方法,并涉及用cv2.ml或sklearn训练自定义识别模型。已有459人学习下载,适合作为理解人脸检测识别流程、动手调参与排错的实践素材。1. 从一堆散装脚本到能跑的人脸识别:这份 OpenCV 实战包到底装了什么很多人第一次做人脸识别,卡住的地方根本不是算法,而是环境。装完 OpenCV 发现cv2.face模块不存在,或者摄像头一开就报设备被占用,再或者拿到的代码里路径全是作者本机的绝对路径。这份opencv人脸识别实战操作.zip解决的就是这类问题——它把机器视觉里最经典的人脸检测、特征提取、识别比对串成了一条能直接跑的链路,而不是丢给你一堆零散函数。适合两类人:一是刚接触人工智能、想用 Python 把摄像头跑起来的入门者;二是做过图像处理、但没系统走过人脸识别全流程的工程师。它不涉及深度学习训练,核心是 OpenCV 自带的 Haar 级联和 LBPH 算法,好处是 CPU 就能跑、依赖少、代码透明,坏处也明显——光照和角度一变就翻车。下面我按实际拆包顺序讲清楚怎么用、参数怎么调、坑在哪。2. 环境与依赖:把 opencv-contrib 装对,别在第一步就翻车2.1 为什么必须是 opencv-contrib 而不是普通 opencv人脸识别模块cv2.face属于 OpenCV 的 contrib 扩展库,普通pip install opencv-python装出来的包里根本没有这个模块。这是新手最常见的翻车点:代码里写着recognizer = cv2.face.LBPHFaceRecognizer_create(),一运行就报AttributeError: module 'cv2' has no attribute 'face'。原因不是代码错,是包装错了。常见做法是直接装 contrib 版本,它包含了主库加扩展模块:# 卸载可能已存在的普通版本,避免两个包冲突 pip uninstall opencv-python opencv-contrib-python -y # 安装带 contrib 的完整版,人脸识别模块就在里面 pip install opencv-contrib-python # 验证 face 模块是否可用 python -c "import cv2; print(cv2.__version__); print(hasattr(cv2, 'face'))"逻辑说明:先卸载是为了防止opencv-python和opencv-contrib-python同时存在时,Python 导入到没有 face 模块的那个。最后一行验证会打印版本号和True,只要出现False就说明装的还是普通版。参数上不用指定版本号,除非你的项目对某个旧 API 有依赖,那种情况再锁版本。提示:如果你用的是 conda 环境,对应命令是conda install -c conda-forge opencv,但 conda 的包有时 contrib 模块裁剪过,装完同样要用上面那行验证。2.2 摄像头与文件路径的初始化检查环境装好后,第二步是确认视频源能用。OpenCV 读摄像头用cv2.VideoCapture(0),读视频文件把 0 换成路径。这里有个容易被忽略的点:VideoCapture创建失败不会抛异常,而是返回一个对象,isOpened()才是False。很多人不检查直接read(),拿到的是空帧,后面全崩。import cv2 # 0 表示默认摄像头,笔记本通常是内置那个 cap = cv2.VideoCapture(0) # 必须显式检查,创建失败不会报错 if not cap.isOpened(): print("摄像头打开失败,检查是否被其他程序占用") exit() # 设置分辨率,不是所有摄像头都支持任意分辨率 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame = cap.read() if not ret: print("读取帧失败,可能是设备断开") break cv2.imshow("frame", frame) # 按 q 退出,waitKey 必须调用否则窗口不刷新 if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:cap.set设置的分辨率是请求值,摄像头可能返回最接近的可用值,所以别假设读到的帧一定是你设的尺寸,后面做人脸检测前最好用frame.shape确认一下。waitKey(1)里的 1 是毫秒,值越小循环越快,但太小会导致窗口无响应,一般 1 到 30 之间。参数上,如果你有多个摄像头,把 0 换成 1、2 逐个试。3. 人脸检测:Haar 级联的参数怎么调才不误检3.1 detectMultiScale 四个参数的实际含义检测环节用的是 Haar 级联分类器,OpenCV 自带训练好的 XML 文件,比如haarcascade_frontalface_default.xml。核心函数是detectMultiScale,它返回一组矩形框,每个框是一个人脸位置。这个函数有四个关键参数,调不好就是漏检或满屏误检。import cv2 # 加载人脸检测器,路径来自 OpenCV 安装目录的 data 文件夹 face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) # 确认分类器加载成功,空对象检测时会报错 if face_cascade.empty(): raise IOError("级联分类器加载失败,检查路径") img = cv2.imread("test.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 关键参数:缩放步长、最小邻居数、最小尺寸 faces = face_cascade.detectMultiScale( gray, scaleFactor=1.1, # 每次图像缩小比例 minNeighbors=5, # 一个候选框周围至少有几个邻居才保留 minSize=(30, 30) # 小于这个尺寸的不当人脸 ) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imshow("result", img) cv2.waitKey(0) cv2.destroyAllWindows()逻辑说明:scaleFactor=1.1表示每次把图像缩小 10% 再检测,值越小检测越细但越慢,1.05 到 1.2 是常用区间。minNeighbors控制误检,值越大越严格,漏检也越多,5 是平衡点,人脸密集场景可以降到 3。minSize用来过滤远处的小噪点,设太小会把纹理误判成人脸。参数没有万能值,得拿你的实际画面试。3.2 灰度转换与直方图均衡化的作用Haar 特征是在灰度图上算的,所以必须先cvtColor转灰度。但只转灰度还不够,光照不均时检测率会掉。常见做法是加一步直方图均衡化,把对比度拉开。# 转灰度后做均衡化,改善背光或过曝场景 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) # 用均衡化后的图检测 faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(30, 30))逻辑说明:equalizeHist把像素值重新分布,让暗部细节更清楚。代价是噪声也会被放大,如果画面本身噪点多,均衡化后误检可能增加,这时候要么先做高斯模糊,要么干脆不加这一步。我一般会两种都跑一遍,看哪种框得准。注意:Haar 检测对正脸有效,侧脸、低头、遮挡基本检不到。如果你的场景人脸角度变化大,这套方案本身就不合适,得换基于深度学习的方法,但那就超出这份资源范围了。4. 训练与识别:LBPH 的样本采集和阈值设定4.1 采集样本时的命名规范与数量LBPH 是局部二值模式直方图,属于传统机器学习方法,需要先用带标签的人脸图训练模型。这份资源里的流程通常是:给每个人采集一批人脸图,存成label.序号.jpg的格式,label 是整数编号。采集时最容易犯的错是数量不够或角度单一。import cv2 import os face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) # 用户编号,采集前手动指定 user_id = 1 save_dir = "dataset" os.makedirs(save_dir, exist_ok=True) cap = cv2.VideoCapture(0) count = 0 while count < 30: # 每人采集 30 张 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: count += 1 # 只保留人脸区域,减少背景干扰 face_roi = gray[y:y + h, x:x + w] # 统一尺寸,LBPH 对尺寸不敏感但统一后更稳 face_roi = cv2.resize(face_roi, (200, 200)) cv2.imwrite(f"{save_dir}/{user_id}.{count}.jpg", face_roi) cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imshow("collect", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:minSize=(80, 80)比检测时设得大,是为了保证采集到的人脸有足够像素,太小的图训练出来没意义。每人 30 张是经验值,太少模型不稳,太多训练变慢。采集时要让人稍微转动头部、变换表情,覆盖不同状态,否则模型只认采集时那个姿势。文件名里的序号从 1 递增,训练脚本靠文件名前缀解析 label。4.2 训练模型与置信度阈值采集完就该训练了。LBPH 训练很快,CPU 几秒到几十秒。训练完保存成 yml 文件,识别时加载。识别返回两个值:label 和 confidence。这里有个反直觉的点——confidence 在 LBPH 里是距离,值越小表示越像,不是概率。import cv2 import numpy as np from PIL import Image import os # 读取数据集,解析 label path = "dataset" recognizer = cv2.face.LBPHFaceRecognizer_create() faces = [] ids = [] for file in os.listdir(path): if file.endswith(".jpg"): img_path = os.path.join(path, file) # 转灰度,LBPH 只吃灰度图 gray_img = Image.open(img_path).convert('L') img_np = np.array(gray_img, 'uint8') # 文件名格式 label.序号.jpg,取第一段做标签 label = int(file.split(".")[0]) faces.append(img_np) ids.append(label) recognizer.train(faces, np.array(ids)) recognizer.save("trainer.yml") print("训练完成,样本数:", len(faces))逻辑说明:Image.open().convert('L')是转灰度的稳妥写法,比cv2.imread(..., 0)在某些格式上更可靠。np.array(..., 'uint8')保证数据类型一致,否则训练会报类型错误。保存的trainer.yml就是模型文件,识别时用recognizer.read()加载。识别阶段的阈值设定是玄学重灾区:recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read("trainer.yml") # 识别时判断置信度,小于 50 认为可信 label, confidence = recognizer.predict(gray_face) if confidence < 50: print(f"识别为 {label},置信度 {confidence:.1f}") else: print("未知人脸")逻辑说明:confidence 是距离值,0 表示完全一样,越大越不像。50 这个阈值不是固定的,取决于你的样本质量和人数。人少时阈值可以设 40 到 60,人多时要收紧到 30 左右,否则容易认错。我一般会拿几张没参与训练的照片测一遍,看已知人脸的 confidence 分布,再定阈值。5. 避坑与排查:五个真实踩过的坑5.1 现象:检测框满屏乱跳,人脸反而没框住原因:minNeighbors设得太低,或者画面噪声大导致 Haar 把纹理当人脸。解决:先把minNeighbors提到 6 到 8,再加一步高斯模糊cv2.GaussianBlur(gray, (5, 5), 0)压噪。如果还乱跳,检查是不是把scaleFactor设成了 1.0,那会导致计算量爆炸且误检增多。5.2 现象:训练时报 "labels must be integers"原因:从文件名解析 label 时拿到了字符串,或者文件名格式不统一,比如有的叫1.jpg有的叫1.1.jpg,split后取到的段不对。解决:统一命名规范,解析后强制int()转换,并在训练前打印一遍 ids 列表确认全是整数。5.3 现象:识别时所有人都被判成同一个人原因:样本里某个人占了绝大多数,或者采集时背景没变化,LBPH 学到了背景而不是人脸。解决:保证每人样本数接近,采集时换几个位置和光照。另外检查是不是忘了resize到统一尺寸,尺寸差异过大会让直方图对不齐。5.4 现象:摄像头读几帧后就卡死原因:waitKey没调用或参数为 0,导致 GUI 事件不处理;或者cap.read()返回的帧没及时释放,缓冲区堆积。解决:确保循环里有cv2.waitKey(1),并且不要在循环里做耗时操作,比如每帧都存图。如果用的是笔记本,检查是不是被其他会议软件占用了摄像头。5.5 现象:换台机器跑,路径全报错原因:代码里写了作者本机的绝对路径,比如C:/Users/xxx/dataset。解决:全部改成相对路径,用os.path.join拼接,数据集和模型文件放在脚本同级目录。这份资源里的脚本如果带绝对路径,第一件事就是全局替换成相对路径。6. 进阶技巧:用置信度分布反推阈值,而不是拍脑袋阈值定不好,识别要么认错要么全判未知。与其凭感觉设 50,不如用数据说话。做法是:拿一批已知标签的测试图,跑一遍predict,把每个人的 confidence 收集起来,看分布。已知人脸的 confidence 应该集中在一个区间,未知人脸则普遍偏高。两条分布的交叠处就是阈值该放的位置。import cv2 import numpy as np import os recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read("trainer.yml") face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) # 收集已知人脸的置信度 known_scores = [] test_dir = "test_known" for file in os.listdir(test_dir): img = cv2.imread(os.path.join(test_dir, file)) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.1, 5) for (x, y, w, h) in faces: roi = cv2.resize(gray[y:y+h, x:x+w], (200, 200)) label, conf = recognizer.predict(roi) known_scores.append(conf) # 收集未知人脸的置信度 unknown_scores = [] test_dir2 = "test_unknown" for file in os.listdir(test_dir2): img = cv2.imread(os.path.join(test_dir2, file)) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.1, 5) for (x, y, w, h) in faces: roi = cv2.resize(gray[y:y+h, x:x+w], (200, 200)) label, conf = recognizer.predict(roi) unknown_scores.append(conf) print("已知人脸置信度:均值 %.1f,最大 %.1f" % (np.mean(known_scores), np.max(known_scores))) print("未知人脸置信度:均值 %.1f,最小 %.1f" % (np.mean(unknown_scores), np.min(unknown_scores)))逻辑说明:如果已知人脸的 max 是 45,未知人脸的 min 是 60,那阈值设在 50 到 55 之间就合理。如果两者交叠严重,说明模型区分度不够,得回去补样本或换特征。这个方法的代价是需要准备两组测试图,但比反复试参数靠谱得多。场景建议阈值说明人数少于 5 人50 到 60类间差异大,可以放宽人数 5 到 20 人35 到 50需要平衡误识和拒识人数超过 20 人25 到 35类间距离近,必须收紧光照变化大在基础上减 5置信度整体偏高,阈值要降从那以后我每次调 LBPH 阈值,都强制先跑一遍置信度分布,不再靠猜。这套流程虽然土,但比盲目试参数省时间。希望帮到你。本文还有配套的精品资源,点击获取
简介:这份资源面向具备Python基础、希望入门机器视觉与人脸识别的开发者与学习者,以OpenCV为核心工具,围绕明星人脸检测与识别这一具体场景展开实战。压缩包共120个文件,以110张jpg与5张jpeg人脸样本图片为主体,另含2个Haar级联分类器xml模型文件、1个Python源码脚本、1个说明txt和1张png,整体约51.99MB,图片用于训练与测试,xml提供预训练检测模型,py脚本串联完整流程。内容覆盖Haar特征与级联分类器原理、灰度化与直方图均衡化等预处理、detectMultiScale人脸定位,以及EigenFace、FisherFace、LBPH等特征提取与匹配方法,并涉及用cv2.ml或sklearn训练自定义识别模型。已有459人学习下载,适合作为理解人脸检测识别流程、动手调参与排错的实践素材。1. 从一堆散装脚本到能跑的人脸识别:这份 OpenCV 实战包到底装了什么很多人第一次做人脸识别,卡住的地方根本不是算法,而是环境。装完 OpenCV 发现cv2.face模块不存在,或者摄像头一开就报设备被占用,再或者拿到的代码里路径全是作者本机的绝对路径。这份opencv人脸识别实战操作.zip解决的就是这类问题——它把机器视觉里最经典的人脸检测、特征提取、识别比对串成了一条能直接跑的链路,而不是丢给你一堆零散函数。适合两类人:一是刚接触人工智能、想用 Python 把摄像头跑起来的入门者;二是做过图像处理、但没系统走过人脸识别全流程的工程师。它不涉及深度学习训练,核心是 OpenCV 自带的 Haar 级联和 LBPH 算法,好处是 CPU 就能跑、依赖少、代码透明,坏处也明显——光照和角度一变就翻车。下面我按实际拆包顺序讲清楚怎么用、参数怎么调、坑在哪。2. 环境与依赖:把 opencv-contrib 装对,别在第一步就翻车2.1 为什么必须是 opencv-contrib 而不是普通 opencv人脸识别模块cv2.face属于 OpenCV 的 contrib 扩展库,普通pip install opencv-python装出来的包里根本没有这个模块。这是新手最常见的翻车点:代码里写着recognizer = cv2.face.LBPHFaceRecognizer_create(),一运行就报AttributeError: module 'cv2' has no attribute 'face'。原因不是代码错,是包装错了。常见做法是直接装 contrib 版本,它包含了主库加扩展模块:# 卸载可能已存在的普通版本,避免两个包冲突 pip uninstall opencv-python opencv-contrib-python -y # 安装带 contrib 的完整版,人脸识别模块就在里面 pip install opencv-contrib-python # 验证 face 模块是否可用 python -c "import cv2; print(cv2.__version__); print(hasattr(cv2, 'face'))"逻辑说明:先卸载是为了防止opencv-python和opencv-contrib-python同时存在时,Python 导入到没有 face 模块的那个。最后一行验证会打印版本号和True,只要出现False就说明装的还是普通版。参数上不用指定版本号,除非你的项目对某个旧 API 有依赖,那种情况再锁版本。提示:如果你用的是 conda 环境,对应命令是conda install -c conda-forge opencv,但 conda 的包有时 contrib 模块裁剪过,装完同样要用上面那行验证。2.2 摄像头与文件路径的初始化检查环境装好后,第二步是确认视频源能用。OpenCV 读摄像头用cv2.VideoCapture(0),读视频文件把 0 换成路径。这里有个容易被忽略的点:VideoCapture创建失败不会抛异常,而是返回一个对象,isOpened()才是False。很多人不检查直接read(),拿到的是空帧,后面全崩。import cv2 # 0 表示默认摄像头,笔记本通常是内置那个 cap = cv2.VideoCapture(0) # 必须显式检查,创建失败不会报错 if not cap.isOpened(): print("摄像头打开失败,检查是否被其他程序占用") exit() # 设置分辨率,不是所有摄像头都支持任意分辨率 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame = cap.read() if not ret: print("读取帧失败,可能是设备断开") break cv2.imshow("frame", frame) # 按 q 退出,waitKey 必须调用否则窗口不刷新 if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:cap.set设置的分辨率是请求值,摄像头可能返回最接近的可用值,所以别假设读到的帧一定是你设的尺寸,后面做人脸检测前最好用frame.shape确认一下。waitKey(1)里的 1 是毫秒,值越小循环越快,但太小会导致窗口无响应,一般 1 到 30 之间。参数上,如果你有多个摄像头,把 0 换成 1、2 逐个试。3. 人脸检测:Haar 级联的参数怎么调才不误检3.1 detectMultiScale 四个参数的实际含义检测环节用的是 Haar 级联分类器,OpenCV 自带训练好的 XML 文件,比如haarcascade_frontalface_default.xml。核心函数是detectMultiScale,它返回一组矩形框,每个框是一个人脸位置。这个函数有四个关键参数,调不好就是漏检或满屏误检。import cv2 # 加载人脸检测器,路径来自 OpenCV 安装目录的 data 文件夹 face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) # 确认分类器加载成功,空对象检测时会报错 if face_cascade.empty(): raise IOError("级联分类器加载失败,检查路径") img = cv2.imread("test.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 关键参数:缩放步长、最小邻居数、最小尺寸 faces = face_cascade.detectMultiScale( gray, scaleFactor=1.1, # 每次图像缩小比例 minNeighbors=5, # 一个候选框周围至少有几个邻居才保留 minSize=(30, 30) # 小于这个尺寸的不当人脸 ) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imshow("result", img) cv2.waitKey(0) cv2.destroyAllWindows()逻辑说明:scaleFactor=1.1表示每次把图像缩小 10% 再检测,值越小检测越细但越慢,1.05 到 1.2 是常用区间。minNeighbors控制误检,值越大越严格,漏检也越多,5 是平衡点,人脸密集场景可以降到 3。minSize用来过滤远处的小噪点,设太小会把纹理误判成人脸。参数没有万能值,得拿你的实际画面试。3.2 灰度转换与直方图均衡化的作用Haar 特征是在灰度图上算的,所以必须先cvtColor转灰度。但只转灰度还不够,光照不均时检测率会掉。常见做法是加一步直方图均衡化,把对比度拉开。# 转灰度后做均衡化,改善背光或过曝场景 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) # 用均衡化后的图检测 faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(30, 30))逻辑说明:equalizeHist把像素值重新分布,让暗部细节更清楚。代价是噪声也会被放大,如果画面本身噪点多,均衡化后误检可能增加,这时候要么先做高斯模糊,要么干脆不加这一步。我一般会两种都跑一遍,看哪种框得准。注意:Haar 检测对正脸有效,侧脸、低头、遮挡基本检不到。如果你的场景人脸角度变化大,这套方案本身就不合适,得换基于深度学习的方法,但那就超出这份资源范围了。4. 训练与识别:LBPH 的样本采集和阈值设定4.1 采集样本时的命名规范与数量LBPH 是局部二值模式直方图,属于传统机器学习方法,需要先用带标签的人脸图训练模型。这份资源里的流程通常是:给每个人采集一批人脸图,存成label.序号.jpg的格式,label 是整数编号。采集时最容易犯的错是数量不够或角度单一。import cv2 import os face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) # 用户编号,采集前手动指定 user_id = 1 save_dir = "dataset" os.makedirs(save_dir, exist_ok=True) cap = cv2.VideoCapture(0) count = 0 while count < 30: # 每人采集 30 张 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: count += 1 # 只保留人脸区域,减少背景干扰 face_roi = gray[y:y + h, x:x + w] # 统一尺寸,LBPH 对尺寸不敏感但统一后更稳 face_roi = cv2.resize(face_roi, (200, 200)) cv2.imwrite(f"{save_dir}/{user_id}.{count}.jpg", face_roi) cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imshow("collect", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:minSize=(80, 80)比检测时设得大,是为了保证采集到的人脸有足够像素,太小的图训练出来没意义。每人 30 张是经验值,太少模型不稳,太多训练变慢。采集时要让人稍微转动头部、变换表情,覆盖不同状态,否则模型只认采集时那个姿势。文件名里的序号从 1 递增,训练脚本靠文件名前缀解析 label。4.2 训练模型与置信度阈值采集完就该训练了。LBPH 训练很快,CPU 几秒到几十秒。训练完保存成 yml 文件,识别时加载。识别返回两个值:label 和 confidence。这里有个反直觉的点——confidence 在 LBPH 里是距离,值越小表示越像,不是概率。import cv2 import numpy as np from PIL import Image import os # 读取数据集,解析 label path = "dataset" recognizer = cv2.face.LBPHFaceRecognizer_create() faces = [] ids = [] for file in os.listdir(path): if file.endswith(".jpg"): img_path = os.path.join(path, file) # 转灰度,LBPH 只吃灰度图 gray_img = Image.open(img_path).convert('L') img_np = np.array(gray_img, 'uint8') # 文件名格式 label.序号.jpg,取第一段做标签 label = int(file.split(".")[0]) faces.append(img_np) ids.append(label) recognizer.train(faces, np.array(ids)) recognizer.save("trainer.yml") print("训练完成,样本数:", len(faces))逻辑说明:Image.open().convert('L')是转灰度的稳妥写法,比cv2.imread(..., 0)在某些格式上更可靠。np.array(..., 'uint8')保证数据类型一致,否则训练会报类型错误。保存的trainer.yml就是模型文件,识别时用recognizer.read()加载。识别阶段的阈值设定是玄学重灾区:recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read("trainer.yml") # 识别时判断置信度,小于 50 认为可信 label, confidence = recognizer.predict(gray_face) if confidence < 50: print(f"识别为 {label},置信度 {confidence:.1f}") else: print("未知人脸")逻辑说明:confidence 是距离值,0 表示完全一样,越大越不像。50 这个阈值不是固定的,取决于你的样本质量和人数。人少时阈值可以设 40 到 60,人多时要收紧到 30 左右,否则容易认错。我一般会拿几张没参与训练的照片测一遍,看已知人脸的 confidence 分布,再定阈值。5. 避坑与排查:五个真实踩过的坑5.1 现象:检测框满屏乱跳,人脸反而没框住原因:minNeighbors设得太低,或者画面噪声大导致 Haar 把纹理当人脸。解决:先把minNeighbors提到 6 到 8,再加一步高斯模糊cv2.GaussianBlur(gray, (5, 5), 0)压噪。如果还乱跳,检查是不是把scaleFactor设成了 1.0,那会导致计算量爆炸且误检增多。5.2 现象:训练时报 "labels must be integers"原因:从文件名解析 label 时拿到了字符串,或者文件名格式不统一,比如有的叫1.jpg有的叫1.1.jpg,split后取到的段不对。解决:统一命名规范,解析后强制int()转换,并在训练前打印一遍 ids 列表确认全是整数。5.3 现象:识别时所有人都被判成同一个人原因:样本里某个人占了绝大多数,或者采集时背景没变化,LBPH 学到了背景而不是人脸。解决:保证每人样本数接近,采集时换几个位置和光照。另外检查是不是忘了resize到统一尺寸,尺寸差异过大会让直方图对不齐。5.4 现象:摄像头读几帧后就卡死原因:waitKey没调用或参数为 0,导致 GUI 事件不处理;或者cap.read()返回的帧没及时释放,缓冲区堆积。解决:确保循环里有cv2.waitKey(1),并且不要在循环里做耗时操作,比如每帧都存图。如果用的是笔记本,检查是不是被其他会议软件占用了摄像头。5.5 现象:换台机器跑,路径全报错原因:代码里写了作者本机的绝对路径,比如C:/Users/xxx/dataset。解决:全部改成相对路径,用os.path.join拼接,数据集和模型文件放在脚本同级目录。这份资源里的脚本如果带绝对路径,第一件事就是全局替换成相对路径。6. 进阶技巧:用置信度分布反推阈值,而不是拍脑袋阈值定不好,识别要么认错要么全判未知。与其凭感觉设 50,不如用数据说话。做法是:拿一批已知标签的测试图,跑一遍predict,把每个人的 confidence 收集起来,看分布。已知人脸的 confidence 应该集中在一个区间,未知人脸则普遍偏高。两条分布的交叠处就是阈值该放的位置。import cv2 import numpy as np import os recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read("trainer.yml") face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) # 收集已知人脸的置信度 known_scores = [] test_dir = "test_known" for file in os.listdir(test_dir): img = cv2.imread(os.path.join(test_dir, file)) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.1, 5) for (x, y, w, h) in faces: roi = cv2.resize(gray[y:y+h, x:x+w], (200, 200)) label, conf = recognizer.predict(roi) known_scores.append(conf) # 收集未知人脸的置信度 unknown_scores = [] test_dir2 = "test_unknown" for file in os.listdir(test_dir2): img = cv2.imread(os.path.join(test_dir2, file)) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.1, 5) for (x, y, w, h) in faces: roi = cv2.resize(gray[y:y+h, x:x+w], (200, 200)) label, conf = recognizer.predict(roi) unknown_scores.append(conf) print("已知人脸置信度:均值 %.1f,最大 %.1f" % (np.mean(known_scores), np.max(known_scores))) print("未知人脸置信度:均值 %.1f,最小 %.1f" % (np.mean(unknown_scores), np.min(unknown_scores)))逻辑说明:如果已知人脸的 max 是 45,未知人脸的 min 是 60,那阈值设在 50 到 55 之间就合理。如果两者交叠严重,说明模型区分度不够,得回去补样本或换特征。这个方法的代价是需要准备两组测试图,但比反复试参数靠谱得多。场景建议阈值说明人数少于 5 人50 到 60类间差异大,可以放宽人数 5 到 20 人35 到 50需要平衡误识和拒识人数超过 20 人25 到 35类间距离近,必须收紧光照变化大在基础上减 5置信度整体偏高,阈值要降从那以后我每次调 LBPH 阈值,都强制先跑一遍置信度分布,不再靠猜。这套流程虽然土,但比盲目试参数省时间。希望帮到你。本文还有配套的精品资源,点击获取