简介:一套基于YOLOv8的校园建筑玻璃幕墙清洁度检测项目,面向计算机视觉、目标检测方向的毕业设计与课程设计场景,可帮助在校学生快速搭建完整的目标检测应用流程。压缩包共8个文件,大小约15.91MB,由3个Python源码、3个pt模型权重与2个txt说明文档组成。源码模块包括可视化界面、视频检测和模型训练,pt文件提供yolov8n、yolo11n预训练权重及best.pt训练结果,txt文档涵盖部署教程与项目清单说明。目前已有38人学习下载,资源内的代码经测试运行成功,可直接产出核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果和标签分布图等可视化结果,能够覆盖答辩评审的关注点。整体功能完善,简单部署即可运行,既适合毕设、课程设计或大作业展示,也便于在此基础上继续扩展;资源包目录结构清晰,下载后打开README即可知晓各文件用途,便于按需调用。
1. 校园建筑玻璃幕墙清洁度检测:YOLOv8 这套毕设方案到底在解决什么
如果你在学校里见过保洁人员拿望远镜看玻璃幕墙,再对照阳光判断要不要清洗,那你就知道玻璃幕墙清洁度检测是个真实存在的麻烦事。校园建筑的外立面玻璃一旦积灰、沾上鸟粪或雨痕,不仅影响采光,还会让整栋楼显得破旧。传统的清洁度检查靠人眼巡查,主观性强,还容易漏掉高处区域。这个项目标题里提到的方案,本质上是把这件事交给 YOLOv8 目标检测模型:输入一张玻璃幕墙照片或视频帧,模型输出哪个区域有污渍、污渍属于哪一类,再结合可视化界面把结果标出来。它适合毕设或课程设计,因为数据不需要真实清洗过程,只需要标注照片,训练和部署链路短,OpenCV、PyTorch、PyQt5 这套组合就能跑通。这篇笔记围绕源码、可视化界面、数据集、部署教程四个部分,把从零到能展示的路径拆开讲。
2. 检测思路与数据集准备:先分清“脏”和“污渍”,再让模型学会看玻璃
2.1 玻璃幕墙清洁度的判定逻辑:为什么不能只用边缘检测
玻璃幕墙上的污渍和普通物体检测不太一样。常规的目标检测是找“物体”,但这里找的是“缺陷”或“区域”,边界往往是渐变的。比如一层均匀的灰,在图片上看起来只是整体亮度降了一点,用 Canny 边缘检测会把它当成大量细碎纹理,然后误判成几十个目标。反光、窗框、远处树木倒影也会产生强烈边缘,比真实污渍还显眼,所以传统图像处理在这里会翻车。
YOLOv8 的优势是把缺陷检测当作回归问题,直接学习“污渍区域”的边界框和类别。你需要提前定义清洁度等级,常见做法是分两类或三类:一类是“明显污渍”(鸟粪、泥浆、水渍),一类是“轻微积灰”,第三类“干净”一般不进训练集,因为检测模型只需要在画面上框出问题区域。如果硬要把“干净”也作为一个类,样本失衡会非常严重,而且“干净”样本几乎没法标注出稳定的边界框。实际项目里,我会让模型专注于“污渍”和“水渍”两类,干净区域靠推理逻辑反推——画面里没有检测框就代表清洁度合格。
2.2 公开数据集不够用,怎么组织自己的数据集
校园建筑玻璃幕墙这个场景没有现成的公开数据集,像 COCO 或 VOC 里不会专门标注“玻璃污渍”。常见做法是自己采集并标注。先准备一台普通手机或相机,围绕校园里的玻璃幕墙拍摄两三百张照片,注意覆盖不同时间、不同天气、不同楼层角度。阳光直射时反光严重,阴天时污渍更明显,两类照片都要有,否则模型会把阴影和污渍混淆。
数据组织方式按照 YOLO 格式做:images文件夹存原图,labels文件夹存同名的.txt文件,每行是class_id x_center y_center width height,坐标是相对于图片宽高的归一化值。标注工具用 labelImg 或者 CVAT 都行,导出时直接选 YOLO 格式,省得自己写坐标转换。我一般建议至少标注 300 张,其中每类目标不少于 500 个框。目标是毕设展示,不需要海量数据,300 张配合数据增强已经能让 mAP 跑到 0.8 左右。这里要控制一个度:如果照片全部是近景特写,模型就学不会“在整栋楼的照片里找污渍”;如果全部是远景,小目标又很难学。比例上按“地面拍摄 70%、无人机或高处拍摄 30%”来分配比较稳。
2.3 标注细节:哪些是“污渍”,哪些是“反光”,哪些要剔除
标注是这个项目里最影响结果的环节,也是最容易踩坑的地方。定义边界时,我会遵循三条规则:
第一,肉眼能确认为“脏东西”的区域才标,包括鸟粪、泥点、灰尘堆积、干水渍。窗框边缘的黑色密封胶和雨棚痕迹不标,因为那是结构性阴影,不是清洁度问题。第二,天空倒影和树木倒影不标,即使它们在视觉上占据了玻璃的大半区域。模型学不会“倒影是脏”,强行标注会让推理阶段到处误报。第三,边界模糊的污渍优先用矩形框紧贴可见范围,不要为了“框全”把大量干净玻璃包进去。
反光是一个特例。阳光斜射时,玻璃上的反光带往往会掩盖真实污渍,也会让部分反光区域看起来像白色污渍。我处理的方式是在采集阶段尽量避免正对光源拍摄,如果某张图反光面积超过 40%,直接剔除,不硬标。另外,训练集里的图片统一缩放到 640 或 1280 分辨率,不需要用原始 4000 像素图,YOLOv8 在缩略图上学习小目标污渍足够,还能显著加速训练。做完标注后,检查一遍所有.txt文件里有没有坐标越界或者宽高为 0 的异常,这类问题在后续训练时会报错,而且报错信息不直观。
3. 环境配置与最小训练命令:从 YOLOv8 安装到跑通第一次训练
3.1 YOLOv8 环境配置:CUDA、PyTorch 与 ultralytics 的版本搭配
YOLOv8 的安装入口是ultralytics这个 Python 包,它统一封装了训练、验证和导出。不要自己到 GitHub 上拉一堆源码去编译,直接用 pip 安装是最省事的做法。我建议用conda create -n yolov8 python=3.10新建环境,然后安装 PyTorch。注意先装 PyTorch 再装 ultralytics,因为 ultralytics 依赖 torch,自动安装 PyTorch 容易配上 CPU 版本。
复制代码到本地跑之前,先确认显卡驱动和 CUDA 版本。常见做法是安装 PyTorch 的官方命令:pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118,CUDA 11.8 在多数 30 系和 40 系显卡上都能直接跑。如果你的显卡只支持更高版本,也可以换成 cu121。装完后用python -c "import torch; pring(torch.cuda.is_available())"验证,输出True才说明 GPU 可用。如果显示 False,先检查驱动,再检查 PyTorch 版本是否匹配显卡。
然后安装 ultralytics:
pip install ultralytics安装完成后,建议顺手安装albumentations和tensorboard,前者用来做高级数据增强,后者方便观察训练曲线。ultralytics 默认会用自己集成的增强逻辑,这两个不是必须的依赖,但装了之后能多一个调试维度。最后跑一下yolo predict model=yolov8n.pt source=bus.jpg做冒烟测试,能识别出公交车上的人,说明环境整个链路没问题。这一步能排除很多“代码明明没跑错但没反应”的玄学问题。
3.2 训练自己的数据集:yaml 文件与最小训练命令
把数据集放进项目根目录,结构是:
glass-detection/ ├── data.yaml ├── train/ │ ├── images/ │ └── labels/ └── val/ ├── images/ └── labels/data.yaml的写法要注意,train和val我们写绝对路径,避免换一台电脑就找不到数据。文件内容如下:
path: /home/user/glass-detection train: train/images val: val/images nc: 2 names: 0: stain 1: water_streak这里的nc是类别数量,names里0和1的类别顺序必须和你标注时一致。如果你把“水渍”定为0,但标注工具导出的 class_id 是1,训练出的模型会张冠李戴。我一般在训练前写一个小脚本检查数据集的类别分布,防止标注工具自动改了 ID。
最小训练命令只需要一行:
yolo train model=yolov8n.pt data=data.yaml epochs=100 imgsz=640 batch=16yolov8n.pt是预训练权重,n是最轻量的版本,适合起步调试。如果你直接下载yolov8m.pt或yolov8l.pt,精度更高,但显存占用会涨到一个对毕设不友好的水平。第一次训练建议用 nano 模型,把流程跑通,再换yolov8s.pt提精度。
3.3 训练参数怎么调:imgsz、epochs、batch 与数据增强
训练参数的选择直接决定你能不能在可用时间内拿到一个能演示的模型。先说imgsz。玻璃幕墙污渍属于小目标,如果原始照片是 4000 像素,污渍实际只有几十像素,直接缩放到 640 会让目标变成 5 到 10 个像素,模型很难学。我一般先用imgsz=640跑通,再试着开到imgsz=1280。注意,imgsz=1280的显存占用和训练时间几乎是 640 的四倍,根据自己的显卡量力而行。
epochs取多少要看数据量。300 张图的情况下,我常用的做法是先跑 100 epochs,观察val/box_loss变化。如果到第 60 个 epoch 已经收敛,就提前停机;如果还在下降,追加到 150。关门跑epochs=300没必要,因为过拟合会在 100 个 epoch 之后逐渐出现,模型开始死记训练集里的窗户纹理,验证集 mAP 反而下降。
batch的设置原则是能开多大开多大,以显存不爆为准。8GB 显存跑yolov8n和imgsz=640,batch=16没问题;如果换成yolov8s,batch=8更稳妥。超参数里和玻璃污渍关系最大的是hsv_h、hsv_s和hsv_v,它们控制色调、饱和度、明度的随机扰动。玻璃表面有大量渐变反光,适当调大色调扰动让模型学到“颜色变化不影响污渍判断”是值得的。ultralytics 的命令行可以直接传:
yolo train model=yolov8s.pt data=data.yaml epochs=150 imgsz=640 batch=8 hsv_h=0.02 hsv_s=0.8 hsv_v=0.4这里hsv_h=0.02表示色调偏移范围很小,避免把玻璃幕墙的蓝色调变成奇怪颜色;hsv_s=0.8允许饱和度大幅变化,模拟阴天和晴天的差异。
4. 可视化界面与推理部署:把模型封装成毕设演示工具
4.1 用 PyQt5 或 Gradio 做清洁度检测可视化界面
训练好模型只是第一步,毕设答辩时需要让评委看到“能交互的程序”,这意味着必须有一个可视化界面。常见做法有两种:PyQt5 桌面程序和 Gradio Web 界面。PyQt5 适合离线演示,环境可控,打包成 exe 后到哪台电脑都能跑;Gradio 更省事,浏览器打开就能用,但依赖网络和一些前端资源,老师现场演示时如果教室网不好会卡。我推荐 PyQt5,理由是这个项目的检测逻辑不复杂,桌面程序少一层浏览器适配问题。
一个最小可用的 PyQt5 界面包括:一个按钮选择图片,一个按钮开启摄像头或视频流,一个标签区域显示检测结果。核心代码框架:
import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QFileDialog, QVBoxLayout, QWidget from PyQt5.QtGui import QImage, QPixmap import cv2 from ultralytics import YOLO class GlassDetectorApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("校园玻璃幕墙清洁度检测") self.model = YOLO("best.pt") btn = QPushButton("选择图片") btn.clicked.connect(self.open_image) self.label = QLabel("检测结果将显示在这里") layout = QVBoxLayout() layout.addWidget(btn) layout.addWidget(self.label) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) def open_image(self): fname, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Image Files (*.jpg *.png)") if not fname: return results = self.model(fname, conf=0.25) annotated = results[0].plot() rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qimg = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg)) if __name__ == "__main__": app = QApplication(sys.argv) win = GlassDetectorApp() win.show() sys.exit(app.exec_())这段代码的关键在于self.model(fname, conf=0.25)。conf就是置信度阈值,示意用 0.25 已经很激进,真正常跑时我会设到 0.4 左右,原因放在后面的避坑章节细说。results[0].plot()是 ultralytics 自带的方法,直接返回画好检测框的 BGR 图像,省去手动画框的麻烦。PyQt5 显示图像时要注意把 BGR 转成 RGB,否则颜色会偏蓝偏黄,评委可能觉得你的界面有问题。
4.2 推理脚本要处理的三个问题:视频流、结果输出、置信度阈值
如果想把程序做得更像一个“系统”,就需要支持视频流。摄像头或视频文件的处理逻辑本质上是一个循环,逐帧读、逐帧检测、逐帧显示。这里要注意性能问题,YOLOv8n 在 CPU 上大约 10 到 20 FPS,在 GPU 上 60 FPS 以上,但 PyQt5 的界面刷新如果每帧都重绘,CPU 版本会显得很卡。常见优化是跳帧检测:每 3 帧做一次推理,中间两帧直接显示上一帧结果。代码片段如下:
cap = cv2.VideoCapture(0) fps_skip = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break if fps_skip % 3 == 0: results = self.model(frame, conf=0.4) annotated = results[0].plot() self.display(annotated) else: self.display(self.last_frame) fps_skip += 1同时要处理检测结果的输出:每次检测后把当前帧里的污渍坐标和置信度记录下来,写成 CSV 或 JSON。对毕设来说,这算一个“工作量证明”,用来展示你考虑了结果的可追溯性。我一般会在“导出报告”按钮里加一段逻辑,把当前检测的图片名、类别、置信度、边界框坐标写入csv。注意中文文件名和 CSV 编码问题,Windows 上需要加encoding='utf-8-sig',不然用 Excel 打开是乱码。
置信度阈值的选择直接影响演示效果。阈值太低,玻璃反光、窗框边缘会疯狂出框;阈值太高,明显的污渍又会被过滤掉,看起来像模型失效。这个 bug 很像“黑匣子”问题——用户不理解模型为什么时好时坏。我建议在界面里加一个滑杆,让演示时能实时调整阈值,比固定写死更实用。
4.3 部署教程里最容易被忽略的模型导出与运行环境
部署教程写得再详细,也架不住环境不一致。常见的是别人给你代码,你拿到手直接跑pip install ultralytics,结果发现代码里是from ultralytics import YOLO,best.pt是拿 YOLOv5 训练出来的,模型结构不兼容,直接报错。这里要养成一个习惯:训练完模型后,用下面的命令导出成同一套运行环境能用的格式:
yolo export model=best.pt format=onnx opset=12导出为 ONNX 的好处是跨框架,就算对方电脑上的 torch 版本和 ultralytics 版本对不上,用onnxruntime推理也能跑。更稳妥的做法是把导出的 ONNX 文件和.pt文件一起放进项目包里,并在部署文档里说明“优先使用 ONNX 推理”。如果需要打包成 exe 给评委演示,PyInstaller 打包best.pt还涉及资源路径问题,最好把模型文件放在程序同级的models文件夹,用相对路径加载:
import sys import os def resource_path(relative_path): base_path = getattr(sys, "_MEIPASS", os.path.abspath(".")) return os.path.join(base_path, relative_path) model = YOLO(resource_path("models/best.pt"))这个resource_path函数是 PyInstaller 打包的必备处理,不写的话 exe 双击会报找不到文件,十有八九会让人误解为代码损坏。
5. 避坑指南:训练和部署中常见的 5 个坑
5.1 现象:loss 一直不降 / 检测不到玻璃
训练前几十个 epoch,loss 在 1 左右震荡,完全降不下去,或者能正确识别污渍的验证集图片寥寥无几。原因大概率是标注坐标和图像尺寸不匹配。我遇到过用 labelImg 导出时选了 PascalVOC 格式,然后转 YOLO 时把坐标方程算错,宽高变成负数,模型学成了一个垃圾映射。解决方式是在训练前写一个检查脚本:读取任意一张图和对应的.txt,把标注框画在原图上,人眼确认框的位置是否正确。这个动作很笨,但能把标错坐标一次性揪出来。另一个原因是数据集里的图片分辨率差距太大,有 4000 像素的远景,也有 500 像素的局部特写,模型学到的是两种完全不同的目标尺度。统一做一次缩放预处理再训练,loss 会大幅改善。
5.2 现象:训练时显存爆掉
训练启动后没几分钟,程序宕机提示 CUDA out of memory。最常见原因是imgsz和batch叠加后超出了显存上限。解决办法不是单纯减小 batch,因为 batch 太小 BN 层的统计量不稳定,模型精度反而下降。我会先把batch调成 8,imgsz调成 640,如果还爆,就把yolov8s换成yolov8n。另一个容易被忽略的因素是 PyTorch 缓存了未释放的显存。训练前别开着好几个 Jupyter Notebook 或 PyQt 界面,把无关进程杀掉。还有一点,Windows 上如果显卡被 WDDM 驱动占用,NVIDIA 控制面板里开启“全局 GPU 计算模式”可以缓解一部分问题。
5.3 现象:界面卡死或推理很慢
PyQt5 界面在点击按钮后直接变成“未响应”,原因是推理放在了 GUI 主线程,图片处理耗时较长,界面刷新线程被阻塞。解决方式是把推理丢到单独线程。最简单的是用QThread或 Pythonthreading模块,在后台执行模型推理,推理完成后通过信号把结果传回主线程更新界面。如果是摄像头视频流卡顿,情况不同,那是 CPU 版本推理本身太慢,只能降帧或者换推理引擎。ONNX Runtime 可以尝试,OpenVINO 也可以,但毕设阶段用yolov8n加跳帧已经足够。
5.4 现象:标注数据没问题但 mAP 很低
训练完mAP50在 0.3 左右,人眼看图片是能认出来的,但模型记得不牢。这个情况通常和类别不平衡有关。如果污渍类有 800 个框,水渍类只有 100 个框,模型会把两者混淆,最终水渍类精度极低。处理方式有两种:一是收集更多水渍样本;二是把数据增强对这类样本的翻转、旋转比例加大。ultralytics 里可以用augment参数控制,但更有效的是手动复制水渍样本几次,相当于过采样。如果时间不够,直接把水渍类和污渍类合并成单一“污渍”类,回归问题变成单类检测,mAP 会立刻上涨。毕设展示重点是“玻璃清洁度判断”,类别少一点不是问题。
5.5 现象:导出模型后结果和训练时不一致
用best.pt在训练脚本里做预测效果正常,但导出 ONNX 后同一张图的检测结果突然没有框,或者置信度低很多。原因一般是推理图像预处理不一致。YOLOv8 训练时图像会做灰度填充,而自定义推理代码如果直接cv2.imread后传入模型,尺寸和通道顺序都可能不同。解决方式是统一调用 ultralytics 的YOLO类来推理,不要自己手写预处理。如果必须用 ONNX Runtime,参考官方获取输入输出的方式,并确认归一化范围是0-1还是0-255。这个细节和“训练与部署两套逻辑”的说法对得上,属于典型的“黑匣子翻车”,我见过不少人在导出环节卡住。
6. 进阶验证:画热力图、损失曲线与置信度校准
6.1 画损失函数曲线:从 results.csv 到可视化
YOLOv8 训练完成后,会在runs/detect/train/目录下生成results.csv,里面记录了每个 epoch 的train/box_loss、val/box_loss、metrics/mAP50等指标。很多人不会读这个文件,只在终端看训练输出的文本日志,那太粗糙。用 pandas 读取并画图,可以让答辩时的“调参过程”可视化:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/train/results.csv") fig, ax = plt.subplots(1, 2, figsize=(12, 4)) ax[0].plot(df["epoch"], df["train/box_loss"], label="train box loss") ax[0].plot(df["epoch"], df["val/box_loss"], label="val box loss") ax[0].set_xlabel("epoch") ax[0].set_ylabel("loss") ax[0].legend() ax[1].plot(df["epoch"], df["metrics/mAP50(B)"], label="mAP50") ax[1].set_xlabel("epoch") ax[1].set_ylabel("mAP50") ax[1].legend() plt.tight_layout() plt.savefig("training_curve.png", dpi=200)这里要注意 pandas 列名里.和/的处理,直接按官方生成的列名写就行。画出来的曲线如果 val loss 在第 50 个 epoch 开始上升,说明过拟合,需要早停;如果两者一直同步下降,说明还能继续训练。这张图在答辩时比十页文字都管用。
6.2 可视化热力图:用 Grad-CAM 看模型在“看”哪里
YOLOv8 不能直接输出解释性热力图,但常见做法是用 Grad-CAM 针对最后一个卷积层做梯度加权,生成一张叠加在原因上的热力图。这个操作的意义是验证模型是否真的关注污渍区域,而不是靠窗框或天空纹理来“蒙答案”。实现方式是先把模型拆成特征提取部分和后处理部分,用一张测试图片做前向,拿到特征图和梯度,再计算加权平均得到粗略热力图。如果热力图高亮区域和人工标注污渍的边界框高度重叠,说明模型学到的空间特征是对的;如果热力图集中在反光带或玻璃边缘,说明模型被误导了,需要回到数据集检查标注一致性。这类分析可以写成一个小节,证明你不仅把模型训完,还做了可解释性验证。
6.3 置信度阈值校准与最终验收清单
模型训练完成后,建议在验证集上统计不同置信度阈值下的精确率和召回率,选择使二者平衡的阈值作为界面默认值。方法很简单:用model.val()得到验证结果,然后在 ultralytics 的metrics.py结果里读取 PR 曲线数据,找到精确率接近召回率的点。如果演示时希望少误报,把阈值调高到 0.5;如果希望多检出,调到 0.25。这个决策过程要写进部署文档,让使用者明白“为什么默认阈值是 0.4”,而不是拍脑袋定的。
最终验收清单是这样的:输入一张接近 0.5 MB 的 JPG,程序在 3 秒内给出结果;输出的检测框置信度大于 0.4;不同光照条件下,至少 20 张测试图里的明显污渍能被框出;视频流模式不卡死,连续运行 10 分钟内存占用稳定。如果能跑通这些项目,整个毕设的“实验、验证、展示”三块就算齐了。从标注到训练再到部署,最耗时间的其实不是模型,而是数据一致性和环境兼容性。我自己的习惯是每改一次数据集就完整跑一遍训练,而不是攒在一起看结果。希望这篇笔记能帮你把 YOLOv8 玻璃幕墙清洁度检测的方向跑通,少走一点我走过的冤枉路。
本文还有配套的精品资源,点击获取