简介:这份资源是面向计算机视觉方向的毕业设计与课程设计参考项目,围绕玻璃幕墙结构胶老化程度评估展开,采用YOLOv8目标检测框架实现多类别老化状态识别。项目适合人工智能、计算机、自动化等专业学生及需要快速搭建检测系统的开发者,提供从数据到界面再到部署的完整闭环。压缩包共97个文件,约24.21MB,以70个Python源码文件为核心,辅以4个pt权重文件、5个xml配置、12个pyc缓存及少量txt说明与ico图标,涵盖模型训练、推理检测、可视化服务等模块。已有35人学习下载。资源包含完整数据集、可视化界面与部署说明,可生成核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线及验证集预测结果,并附有视频测试样例与多模型权重,便于直接运行、复现实验与二次修改,适合作为毕设答辩或课程作业的可靠基础。
1. 从一张幕墙照片到老化等级:这套评估系统到底在做什么
玻璃幕墙用久了,最怕的不是玻璃碎,而是把玻璃粘在铝框上的那层结构胶悄悄老化。硅酮结构胶在紫外线、温差、湿度循环下会逐渐硬化、开裂、粉化,肉眼在几十米外根本看不出来,但一旦失效就是整块玻璃坠落的风险。传统做法靠人工吊篮近距离目视检查,效率低、危险、还极度依赖老师傅的经验。这套《基于YOLOv8的玻璃幕墙结构胶老化程度评估系统》想解决的就是这件事:用无人机或长焦拍到的幕墙胶缝图像,喂给 YOLOv8 做目标检测,把胶缝区域框出来并判定老化等级,再配一个可视化界面把结果叠加回原图,最后给出一份可存档的评估记录。它适合做毕设或课程设计的同学,也适合想快速验证「视觉检测能不能替代部分人工巡检」的工程人员。整套东西包含源码、完整数据集、可视化界面和部署教程,简单部署即可运行,重点不在炫技,而在于把数据、模型、界面、部署这条链路完整跑通。下面我按自己落地这类项目的顺序,把关键环节拆开讲清楚。
2. 数据先行:结构胶老化数据集怎么标、怎么分、怎么增强
2.1 老化等级怎么定义才不打架
做检测项目,翻车往往不是模型不行,而是标签定义从一开始就含糊。结构胶老化如果只分「好/坏」两类,模型学到的边界会非常模糊,因为「轻微老化」和「中度老化」在像素上差异很小。我一般会按工程可判读的标准分成四类,让标注员有明确依据:
| 等级 | 代号 | 视觉特征 | 处理建议 |
|---|---|---|---|
| 正常 | normal | 胶缝饱满、颜色均匀、无裂纹 | 常规巡检 |
| 轻微老化 | slight | 表面轻微粉化、局部变色 | 缩短巡检周期 |
| 中度老化 | moderate | 出现细裂纹、边缘轻微剥离 | 安排复检 |
| 严重老化 | severe | 明显开裂、脱粘、胶体缺失 | 立即维修 |
这个分级不是拍脑袋,而是让每一类都有可复现的判读特征。标注时要求标注员对同一张图独立标两次,一致性低于 90% 的样本退回重标。类别不平衡是常态,严重老化样本通常最少,所以后面增强要针对性补。
2.2 用 Labelme 标注再转 YOLOv8 格式
热词里 labelme 标注用于 yolov8 是高频需求,实际流程就是先标 JSON 再转 TXT。Labelme 画框时类别名直接写英文代号,避免中文路径和中文类别带来的编码坑。
# 安装 labelme pip install labelme # 启动标注,图像目录和输出目录分开 labelme images/ --output labels_json/ --autosave标完得到一堆 JSON,需要转成 YOLOv8 要求的class x_center y_center width height归一化格式。转换脚本如下:
import json import os from pathlib import Path # 类别顺序必须和 data.yaml 里的 names 完全一致 CLASSES = ["normal", "slight", "moderate", "severe"] CLASS_TO_ID = {c: i for i, c in enumerate(CLASSES)} def convert(json_dir, out_dir, img_dir): os.makedirs(out_dir, exist_ok=True) for jf in Path(json_dir).glob("*.json"): data = json.loads(jf.read_text(encoding="utf-8")) # 原图尺寸用于归一化,缺失就跳过,别硬算 img_h = data.get("imageHeight") img_w = data.get("imageWidth") if not img_w or not img_h: print(f"skip {jf.name}: no size") continue lines = [] for shape in data["shapes"]: label = shape["label"] if label not in CLASS_TO_ID: continue (x1, y1), (x2, y2) = shape["points"] # 保证左上右下顺序,标注时手抖反了也能救 x1, x2 = min(x1, x2), max(x1, x2) y1, y2 = min(y1, y2), max(y1, y2) xc = (x1 + x2) / 2 / img_w yc = (y1 + y2) / 2 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{CLASS_TO_ID[label]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") out_file = Path(out_dir) / (jf.stem + ".txt") out_file.write_text("\n".join(lines), encoding="utf-8") convert("labels_json", "labels_txt", "images")逻辑说明:CLASSES的顺序是全局契约,训练、推理、界面显示都依赖它,改一处必须全改。归一化用原图宽高,所以 JSON 里必须带尺寸信息,Labelme 默认会写。坐标顺序做了 min/max 保护,避免标注时拖框方向不一致导致负宽高。参数上:.6f保留六位小数足够,YOLO 内部还会再处理。
2.3 划分数据集与针对性增强
划分比例我一般用 8:1:1,但幕墙图像有个特点:同一面墙的相邻照片高度相似,如果随机划分,训练集和验证集会「串味」,验证指标虚高。正确做法是按「幕墙编号」分组划分,同一面墙的图只进一个集合。
import random from pathlib import Path from collections import defaultdict def split_by_wall(img_dir, label_dir, out_root, ratios=(0.8, 0.1, 0.1)): # 文件名前缀假设为 wallID_xxx.jpg,按 wallID 分组 groups = defaultdict(list) for img in Path(img_dir).glob("*.jpg"): wall_id = img.stem.split("_")[0] groups[wall_id].append(img.stem) walls = list(groups.keys()) random.seed(42) # 固定种子,保证可复现 random.shuffle(walls) n = len(walls) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) splits = { "train": walls[:n_train], "val": walls[n_train:n_train + n_val], "test": walls[n_train + n_val:], } for split, wall_list in splits.items(): for sub in ("images", "labels"): (Path(out_root) / split / sub).mkdir(parents=True, exist_ok=True) for wall in wall_list: for stem in groups[wall]: src_img = Path(img_dir) / f"{stem}.jpg" src_lbl = Path(label_dir) / f"{stem}.txt" if src_img.exists(): (Path(out_root) / split / "images" / src_img.name).write_bytes(src_img.read_bytes()) if src_lbl.exists(): (Path(out_root) / split / "labels" / src_lbl.name).write_bytes(src_lbl.read_bytes()) split_by_wall("images", "labels_txt", "dataset")增强方面,幕墙图像的难点是光照和角度。我一般开 HSV 抖动、随机旋转 ±15 度、mosaic,但不开上下翻转,因为幕墙有重力方向,翻转会造出不存在的物理状态。严重老化样本少,可以单独对它做复制加轻微扰动,但别过度,否则模型会对这一类过拟合。
3. YOLOv8 训练:参数怎么设、损失曲线怎么看、模型怎么选
3.1 环境搭建与预训练权重
热词里 yolov8 环境配置、yolov8 预训练权重下载 是绕不开的。CPU 版本也能跑通小数据集,只是慢。Ubuntu 20.04 上我一般这样配:
conda create -n yolo8 python=3.10 -y conda activate yolo8 # 装 ultralytics,会自动带 torch,CPU 机器不用额外装 cuda pip install ultralytics # 验证 yolo checks预训练权重用yolov8n.pt或yolov8s.pt,n 最快适合毕设演示,s 精度更好适合真实巡检。权重文件放到项目根目录,训练时指定路径即可,不要依赖自动下载,离线环境会卡住。
3.2 data.yaml 与训练命令
data.yaml是数据和类别的契约文件:
path: ./dataset train: train/images val: val/images test: test/images nc: 4 names: ["normal", "slight", "moderate", "severe"]训练命令:
yolo detect train \ model=yolov8s.pt \ data=data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=30 \ device=0 \ project=runs/glue \ name=exp1参数说明:imgsz=640是精度和速度的平衡点,幕墙胶缝细长,再小会丢细节;batch=16显存不够就降到 8 或 4,CPU 训练建议 4;lr0=0.01是初始学习率,配合lrf=0.01做余弦退火;patience=30表示 30 轮没提升就早停,省时间。device=0用第一块 GPU,CPU 训练改成device=cpu。
3.3 损失曲线怎么读才算没白跑
热词 yolov8 画损失函数曲线图 背后是想判断训练是否健康。训练完runs/glue/exp1/下有results.csv,画图:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/glue/exp1/results.csv") df.columns = [c.strip() for c in df.columns] # 列名可能带空格 fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(df["epoch"], df["train/box_loss"], label="box") axes[0].plot(df["epoch"], df["train/cls_loss"], label="cls") axes[0].plot(df["epoch"], df["train/dfl_loss"], label="dfl") axes[0].set_title("train loss") axes[0].legend() axes[1].plot(df["epoch"], df["metrics/mAP50(B)"], label="mAP50") axes[1].plot(df["epoch"], df["metrics/mAP50-95(B)"], label="mAP50-95") axes[1].set_title("val mAP") axes[1].legend() plt.savefig("loss_curve.png", dpi=150)判断标准:三条 loss 应平滑下降,若 cls_loss 震荡剧烈,多半是类别不平衡或学习率过大;若 mAP50 早早到 0.9 但 mAP50-95 很低,说明框位置不够准,可能是标注框太松。验证 loss 开始上升而训练 loss 继续降,就是过拟合,该早停或加增强。
3.4 模型选型与改进的边界
热词 yolov8 改进、yolov8 head 改进、协调注意力机制 很热,但我要泼盆冷水:毕设或课程设计阶段,先把 baseline 跑稳比堆改进重要。如果 baseline 的 mAP50 已经 0.85 以上,再考虑加注意力。加注意力前先确认瓶颈在哪——是漏检小目标还是分类混淆。幕墙胶缝是细长目标,如果漏检多,优先调imgsz和 anchor,而不是盲目加模块。改进后必须做消融对比,同一份验证集、同一个种子,否则指标波动你分不清是改进还是玄学。
4. 可视化界面:把检测结果变成能给人看的评估报告
4.1 界面选型:Gradio 还是 Streamlit
毕设演示要的是「打开就能用」,我一般选 Gradio,因为它对图像输入输出支持最直接,几十行就能出一个能上传、能显示、能下载的界面。Streamlit 更适合做多页报表。这里用 Gradio:
import gradio as gr from ultralytics import YOLO from PIL import Image import numpy as np model = YOLO("runs/glue/exp1/weights/best.pt") CLASSES = ["normal", "slight", "moderate", "severe"] # 等级到建议的映射,界面直接给结论 ADVICE = { "normal": "常规巡检", "slight": "缩短巡检周期", "moderate": "安排复检", "severe": "立即维修", } def assess(img): if img is None: return None, "请上传幕墙图像" results = model.predict(img, imgsz=640, conf=0.25, iou=0.45) r = results[0] annotated = r.plot() # BGR numpy annotated = annotated[:, :, ::-1] # 转 RGB 给界面 counts = {} for box in r.boxes: name = CLASSES[int(box.cls)] counts[name] = counts.get(name, 0) + 1 if not counts: return annotated, "未检测到胶缝区域,请确认图像角度" # 取最严重等级作为整体结论 order = ["severe", "moderate", "slight", "normal"] worst = next(c for c in order if c in counts) detail = ",".join(f"{k}:{v}" for k, v in counts.items()) return annotated, f"整体评估:{worst}({ADVICE[worst]})\n明细:{detail}" demo = gr.Interface( fn=assess, inputs=gr.Image(type="numpy"), outputs=[gr.Image(label="检测结果"), gr.Textbox(label="评估结论")], title="玻璃幕墙结构胶老化评估", ) demo.launch(server_name="0.0.0.0", server_port=7860)逻辑说明:conf=0.25是置信度阈值,太低会误检,太高会漏检,幕墙场景我一般从 0.25 起调;iou=0.45控制重叠框合并。整体结论取最严重等级,符合工程上「就高不就低」的安全原则。r.plot()返回 BGR,必须转 RGB,否则界面颜色发蓝,这是新手最常见的翻车点。
4.2 界面之外还要留一份可存档记录
演示界面好看,但工程上要留痕。我一般加一个 CSV 落盘,每次评估记录时间、文件名、各等级数量、整体结论。这样巡检报告可追溯,也方便后续统计某面墙的老化趋势。落盘代码放在assess里,用csv追加写即可,注意加文件锁或单线程,Gradio 默认并发下多请求同时写会乱。
5. 部署与避坑:从本机跑通到别人也能跑通
5.1 部署教程该覆盖的最小闭环
热词里部署教程、docker 部署、rk3588 部署 yolov8 都指向同一个诉求:换台机器还能跑。最小闭环是:依赖清单、权重文件、启动命令、端口说明。我一般给一个requirements.txt加一个start.sh:
# start.sh #!/bin/bash set -e python -c "from ultralytics import YOLO; YOLO('yolov8s.pt')" # 预热检查 python app.pyDocker 化时基础镜像选python:3.10-slim,把权重和代码 COPY 进去,暴露 7860 端口。注意 slim 镜像缺 OpenCV 依赖,要补libgl1和libglib2.0-0,否则import cv2直接报错,这个坑几乎人人踩一次。
5.2 常见问题排查
现象:训练一开始就报类别越界。原因:data.yaml的names顺序和标注转换时的CLASSES不一致,标签里的类别 id 超出nc。解决:两边顺序逐字对齐,改完重新生成标签。
现象:界面检测框颜色全一样,分不清等级。原因:r.plot()默认按类别上色,但如果类别 id 映射错,颜色会错乱。解决:确认CLASSES顺序,必要时自己写画框逻辑,按等级指定颜色。
现象:验证集 mAP 很高,实际用起来一塌糊涂。原因:数据集按随机划分,同一面墙的图同时进了训练和验证,指标虚高。解决:按幕墙编号分组划分,重训。
现象:CPU 训练慢到怀疑人生。原因:imgsz和batch太大,或没开多线程。解决:降到imgsz=416、batch=4,设workers=4,或者干脆先用yolov8n跑通流程再换大模型。
现象:Docker 里启动报缺库。原因:slim 镜像缺系统级依赖。解决:Dockerfile 里apt-get install -y libgl1 libglib2.0-0,再装 Python 依赖。
6. 让评估结果更可信:置信度校准与一个实用技巧
模型给出「severe」不代表一定准,工程上要的是可信。我一般做两件事:一是置信度校准,用验证集统计每个等级在不同置信度阈值下的准确率和召回率,画 PR 曲线,选一个让严重老化召回优先的阈值——安全场景宁可误报不可漏报,所以 severe 类的阈值我会压到 0.2 左右。二是多帧投票,同一段胶缝如果有多张不同角度的图,对同一区域的检测结果做投票,单帧误检会被压下去。
import numpy as np from collections import Counter def vote_severity(frame_results): # frame_results: 每帧的整体结论列表,如 ["moderate","severe","moderate"] cnt = Counter(frame_results) # 严重等级加权,安全优先 weight = {"severe": 3, "moderate": 2, "slight": 1, "normal": 0} score = sum(weight[r] for r in frame_results) / len(frame_results) if score >= 2.0: return "severe" if score >= 1.0: return "moderate" if score > 0: return "slight" return "normal"这个加权投票的逻辑是:只要多帧里 severe 占比够高,整体就判 severe,避免单帧漏检导致低估风险。参数weight可以按项目实际调整,但 severe 的权重一定要拉开。
还有一个我踩过坑才养成的习惯:每次改完数据或参数,先跑 10 张图的推理可视化,肉眼确认框和类别都对,再启动长训练。血泪经验是,直接开 150 轮训练,两小时后才发现标签全错,那种后悔药没处买。这套系统值不值得做?如果你要做毕设或课程设计,它把数据、模型、界面、部署四条线都串起来了,工作量饱满且可复现;如果你要做真实巡检,它能当辅助筛查,但别指望它替代人工复检,边界要清楚。希望帮到你。
本文还有配套的精品资源,点击获取