简介面向计算机专业毕业设计与期末大作业场景这是一套基于YOLOv5的口罩检测完整工程包含清洗标注后的数据集、可直接运行的Python训练/推理代码、训练好的模型权重以及配套标注文件。项目曾获导师认可并拿下98分评审高分适合正在筹备毕业设计、课程设计或希望上手目标检测实战的学习者参考复现。压缩包共149个文件总大小约922.84MB涵盖40个Python脚本、44个YAML配置、21个PYC编译文件、3个PT权重文件以及SH训练脚本、TXT标注、示例图片与Markdown说明另附Dockerfile和Jupyter教程便于快速搭建环境、复现实验和二次开发。文件类型覆盖数据、模型、代码、文档多个层面目录结构清晰方便按需检索。目前已有193人学习浏览。这套资料不仅给出完整检测流程还通过说明文档和示例图片降低上手门槛对需要完成课设、期末大作业或积累项目经验的学生来说是一份实用且可直接落地的参考资料。1. 基于YOLOV5的口罩检测一个看似“老掉牙”却能完整覆盖毕设全流程的项目如果你在毕业设计或期末大作业里看到“基于YOLOV5口罩检测数据集代码训练好的模型标注好的数据”大概率会被这一长串名字震慑住觉得包越大越难上手。实际上拆开看它由五样东西组成标注好的图片集、YOLO格式的标签文件、YOLOv5的工程源码、训练跑出来的权重、以及调用这个权重的检测脚本。对一个人要在一两个月内出成果的毕设来说这套组合的优势在于覆盖了数据标注、模型训练、推理部署、结果展示四条链路每一段都有足够的“工作量”可写同时又不会因为算法过深导致卡住。适合人群很明确目标不是发论文而是稳定跑通全流程、答辩有东西可讲的本科生和研究生。接下来按数据、训练、推理、避坑、验收这五步把方案拆开讲。2. 数据准备是最容易拉开差距的环节从图片到YOLO格式标签不管标题里的资源包再全数据准备这一步都绕不开。一个可用的口罩检测数据集至少要包含原始图片、标注文件、类别定义三样东西。图片分辨率不要求统一YOLOv5训练时会自动resize到训练尺寸但原图不能缩得太小口罩在画面里通常占几十到几百像素太小的目标在resize后特征会被磨掉。标注文件最常见的是YOLO格式的TXT每张图片对应一个同名txt每行是一个目标框class_id加归一化的中心点x、中心点y、宽、高。这部分做扎实后面训练和推理才能顺利。2.1 二分类还是三分类类别定义决定后续所有工作先想清楚一个问题你的模型到底区分几类。最常见的是二分类“with_mask”和“without_mask”。这个定义清晰、标注快、训练稳定是资源包里的默认配置。也有一些数据集做三分类把“mask_weared_incorrect”单独拉出来也就是口罩戴得不规范。三分类的问题是“不规范”这个概念边界模糊鼻子露出来算不算、口罩挂在下巴上算不算、绳子勒得太松算不算不同标注人员标准不一样数据一致性没法保证。从毕设角度考虑我的建议是先用二分类跑通把数据、训练、部署的流水线稳定住再在论文里把“三分类扩展”写成对比实验。这样有工作量又不会因为类别边界模糊导致结果不可复现。类别定义一旦定了class_id的顺序就必须固定。常见映射是0代表with_mask1代表without_mask。这个映射会同时写进两个地方标签txt文件的第一列、训练时用的mask.yaml里的names列表。两处对不上训练时标签全乱loss曲线像条直线模型什么都学不到。2.2 把VOC标注转成YOLO格式转换脚本和坐标换算的细节网上下载的口罩数据集里很大一部分给的是VOC格式的XML标注结构是bndbox里写xmin、ymin、xmax、ymax。YOLOv5要求的是归一化中心点坐标所以必须做一次格式转换。转换逻辑不复杂但有几个细节值得认真处理。import os import xml.etree.ElementTree as ET # 类别映射表必须和后续训练的mask.yaml保持一致 CLASS_MAP {with_mask: 0, without_mask: 1} def voc_xml_to_yolo_txt(xml_path, txt_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in CLASS_MAP: print(f未定义类别跳过: {cls_name} - {xml_path}) continue box obj.find(bndbox) x_min float(box.find(xmin).text) y_min float(box.find(ymin).text) x_max float(box.find(xmax).text) y_max float(box.find(ymax).text) # 像素坐标转归一化中心点坐标 x_center ((x_min x_max) / 2.0) / img_w y_center ((y_min y_max) / 2.0) / img_h width (x_max - x_min) / img_w height (y_max - y_min) / img_h if not (0 x_center 1 and 0 y_center 1): print(f坐标越界检查标注: {xml_path}) continue lines.append(f{CLASS_MAP[cls_name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) if lines: with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) if __name__ __main__: xml_dir VOC/Annotations txt_dir VOC/labels jpg_dir VOC/JPEGImages os.makedirs(txt_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue base os.path.splitext(xml_name)[0] jpg_path os.path.join(jpg_dir, base .jpg) if not os.path.exists(jpg_path): print(f图片缺失跳过: {jpg_path}) continue from PIL import Image img Image.open(jpg_path) w, h img.size # 必须取原图真实宽高 voc_xml_to_yolo_txt( os.path.join(xml_dir, xml_name), os.path.join(txt_dir, base .txt), w, h, )逻辑说明脚本遍历XML目录对每个目标框读取bbox的四个角点换算成归一化的中心点、宽高然后按“class x y w h”的格式写进txt。换算公式本身不复杂真正要命的是两个细节。第一个是图片宽高的来源。我见过有人把分母写死成640但原图是1920×1080归一化后的坐标全部偏移训练出来的框全打在错误位置上。所以代码里用PIL读原图尺寸再传给转换函数。第二个是越界检查。有些公开数据集的标注框会超出图像边界比如x_max比图片宽度还大几像素。这种框在YOLO训练里不会报错但会让loss在两个维度上互相拉扯。上面代码里加了范围检查越界的就打印出来回头人工看一眼是标注笔误还是存在难样本。2.3 目录结构与数据划分train、val、test的三抽屉规则YOLOv5对数据集的目录结构有明确约定images和labels两个顶层目录各自下面再分train、val、test。图片和标签文件同名不同扩展名一一对应。这也是网上资源包最常见的组织方式。mask_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/划分比例方面我一般用8:1:1。test集在训练过程中根本不会被读取它的意义在于最后做“模型从未见过的数据”评估。很多同学在训练时发现test没用到就偷偷把test合并进train结果答辩时拿测试集一测效果确实好但论文里测试集和训练集数据重叠专家一眼就能看出来。划分脚本要注意随机种子和同人同帧问题。口罩数据集里的图片经常出现“同一批人在不同角度、不同光线下的连拍”简单shuffle可能让同一个人的多张图同时出现在train和val里导致验证集精度虚高。import os import random import shutil random.seed(42) # 固定随机种子保证复现 all_images mask_dataset/all_images all_labels mask_dataset/all_labels names [f[:-4] for f in os.listdir(all_images) if f.endswith(.jpg)] random.shuffle(names) n len(names) train_names names[: int(n * 0.8)] val_names names[int(n * 0.8): int(n * 0.9)] test_names names[int(n * 0.9):] for split_name, split_items in [ (train, train_names), (val, val_names), (test, test_names), ]: os.makedirs(fmask_dataset/images/{split_name}, exist_okTrue) os.makedirs(fmask_dataset/labels/{split_name}, exist_okTrue) for base_name in split_items: shutil.copy( f{all_images}/{base_name}.jpg, fmask_dataset/images/{split_name}/{base_name}.jpg, ) shutil.copy( f{all_labels}/{base_name}.txt, fmask_dataset/labels/{split_name}/{base_name}.txt, )随机种子固定为42保证同一个数据集多次划分得到同样结果这在论文的实验设置小节里是个加分项。划分完还要做一个分布检查统计每个split里各类别的目标框数量。在Linux或macOS上我习惯直接在shell里统计for split in train val test; do echo $split cat mask_dataset/labels/$split/*.txt | awk {print $1} | sort | uniq -c done如果val里with_mask有几千个框、without_mask只有几十个说明划分有偏需要回退重新分。类别不均衡虽然不一定导致训练失败但会让测试集评估结果失真。这个坑在答辩时最容易暴露你拿一张未戴口罩的人像去测检测器怎么都检测不出来不是因为模型没学到而是因为验证集里几乎没有这类样本。数据准备到这个程度才可以进入训练环节。你手上的数据集应该长这样图片整齐、标注文件一行一个目标框、类别分布大致均衡。这个基础打好了训练阶段基本不会被数据拖后腿。3. 用YOLOv5训练自己的口罩检测模型环境配置、训练命令与参数调优数据准备完整之后进入训练阶段。这个阶段的核心任务是搭好环境、写好数据配置、跑通训练、看懂日志。训练本身是流水线动作真正决定论文质量的是你能不能理解每个参数在干什么、loss曲线在说什么以及出问题时往哪个方向排查。3.1 环境配置torch、CUDA与工程依赖的版本匹配YOLOv5工程对环境的依赖集中在requirements.txt里torch和torchvision是其中最核心的两个。版本匹配有严格对应torch 1.10对应torchvision 0.11torch 2.0对应torchvision 0.15。装错版本import torchvision时大概率直接抛错。conda create -n yolov5 python3.8 -y conda activate yolov5 pip install torch1.10.0 torchvision0.11.0 --index-url https://download.pytorch.org/whl/cu113 pip install -r requirements.txt命令说明第一行创建干净的Python 3.8环境第二行激活第三行从PyTorch官方源安装CUDA 11.3对应版本第四行安装工程其余依赖。注意第三条命令没有让pip从默认PyPI装torch因为PyPI上的torch默认是CPU版本训练速度慢到难以接受。装完立刻验证GPU可用性python -c import torch; print(torch.__version__, torch.cuda.is_available())打印出版本号和True才算环境通过。这一步花五分钟检查好过训练到一半才发现模型在CPU上跑一个epoch等二十分钟等到想砸电脑。3.2 数据配置与训练命令写对mask.yaml是关键一步训练前要先写一个数据配置文件告诉YOLOv5去哪里找图片、有几类、类名是什么。路径和类别写错训练能启动但结果全错属于黑匣子问题。# mask.yaml path: /home/user/mask_dataset train: images/train val: images/val test: images/test nc: 2 names: 0: with_mask 1: without_mask这里用了相对path的写法YOLOv5官方支持这种配置能避免在不同机器上反复改绝对路径。nc是类别数必须是names列表长度写错会在训练日志里直接看到AssertionError提示类别数不一致。names的顺序必须和标签txt第一列的class_id完全一致这是整个训练环节最容易犯的隐藏错误。数据配置写好后训练命令长这样python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data mask.yaml \ --cfg yolov5s.yaml \ --weights yolov5s.pt \ --hyp hyp.scratch-low.yaml \ --project runs/train \ --name mask_exp参数说明--img 640是输入尺寸。口罩属于中等尺寸目标640是精度和速度的平衡点。想提升精度可以试960但显存消耗接近翻倍显卡不超过16G时谨慎开启。--batch 16是批大小。显存不足就降降batch是第一步降完后才考虑少开数据增强或减小模型。--epochs 100是训练轮数。口罩检测任务不复杂100轮足够拿到稳定结果。资源充裕可以放到150但不要一上来就300轮时间成本不值得。--cfg yolov5s.yaml是网络结构s是small版本口罩检测场景用s起步就够了。m和l版本精度提升有限训练时间翻倍毕设性价比不高。--weights yolov5s.pt是预训练权重做迁移学习用。强烈建议加这个参数不要从零训练口罩是常见目标COCO预训练提供的底层特征能大幅缩短收敛时间。--hyp hyp.scratch-low.yaml是超参数文件控制学习率、数据增强强度等。默认值经过工程反复验证大多数情况下不需要改。3.3 训练日志怎么读loss曲线、mAP与过拟合信号训练开始后终端会每隔一段时间打印一次类似这样的日志Epoch gpu_mem box obj cls labels img_size 99/100 11.2G 0.021 0.031 0.004 4 640box、obj、cls分别是三种loss边界框回归损失、目标存在性损失、分类损失。口罩检测场景重点关注obj_loss它代表模型对“这个位置有没有目标”的判断。如果obj_loss在训练后期迟迟不降检测时大概率会出现虚框和漏检也就是把背景误认成口罩或者漏掉远处的小口罩。只看mAP是不够的更要看loss曲线的趋势train_loss下降val_loss也下降说明模型在正常学习。train_loss降但val_loss反弹说明过拟合已经出现。这时应减少训练轮数、降低数据增强强度或者把模型换小一档。我一般会把数据增强里的旋转角度从默认值调低人脸方向不会转90度过强的旋转增强反而会让模型学到错误的空间先验。loss在训练早期就出现NaN往往是batch里混入了空标签文件或异常坐标回头检查数据集比调整超参数更有效。训练结束后的产物在runs/train/mask_exp/weights/下两个文件best.pt和last.pt。best.pt是在验证集上mAP最高的权重部署和演示用best.ptlast.pt是最后一轮权重如果要继续finetune从last.pt续训。这个选择我每次都会跟身边的人强调不要拿last.pt去做推理演示尤其当你想展示模型泛化能力的时候——最后一轮不一定是最优的那一轮。训练这关过去了才算真正拿到了“训练好的模型”。接下来要做的是让这个模型跑起来、接上摄像头、导出成更轻量的格式。4. 模型推理与部署从单张图片到实时摄像头检测训练好的权重文件写在runs目录里但模型的真正价值要在推理阶段体现。一张图片、一段视频、一个摄像头画面分别对应三种不同的调用方式。这一章会把三种场景全部跑通并讲清楚每个参数的实际作用避免你照着命令敲完之后仍然不知道自己在调什么。4.1 用detect.py推理单张图片与视频参数含义先说清楚YOLOv5自带detect.py一行命令就能完成推理python detect.py \ --weights runs/train/mask_exp/weights/best.pt \ --source data/images/street.jpg \ --conf-thres 0.5 \ --iou-thres 0.45 \ --save-txt \ --save-conf \ --project runs/detect \ --name mask_demo参数说明--source指定输入可以是一个图片路径、一个视频文件路径、一个目录或者数字0代表摄像头。--conf-thres是置信度阈值只有置信度大于0.5的检测框才会被保留。调高能减少误报调低能减少漏检。实际场景里0.4到0.5之间比较常用。--iou-thres是NMS的交并比阈值控制重叠框的合并力度。0.45是常用值两个框重叠超过45%会被合并成一个。--save-txt会把检测结果写回txt文件每行格式是class_id x_center y_center width height conf不保存坐标会少很多后续处理的信息。--save-conf把置信度也写进txt。答辩时如果你说“置信度0.87”却被要求给出证据txt里就有数据可以直接翻。推理完成后结果图片会保存在runs/detect/mask_demo/目录下框和置信度已经画在图上。这一步是整个毕设闭环里最容易做成演示效果的一环拿三五张不同场景的照片跑一遍把结果摆进PPT视觉冲击力远大于一堆指标表格。4.2 摄像头实时检测线程模型与帧率控制毕设演示的高潮通常是把模型接到摄像头上实时看到检测框和置信度数字跳出来。这块踩坑最多的地方不是模型而是画面流畅度。常见做法是写一个摄像头循环把每个帧resize后送进模型推理再画框显示。这里最容易犯的错是让推理阻塞了画面读取摄像头缓冲区一旦积压画面就会越跑越卡。一个很实用的做法是把帧读取和模型推理放到两个线程里摄像头线程只管读帧推理线程取最新帧做检测。import cv2 import torch import threading import queue # 加载训练好的best.pt用local减少网络请求 model torch.hub.load(ultralytics/yolov5, custom, pathruns/train/mask_exp/weights/best.pt, sourcelocal) cap cv2.VideoCapture(0) # 0代表第一个摄像头设备 frame_queue queue.Queue(maxsize2) # 只保留最新帧避免积压 def read_camera(): while True: ret, frame cap.read() if not ret: break if frame_queue.full(): try: frame_queue.get_nowait() # 丢弃旧帧 except queue.Empty: pass frame_queue.put(frame) threading.Thread(targetread_camera, daemonTrue).start() while True: if frame_queue.empty(): continue frame frame_queue.get() results model(frame) # 推理 rendered results.render()[0] # 画上检测框 cv2.imshow(mask detection, rendered) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明摄像头线程把每一帧放进一个容量为2的队列满了就丢旧帧确保推理线程拿到的永远是最新画面。推理线程只负责取帧、跑模型、显示结果不参与摄像头读取二者互不拖累。这样在普通笔记本上640×640输入、yolov5s模型能跑到20到30帧演示时画面基本流畅。如果帧率还是上不去优先检查模型是不是跑在了CPU上。用前文的环境验证命令确认torch.cuda.is_available()为True再在代码里显式把模型搬到GPUmodel.cuda()。这两个动作能解决九成以上的“摄像头演示卡顿”问题。4.3 导出ONNX格式离开训练环境也能跑的部署形式毕设做到后期你可能会遇到一个需求把模型集成到一个不依赖Python环境的系统里或者用C调用。YOLOv5自带的export.py可以把权重导出成ONNX、TorchScript、TensorRT等格式。ONNX是最通用、调试信息最直观的。python export.py \ --weights runs/train/mask_exp/weights/best.pt \ --include onnx \ --opset 12参数说明--include onnx指定导出格式可以一次导出多种用空格分隔。--opset 12是ONNX的算子集版本。版本太高旧版推理框架不识别版本太低部分算子无法表示。12是个兼容性很好的选择。导出后会得到best.onnx文件。用ONNX Runtime加载并推理不需要再引入torch这在“把模型打包给不熟悉深度学习的老师看”的场景里很管用。import cv2 import numpy as np import onnxruntime as ort sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name img cv2.imread(data/images/street.jpg) img_resized cv2.resize(img, (640, 640)) img_rgb cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) img_norm img_rgb.astype(np.float32) / 255.0 img_tensor img_norm.transpose(2, 0, 1)[None] # (1, 3, 640, 640) outputs sess.run(None, {input_name: img_tensor}) # outputs[0] 的形状是 (1, 25200, 5 类别数)需要自己做后处理这段代码只做了前向推理NMS还得手写这也就是为什么日常文档会说“ONNX导出后再部署需要额外做后处理”。如果你只是毕设演示用torch.hub方式加载best.pt更简单ONNX适合需要往C端迁移的场景哪怕只做了一步导出论文里也能写“模型具备跨平台部署能力”。推理部分跑通后下面我把训练和部署过程中踩过的坑按优先级整理一遍。这些坑不会写在任何README里但它们决定了你能不能按期交付。5. 口罩检测项目避坑指南五个高频翻车现场这一章专门写给时间紧迫、只差临门一脚的同学。下面五条是我在不同项目里反复踩过的坑每一条都按“现象、原因、解决”的顺序讲清楚。遇到问题先来这里对照能少熬两个通宵。5.1 现象训练时显存溢出报CUDA out of memory原因batch size和输入尺寸组合起来超过了显卡显存上限。很多人一上来就按默认的batch 16、img 640跑遇到一张12G的显卡就会爆。解决优先把batch从16降到8再不够降到4。batch降到4后仍然溢出再把img从640降到512。注意别为了省显存把batch降到1batch等于1时BN层的统计量会非常不稳定loss曲线抖得厉害。实在显存不足又必须用大图可以配合YOLOv5的--cache参数但毕设场景用不上。5.2 现象loss在训练早期就出现NaN原因最常见的是数据集里含有空标签文件或者某个标注框坐标出现inf。YOLOv5在训练时会对标签做归一化和过滤空文件不会报错但会在loss计算里产生空梯度。解决启动训练前跑一遍标签检查脚本统计所有txt文件里的有效行数find mask_dataset -name *.txt | xargs wc -l | grep 0 输出里凡是中间数字为0的txt文件基本是空标签。把这些文件连同对应图片一起删掉删完确认两个目录里文件名还能一一对应然后重新划分数据集再启动训练。5.3 现象训练结束mAP挺高但实际检测漏检严重原因mAP高与“实际场景表现好”是两回事。一种常见原因是数据本身单一训练集里全是干净背景、正面人脸测试时遇到侧脸、口罩颜色与皮肤接近、光线极暗的场景模型自然就失效了。另一种原因是conf-thres设得太高推理时把很多置信度0.3到0.4的正确检测框全部滤掉了。解决先降低推理置信度阈值到0.25再看漏检是否解决。如果还是漏就是数据问题。数据问题的解法是先做数据增强给训练集添加随机亮度、对比度扰动再补充一些背景复杂的图片最后重训一遍。数据增强参数在hyp.scratch-low.yaml里调hsv_h、hsv_s、hsv_v三个值控制颜色扰动强度我一般把hsv_v从默认值调高10%左右用来模拟光线变化。5.4 现象加载best.pt时报错提示权重文件结构不匹配原因torch版本不一致导致的兼容性问题。YOLOv5不同版本的工程对权重文件的保存结构有差异低版本torch保存的权重在高版本torch里通常能加载反过来就会报错。解决如果是从网上下载的训练好的模型先确认它的导出环境。本地环境优先复现同版本torch不要贸然升级torch版本。另外加载权重时加上map_location参数model torch.load(best.pt, map_locationcpu)map_locationcpu能避免GPU显存不足或CUDA版本不匹配导致的加载失败。这个参数是权重加载时的后悔药先用它把权重读进来再手动model.cuda()搬到GPU。5.5 现象检测框全部偏到图像角落完全对不准目标原因坐标归一化时用了错误的图片宽高。典型场景是把所有图按640×640归一化但原图是1920×1080导致框的坐标全部被压缩到左上角。这类错误在训练时不会报错loss也在下降但检测结果完全不能用。解决严格从原始图片读取宽高再进行归一化。转换脚本里用PIL或cv2读取原图size不要用训练尺寸代替。如果在训练完成后才发现这个问题必须回退到数据准备阶段重新生成标签和划分模型重新训练。这是最费时间的一种翻车也是数据检查环节能完全避免的翻车。5.6 现象train和val表现都好test上却一塌糊涂原因虽然划分了test但在实际使用中它的分布没有被验证。更常见的情况是test数据分布与训练集差异过大比如train里是正脸为主的场景test里全是低头、侧脸和逆光画面模型没见过这种分布检测自然失效。解决如果test效果差不要先怀疑模型先看test图片与train图片在场景、光照、口罩样式上的差异。比如train里是室内场景test里全是强逆光的户外模型没学过逆光下的口罩特征检测器会把整张图当成噪声。常见的做法是制作test集时与train集保持同场景取样答辩时选用与训练场景一致的测试图不是作弊而是在论文里如实写“模型适用于场景分布与训练数据一致的情况”。泛化到新场景需要后续补充数据重新训练。这一章讲的这些翻车现场训练和部署里遇到任何一个先对照现象找原因再按解决步骤处理。数据、版本、参数、场景四类问题反复出现追根溯源都是数据质量和版本一致性这两个底线守住项目基本不会翻车。6. 把成果从“能跑”做成“能答辩”验证指标与演示闭环训练出了模型、跑通了部署距离交付还差最后一步——把结果量化让评委在几分钟内就认可你的工作量。很多人最后的演示只是对着摄像头晃了晃然后打开一张loss截图这远远不够。你还需要做两件事用测试集做一轮正式评估再做一个带可视化界面的演示入口。6.1 用测试集跑正式评估拿到PR曲线和混淆矩阵训练过程里的mAP是val集上的答辩时如果拿val集数字撑场面懂行的评委一问测试集就露底。正确的做法是用val.py对test数据做一次最终评估python val.py \ --weights runs/train/mask_exp/weights/best.pt \ --data mask.yaml \ --img 640 \ --conf-thres 0.001 \ --iou-thres 0.6 \ --project runs/val \ --name test_eval参数说明--conf-thres在评估阶段要调得很低0.001这样mAP计算才能覆盖全置信度区间这是评估口径的约定别沿用推理时的0.5。--iou-thres设0.6是计算mAP时判断“预测框是否命中真值框”的IoU标准数值越高判得越严格0.6是平衡点。评估结束后runs/val/test_eval/里会生成混淆矩阵confusion_matrix.png、PR曲线PR_curve.png和F1曲线。这三张图是答辩PPT里的核心素材。其中混淆矩阵最能直观说明模型在哪一类上犯错比如with_mask被误判成without_mask的比例偏高说明模型对浅色口罩和肤色相近的口罩区分能力不足答辩时你可以主动提出这个薄弱点以及可能的改进方向比等评委问出来主动性强得多。6.2 做一个有输入输出、有实时反馈的演示入口实时摄像头demo在实验室好使到了答辩现场容易出意外——摄像头驱动不对、环境光线太强、演示机器没有GPU。我的习惯是准备两手现场能接摄像头就接摄像头接不了就用一个预先做好的图片上传界面兜底。用Flask做一个极简的本地演示服务打开浏览器就能上传图片、看到检测结果不挑机器、不需要装CUDA、还能顺便把后端开发的工作量写进论文。核心代码只有几十行from flask import Flask, request, render_template from PIL import Image import torch app Flask(__name__) # 本地加载权重不依赖网络 model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt, sourcelocal) app.route(/, methods[GET, POST]) def index(): if request.method POST: img Image.open(request.files[image]) results model(img) results.save(static/result.jpg) # 保存带框图片 return render_template(index.html, img_urlstatic/result.jpg) return render_template(index.html) if __name__ __main__: app.run(host0.0.0.0, port8080)这一版不求完善只求“能演示”上传图片、返回检测图、置信度可见。再配合一段静态页面模板界面不用漂亮功能链路完整最重要。很多时候毕业设计答辩看的不是花哨的界面而是整个闭环是不是你自己一步一步走通的。6.3 一个具体的验收习惯我自己的习惯是模型训练完、部署完成后用一个固定的口罩样本集做回归测试。这个样本集里放三张戴口罩的、三张没戴口罩的、两张戴口罩但露出鼻子的、一张完全没有人脸的空背景图。每次训练新模型、改超参数、甚至升级环境后都在这个集合上跑一遍把结果记录在一张表格里。指标包括检出数量、置信度、有没有误报空背景、有没有漏检露鼻子的难样本。这张表在论文里写成“小规模人工验证”在答辩时可以直接当证据别说自己做了多少人脸检测把表格给出来说服力比任何文字描述都强。这个方法不是论文标准动作但它是我在多次交付后沉淀出的习惯——做出来的东西经得起自己复测才经得起别人质疑。整个过程走下来你会发现数据准备占了大头训练反而是等待时间最多、思考最少的环节。真正让项目拉开差距的是你能不能把数据检查、版本管理、验证复测这些“看不见的功夫”落实在流程里。我用上面的固定样本集验收了五个模型版本在最后一版终于把露鼻子样本的漏检率压到了可接受范围。这套流程帮我避免了不止一次答辩前的临时翻车希望帮到你。本文还有配套的精品资源点击获取