OpenCV人脸识别实战:从Haar检测到LBPH模型训练全流程

📅 发布时间:2026/10/7 9:13:02
OpenCV人脸识别实战:从Haar检测到LBPH模型训练全流程
简介研一图像处理期末大作业基于OpenCV完成的人脸识别项目面向图像处理、计算机视觉方向的学生尤其适合需要课程设计参考或入门人脸识别实践的读者。项目贯通人脸识别全流程通过Python脚本批量处理图像构建并扩充人脸数据集采用LBPH局部二值模式直方图算法提取纹理特征并训练识别模型再配合Haar级联分类器对人脸区域进行快速检测最终在静态图像或实时视频流中判别身份。代码按职责拆分为数据采集、模型训练与人脸检测三个模块模块间耦合度低方便理解与二次开发也适合作为实验报告的设计亮点。压缩包共9个文件主要包括3个Python源文件、2个XML分类器配置、1个已训练好的YML模型另有SQLite数据库存储人脸特征与标签以及说明文档整体大小仅1.03MB结构精简适合快速下载与本地运行。已有1652人学习浏览既能作为期末作业的完整参考方案也能帮助读者掌握OpenCV人脸识别从数据准备、特征提取到模型评估的工程化方法为后续研究计算机视觉打下基础。1. 基于openCV的人脸识别期末大作业到底在考什么如果你也在为研一图像处理课的人脸识别大作业发愁说明你已经摸到了同一个题目的门槛。基于openCV的人脸识别表面上是让电脑把摄像头里的人脸认出来实际上考的是图像处理课里最核心的一整条链路图像采集、预处理、特征提取、分类器训练和实时推断。无论你手里的压缩包是要求补完代码还是只给了题目从零写落地路径都一样先检测人脸再采集样本然后训练模型最后接到视频流里跑实时识别。这套东西看起来不难但真做起来你会发现90%的坑不在算法而在OpenCV版本、文件路径、光照条件和训练数据质量。我按实际做这类作业的顺序把完整流程和参数讲清楚新手能跟着跑通熟手可以跳着看排错。2. 人脸识别流程拆解从摄像头采集到训练出第一个模型研一图像处理课的作业一般不会让你从零写卷积神经网络常见做法是给一周时间用OpenCV把传统的识别流程跑通。整体拆成三件事人脸检测、样本采集、模型训练。先别急着追求识别率把原材料准备整齐后面的一切才有意义。2.1 用OpenCV的Haar级联先解决“脸在哪”OpenCV里自带的人脸检测器是Haar Cascade。它的原理不是逐像素比对而是用Haar特征描述人脸局部的明暗对比比如眼睛区域比脸颊暗鼻梁区域比两侧亮。配合积分图能在一次扫描里快速计算出几十个特征再用级联分类器一层层过滤把不是脸的区域提前丢掉。所以你不需要自己训练人脸检测模型只要加载OpenCV自带的haarcascade_frontalface_default.xml对灰度图调用detectMultiScale就够了。import cv2 cascade_path haarcascade_frontalface_default.xml face_cascade cv2.CascadeClassifier(cascade_path) img cv2.imread(sample.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(80, 80) ) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2)detectMultiScale返回的是numpy数组每个元素是(x, y, w, h)x、y是方框左上角坐标w、h是宽高。scaleFactor是每次缩放图像的比率1.1表示每轮缩小10%值越小检测越慢但越不容易漏minNeighbors是候选框至少要被几个邻居框投票值越大误检越少但太大也会让人脸漏掉minSize限制最小人脸尺寸如果摄像头离人很远脸在画面里不到80x80像素就检测不到。实际调试时我一般把scaleFactor固定在1.1只调minNeighbors和minSize因为它们对速度和漏检的影响最直观。2.2 采集人脸样本按人分目录一次性拍够训练集检测到人脸之后下一步是采集训练样本。常见做法是让被识别者坐在摄像头前脚本每检测到一张人脸就把人脸区域裁下来缩放成统一尺寸保存成灰度图。文件按人的编号分目录比如person_1、person_2目录名里的数字就是训练标签。每个人建议至少拍50张而且不能光摆一个姿势不动要左右转头、低头抬头、靠近和远离摄像头否则后面模型会把某个特定角度当成这个人本身。import cv2 import os person_id 1 save_dir f./dataset/person_{person_id} os.makedirs(save_dir, exist_okTrue) camera cv2.VideoCapture(0) face_cascade cv2.CascadeClassifier(haarcascade_frontalface_default.xml) count 0 while count 100: ret, frame camera.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: face gray[y:y h, x:x w] face cv2.resize(face, (200, 200)) cv2.imwrite(f{save_dir}/{count:03d}.jpg, face) count 1 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 camera.release() cv2.destroyAllWindows()这段代码保存的是灰度图因为LBPH训练只接受单通道图片。resize到200x200是为了让所有样本尺寸一致OpenCV的训练接口要求图片大小相同否则train时会直接报错。count用三位数补零命名方便排序和统计。如果笔记本自带的摄像头索引不是0把VideoCapture(0)改成VideoCapture(1)如果采集窗口出现但脸一直检测不到多半是光线问题背对窗户会好很多。采集时不要让脸贴近摄像头保证两只眼睛和嘴都在框内比单纯追求数量更有用。2.3 训练识别器Eigenfaces、Fisherfaces与LBPH怎么选数据集准备好之后进入训练环节。OpenCV的face模块提供三种经典识别器Eigenfaces、Fisherfaces和LBPH。Eigenfaces用PCA把图像压缩成“特征脸”再在低维空间里比较欧氏距离Fisherfaces用LDA把类别间距离拉大对光照有一定抵抗力LBPH则把图像划分成小块统计每个局部区域的二值模式直方图最后用直方图相似度判断身份。识别器原理对光照敏感度数据量需求适用场景EigenfacesPCA降维高中人脸对齐、受控光照FisherfacesLDA判别中每类样本多类别数较少时LBPH局部二值模式直方图低低课程大作业、嵌入式demo对大作业而言LBPH是默认选择。它对姿态和光照变化更宽容训练数据量要求低还自带一个距离值可以当置信度用。Eigenfaces和Fisherfaces有一个共同前提输入人脸必须先精确对齐也就是眼睛、鼻子位置要统一否则识别率会非常不稳定。课程作业往往没有时间做关键点对齐所以选LBPH最稳妥。训练代码是这样import cv2 import os import numpy as np faces [] labels [] data_dir ./dataset for person_name in os.listdir(data_dir): label int(person_name.split(_)[1]) person_dir os.path.join(data_dir, person_name) for file_name in os.listdir(person_dir): img cv2.imread(os.path.join(person_dir, file_name), cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (200, 200)) faces.append(img) labels.append(label) recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.train(faces, np.array(labels)) recognizer.save(model.yml)这里用的是新版OpenCV的写法cv2.face.LBPHFaceRecognizer_create()。如果import cv2.face直接报错说明装的是精简版opencv-pythonface模块不在里面需要换opencv-contrib-python。这一点我会在后面的避坑章节单独讲。LBPH识别器也有几个参数可以调常见用法是LBPHFaceRecognizer_create(radius1, neighbors8, grid_x8, grid_y8)其中radius是邻域半径neighbors是采样点数grid是直方图分块。对大作业来说默认值就够但如果识别率不稳定把grid_x和grid_y调大一点有时能改善局部细节比如改成10x10。不要指望调参挽救烂数据样本数量和质量才是决定下限的东西。2.4 用留出法验证模型别把训练集当考卷训练完直接拿去答辩很容易翻车因为老师可能随手拿一张现场照片问你识别率有多高。常见做法是在采集时留出一部分数据做验证比如每个人最后20张不参与训练只用来测试。如果你已经按上面的脚本把所有图都存下来了可以在训练脚本里按时间顺序切分前80%训练后20%验证。LBPH的predict接口是逐图预测验证代码可以写成correct 0 total 0 # test_faces, test_labels 来自预留的验证集 for face, true_label in zip(test_faces, test_labels): pred_label, confidence recognizer.predict(face) total 1 if pred_label true_label and confidence 80: correct 1 print(faccuracy {correct / total:.2f})这个80是距离阈值不是概率不同人的同一模型数值会差很多。你可以在验证集上多试几个阈值把识别准确率和拒识率画成一条曲线报告里放进去比直接说“我跑通了”有说服力得多。到这里一个最小可用的训练链路已经闭环下一章把模型接到实时视频里。3. 把识别器接进实时视频流检测、预测与置信度调参训练好模型之后剩下的工作就是把识别器和摄像头接起来。很多人在这里第一次遇到挫败训练时识别率挺好一到实时视频就乱跳。原因是“训练用的图片”和“摄像头里截取的人脸”不一致。这一章的三个小节分别解决主循环、阈值和模型加载问题。3.1 主循环灰度转换、人脸框选与身份预测实时识别的主循环并不复杂本质上就是反复执行三件事读一帧图像检测人脸对每个人脸区域预测身份。需要注意的是每一帧都要做一次灰度转换因为Haar Cascade和LBPH都只接受单通道图片。预测前的人脸区域要resize到与训练样本相同尺寸否则模型内部会因尺寸不一致而表现异常。import cv2 camera cv2.VideoCapture(0) cascade cv2.CascadeClassifier(haarcascade_frontalface_default.xml) recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(model.yml) while True: ret, frame camera.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces cascade.detectMultiScale(gray, 1.1, 5, minSize(80, 80)) for (x, y, w, h) in faces: roi cv2.resize(gray[y:y h, x:x w], (200, 200)) label, confidence recognizer.predict(roi) text fperson_{label} conf{confidence:.0f} cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(recognition, frame) key cv2.waitKey(1) 0xFF if key ord(q): break camera.release() cv2.destroyAllWindows()关键在recognizer.predict(roi)。它返回两个值label是训练时的标签confidence是与模型的距离。距离的含义取决于识别器LBPH返回的是直方图距离数值越小表示越接近Eigenfaces返回的是空间距离同样越小越接近。不要把它当成概率int型数值的浮动范围在不同数据集上完全不同所以必须单独调阈值。另外人脸检测框经常会把额头和下巴截得比较紧我一般把roi向外扩一点再裁比如取y-20到yh20的范围能减少resize后的形变影响。如果显示中文时出现乱码不用纠结OpenCV的putText不支持中文字体先显示person_标签报告里再另外标注。3.2 置信度阈值怎么设低于阈值才算认识识别器给每个候选框返回一个confidence但模型并不知道这个人是否真的在训练名单里。如果随便一个新面孔走过来LBPH也会给它一个最近的label和一个比较大的confidence。所以必须设一个阈值低于阈值才显示姓名高于阈值显示unknown否则陌生人也会被当成训练集里的某个人。unknown_threshold 80 if confidence unknown_threshold: name fperson_{label} else: name unknown阈值怎么定常见做法是把验证集里每个类别的真实样本和新面孔样本都跑一遍画出两类confidence的分布。真实样本的confidence一般集中在20到60新面孔通常在100以上取一个能让“真实样本通过率”和“新面孔拒绝率”都足够高的分界值就是这个阈值。更简单的做法是扫描一组阈值把结果打印出来for threshold in [60, 70, 80, 90, 100]: accuracy 0 for face, true_label in zip(test_faces, test_labels): pred_label, confidence recognizer.predict(face) if pred_label true_label and confidence threshold: accuracy 1 print(threshold, accuracy / len(test_faces))调整的时候有个经验如果demo里熟人经常被识别成unknown把阈值抬高一些如果陌生人被识别成熟人就把阈值压低一些。最后那10个单位的差距往往就是玄学建议以验证集统计为准不要拍脑袋。还有一个容易忽略的细节同一个人脸连续几帧的置信度可能都不同我习惯用一个固定长度的队列保存最近10帧的预测结果只有某个label出现次数超过一半才显示这样能滤掉单帧抖动。3.3 把训练好的模型存成yml再加载避免每次重新训练训练脚本和识别脚本最好分开。训练脚本保存model.yml识别脚本加载model.yml这样每次启动都不需要重新读几百张图训练。模型加载只有一行recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(model.yml)模型文件是OpenCV的yml格式里面保存了直方图参数、标签映射和训练样本统计。跨机器使用时最好用同一个OpenCV版本重新训练或读取。如果你在Windows上用新版本opencv-contrib-python训练换到Ubuntu上老版本OpenCV读取有时候能读出来但预测结果可能异常因为LBPH的实现细节在不同版本里有过调整。为了省事课程演示机装什么版本的OpenCV就在那台机器上重新训练一次。另一个容易忽视的问题是文件路径。如果你把model.yml放在脚本目录下用相对路径就够但如果脚本被其他目录的代码import当前工作目录会变read会找不到文件。我一般习惯把模型路径写成一个配置变量在脚本开头打印一下绝对路径一跑起来就知道模型到底加载的是哪个文件。实时识别主循环如果一直报unknown先确认是不是模型文件加载失败而不是一上来就调阈值。4. 避坑专场OpenCV人脸识别的五个典型翻车现场这一章全部是实操里容易卡住的地方每条按现象、原因、解决的顺序写。你不需要先看完所有坑才动手而是跑到哪一步卡住再回来翻对应条目。4.1 现象ModuleNotFoundError: No module named cv2明明pip install opencv-python执行成功python里import cv2却报ModuleNotFoundError。原因通常不是缺包而是解释器混了。比如系统里有Anaconda和系统Python命令行pip装在系统Python里IDE却用conda的解释器或者你装的是opencv-python但需要的cv2.face模块根本不在这个包里。先确认当前Python路径再检查已安装包是排这个坑最快的路径。提示安装OpenCV人脸识别模块直接装opencv-contrib-python它同时包含基础功能和face模块。python -c import sys; print(sys.executable) pip list | findstr opencv如果输出里只有opencv-python没有opencv-contrib-python先执行pip uninstall opencv-python再执行pip install opencv-contrib-python。Windows下用findstrmacOS或Linux把findstr换成grep。如果机器里存在多个Python环境最稳妥的做法是在项目根目录建一个虚拟环境然后在这个环境里重新安装依赖避免系统环境被改乱。4.2 现象cv2.error: OpenCV(4.4.0) ... 摄像头打开失败cv2.error后面跟着一长串路径看起来像OpenCV崩溃其实大概率是VideoCapture没拿到摄像头。常见原因是摄像头索引写错了Windows笔记本摄像头通常是0但外部USB摄像头可能是1如果之前打开过微信或腾讯会议摄像头被占用系统也会拒绝新的读取。还有一种情况是VideoCapture(0)能打开但read返回False说明摄像头虽然被识别但没有数据流。import cv2 camera cv2.VideoCapture(0) if not camera.isOpened(): print(cannot open camera) else: ret, frame camera.read() print(ret, frame.shape if ret else no frame) camera.release()解决方法是先跑这个最小化测试把0换成1、2挨个试并关闭其他占用摄像头的程序。如果是USB摄像头热插拔导致的问题拔掉重插一次最管用。另外把帧分辨率固定一下能减少很多玄学问题camera.set(cv2.CAP_PROP_FRAME_WIDTH, 640) camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)640x480够用既保证人脸尺寸又不会让检测太慢。如果分辨率太高Haar Cascade在CPU上可能每帧要几十毫秒demo会卡得不连贯。4.3 现象图片路径带中文就报错或读不出来Windows下如果数据集路径或文件名里有中文cv2.imread会静默返回None后续训练时np.array里混入空对象会报类型或维度错误。原因是OpenCV的imread在Windows上使用系统编码解析路径中文路径容易解析失败。解决办法是优先使用全英文路径。如果必须读取中文路径使用numpy读字节流再交给cv2.imdecodeimport cv2 import numpy as np def imread_unicode(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_UNCHANGED)imread_unicode读出来的图片格式与imread一致编码由imdecode处理中文路径也能正常读。需要说明的是这个技巧只适合读取磁盘图片如果你要保存带中文路径的文件推荐用cv2.imencode配合tofile而不是直接imwrite中文路径。课程作业里尽量把所有路径都改成英文字母能少踩很多坑。4.4 现象识别置信度忽高忽低换个人就不认训练时准确率很高一上台演示就翻车最典型的原因是检测框没对齐。Haar Cascade给出的框是矩形它判断的是“这块区域像脸”但框里可能只包含半张脸、额头或者带很多背景。LBPH的直方图统计的是局部纹理框里内容稍微偏移置信度就会大幅变化。所以采集和识别必须使用同一套预处理流程尤其是统一resize尺寸和灰度化。解决的方法是约束检测框的大小范围并做人脸区域的直方图均衡化。把scaleFactor从1.1改成1.02会让检测更精细但速度会变慢minNeighbors提高到6可以减少误检框。加上cv2.equalizeHist让光照变得更均匀对传统方法的提升非常明显。gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray cv2.equalizeHist(gray) faces cascade.detectMultiScale(gray, 1.02, 6, minSize(100, 100), maxSize(400, 400))这是因为LBPH的局部二值模式对光照比较敏感直方图均衡化相当于先做了一次全局光照归一化。换个人不认还有一个原因是你训练时只采了一个人脸姿态比如一直正对摄像头微笑现场演示时头一歪特征直方图差距就变大。解决办法是采集时多转几次头让样本覆盖各个角度。4.5 现象训练数据只有几张测试集准确率虚高很多同学用同一个人的十几张图train和test混在一起跑得到99%准确率现场一测就崩。这不叫识别率高叫过拟合。原因是训练样本太少模型基本把那张特定图片的亮度、噪声也记住了。课程作业里至少要让每个人有50张样本并且训练集和验证集分开。如果时间不够最见效的做法不是换深度学习模型而是回来补数据。可以用视频抽帧的方式补数据录制10秒钟的人脸视频每5帧保存一张从1段视频里抽30张。虽然相邻帧高度相似至少比只拍5张强。训练集和验证集按采集顺序前80%训练、后20%验证这样验证结果更接近真实现场。随机打乱也能用但结果会偏乐观答辩时老师拿一张没见过的照片一测就露馅。注意训练脚本里可以加一个检查逻辑读完数据集后统计每个人的图片数量少于40张直接报警不让训练继续。我一般会检查数据集里每个人到底读到了多少张图少于40张就直接报警提示。这样强制自己回去补数据而不是在模型参数上硬调。5. 期末答辩和进阶从OpenCV走向solvePnP、EasyAI、ArcFace这一章回答答辩时最可能被追问的问题同时把课程作业和工业级人脸识别的差距说明白让你至少能在概念上接得住话。5.1 为什么大作业用LBPH而不是深度学习算力、数据量与时间成本课程环境里绝大多数人只有CPU装完OpenCV已经不错了。深度学习人脸识别需要GPU训练数据量动辄上万张调参周期以天计期末大作业的时间根本不够。LBPH的特征提取只在像素邻域上做比较训练时统计直方图CPU上几百张样本几秒就能训练完。这个性价比决定了它在课程作业里的统治地位。但这不意味着LBPH没有价值。人脸识别门禁机这类固定场景早期很多方案就是基于类似LBPH的纹理特征后来才逐渐换成深度特征。你答辩时可以说LBPH是一个可解释、可快速上手的基线而深度学习是更强大的替代品。不要贬低自己用的方案而是强调你理解它在整个方案中的位置。如果想让报告更有分量可以补一组对照实验同一数据集上LBPH和OpenCV的Eigenfaces识别率差别有多大。这个实验在CPU上也能做只需要把训练代码封装成函数三次调用不同模型。结果不用复杂一张柱状图加一段分析就能说明问题。另外如果老师习惯用MATLAB做图像处理可能会问你怎么不用MATLAB。你可以说OpenCV在实时视频流上的生态更轻量数据集处理用Python也更好写MATLAB的Image Processing Toolbox适合离线仿真但做实时摄像头demo不如OpenCV直接。5.2 用OpenCV的solvePnP做姿态估计人脸识别的延伸如果老师问你“能不能判断这个人有没有正对摄像头”那就涉及到solvePnP。它是OpenCV里的一个函数作用是已知一组3D点在世界坐标系中的坐标、它们在图像中的2D投影点和相机内参矩阵求解相机与物体之间的旋转和平移。在人脸场景里3D点可以用一个通用人脸模型2D点则来自人脸关键点检测或手工标定。import cv2 import numpy as np image_points np.array([ (150, 120), (180, 120), (165, 160), (155, 190), (175, 190) ], dtypenp.float32) model_points np.array([ (0.0, 0.0, 0.0), (0.0, -3.0, 0.0), (-3.0, -3.0, 0.0), (3.0, -3.0, 0.0), (0.0, -6.0, 0.0) ], dtypenp.float32) camera_matrix np.array([ [500.0, 0.0, 160.0], [0.0, 500.0, 120.0], [0.0, 0.0, 1.0] ], dtypenp.float32) dist_coeffs np.zeros((4, 1)) success, rvec, tvec cv2.solvePnP( model_points, image_points, camera_matrix, dist_coeffs )上面只是最小调用示例。真正使用时要替换成与画面分辨率匹配的相机内参并且用关键点检测替代手工标定。solvePnP返回的旋转向量可以通过cv2.Rodrigues转换成3x3旋转矩阵再计算出俯仰、偏航、翻滚角。有了角度就能判断人脸是否正对摄像头。这个延伸如果在答辩中出现会让老师觉得你对OpenCV的函数体系有实际了解。5.3 对比EasyAI、ArcFace工业级人脸识别与课程作业的差距这一节不一定需要代码但知识在答辩里很加分。EasyAI和ArcFace这类工业级人脸识别方案采用的是深度卷积网络提取512维特征向量再通过特征向量之间的余弦距离或欧氏距离做比对。OpenCV的LBPH是建立在图像局部直方图上的统计模型两个方案的根本区别在于特征表达是否经过大规模数据训练。方案特征形式训练数据量典型场景OpenCV LBPH局部二值直方图几十张/人课程大作业、小范围门禁demoEasyAI人脸识别深度特征向量大规模数据集人脸识别门禁机、安防检索ArcFace归一化深度特征百万级人脸1对N检索、跨设备识别这张表想说明的是OpenCV是理解图像处理的工具箱而EasyAI和ArcFace是面向产品化的解决方案。课程作业要求你掌握的是前者的图像处理链路毕业论文如果继续做这个方向再切入后者也不晚。如果有余力你可以下载一个人脸数据集在LBPH和预训练的ArcFace模型之间做一次对比但不要指望在课程答辩时现场跑通完整训练。如果老师是做机器视觉的还可能问Halcon和OpenCV的区别。Halcon主打工业视觉算法封装完整但收费OpenCV是开源工具箱自定义空间更大。人脸识别大作业选OpenCV主要是课程环境免费且社区资料多。另外如果你在嵌入式课程里遇到过FPGA图像处理OpenCV里这些算法常常被搬上FPGA做硬件加速。但FPGA做人脸识别一般不会用LBPH的完整流程更常见的是做人脸检测和图像预处理识别部分仍然在CPU端完成。6. 最后给你一个保命技巧把识别结果导出成可视化报告很多人的大作业能跑、能演示但答辩时拿不出可视化证据。老师看不到你中间的预处理结果只看到黑乎乎的窗口和闪来闪去的框。一个捷径是在实时识别脚本里加一个保存按钮按s键保存当前帧带标注图按r键保存一条CSV记录。这样报告里能贴图调参时也有日志可查。import csv result_file open(recognition_result.csv, w, newline, encodingutf-8) writer csv.writer(result_file) writer.writerow([frame_id, label, confidence, x, y, w, h]) frame_id 0 # 在识别主循环的按键处理里加入 if key ord(s): cv2.imwrite(fresult_{frame_id}.jpg, frame) writer.writerow([frame_id, name, confidence, x, y, w, h]) print(fsaved frame {frame_id}) frame_id 1保存下来的标注图可以直接贴进报告CSV记录可以统计置信度分布。答辩时老师问“你的系统错在哪里”你拿出一张识别错误的标注图说“这个case是因为侧脸角度大采集样本里没有覆盖”比空口解释有力得多。程序退出前记得执行result_file.close()避免CSV没写入。我自己当年交这类作业时吃过同样的亏代码能跑但拿不出一张中间的标注图答辩老师一问预处理起了什么作用我只能对着黑窗口干瞪眼。后来我养成了习惯任何图像处理作业都先打通“保存证据”这条链路再谈优化算法。这样不管调参调到什么程度都有后悔药可吃。希望帮到你。本文还有配套的精品资源点击获取