基于YOLOv8的桥梁裂缝检测系统:从数据集处理到可视化部署
简介这套基于YOLOv8的桥梁裂缝检测系统面向计算机视觉、人工智能方向的毕业设计或课程设计场景涵盖从模型训练到可视化展示的完整流程。包内共97个文件以70个Python脚本作为核心代码配合4个预训练权重.pt、5个模型配置.xml、说明文档.txt及操作演示视频.mp4压缩包仅24.21MB整体轻量且结构清晰。项目代码经测试可直接运行自带完整数据集与可视化界面支持生成核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图便于在答辩时直观呈现模型效果。目前已有51人学习下载适合计算机相关专业学生快速搭建桥梁裂缝检测演示系统、完成毕设或课设任务也可作为入门深度学习目标检测项目的参考范例。1. 桥梁裂缝检测为什么用 YOLOv8先搞清楚它解决什么做桥梁裂缝检测的系统我一般会先反问一句你到底是要“数裂缝”还是要“标裂缝”。如果只是判断桥梁有没有裂传统 CV 的边缘检测加上阈值分割就能应付一部分场景但遇到光照不均匀、苔藓覆盖、网状细裂纹就集体失灵。而这个项目标题直接锁定了 YOLOv8等于把问题收敛成了“用目标检测模型在图像里框出每一条可见裂缝”再配上可视化界面和部署教程整套东西的核心价值是把深度学习的检测能力落地成一个能演示、能耗时评估、能写进论文或毕设说明书的完整系统。它适合三类人正在做土木/计算机交叉方向毕业设计的学生想给课题组快速搭一套桥梁病害检测原型的工程师以及想验证 YOLOv8 在细长目标上到底能不能用的人。接下来我会按数据、环境、训练、界面、踩坑、部署这个顺序把各个环节的关键参数和常见翻车点讲清楚。2. 数据集整理与 YOLO 格式转换80% 的效果在数据不在模型2.1 桥梁裂缝数据集从哪里来、怎么划分很多人拿到这个项目第一件事就是打开训练脚本恨不得马上让 loss 降下去。但我看了不少翻车案例最后的根因几乎都出现在数据上。桥梁裂缝检测的数据集常见的做法是使用公开的裂缝数据集或自己拍摄的桥梁结构表面图像。这类数据集大量存在通常包含海堤裂缝、路面裂缝、墙体裂缝等但桥梁裂缝和路面裂缝的纹理背景差异很大桥梁混凝土表面有模板接缝、水渍、蜂窝麻面这些都会干扰检测器。所以不要盲目把所有裂缝数据混在一起训练优先选择和桥梁混凝土表面接近的样本。数据集规模没有绝对标准但我在实际操作中有一个经验基线单个类别至少 300 到 500 张真实图像通过切块和增广扩到 2000 张以上模型才会稳定收敛。整个数据集按 8:1:1 划分成 train / val / test。有一个容易被忽略的点是划分时要按“图像来源”分而不是按“图像文件”随机分。如果你把同一根桥梁的连续拍摄帧既放进 train 又放进 val验证集的 mAP 会虚高等部署到一座新桥时性能立刻露出原形。我自己吃过这个亏val 上 mAP 0.95换一座桥只剩 0.6后面查数据才发现验证集和训练集来自同一段视频的连续截帧。数据标注方面如果数据集原本是 VOC 格式XML你要先用脚本转成 YOLO 格式TXT。如果数据集是已经切好的 512x512 或 640x640 小图那标注框的绝对像素尺寸会比较小这在 YOLOv8 里算不上致命问题但后续训练时输入分辨率建议不要低于 640。2.2 将 VOC 标注转成 YOLO 格式转换脚本与四个边界坑YOLOv8 使用的标注格式是每个 TXT 文件对应一张图像文件名与图像同名每行内容为类别ID 归一化中心x 归一化中心y 归一化宽度 归一化高度。下面这个脚本是我常用的 VOC 转 YOLO 工具直接基于 xml.etree 实现不依赖额外库。import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_dir, class_names): 将单个VOC格式XML转为YOLO格式TXT xml_path: 标注文件路径 out_dir: 输出目录 class_names: 类别名列表, 索引即类别ID os.makedirs(out_dir, exist_okTrue) tree ET.parse(xml_path) root tree.getroot() # 注意: 有的XML里 width/height 在 size 节点, 有的直接在 annotation 下 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) if img_w 0 or img_h 0: print(f[跳过] {xml_path} 的宽高为0) return lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) # 有些标注工具会同时输出 truncated / difficult 节点, 建议直接跳过 difficult difficult obj.find(difficult) if difficult is not None and difficult.text 1: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 边界检查: 防止多边形标注转矩形时坐标越界 x1 max(0.0, min(x1, img_w - 1)) x2 max(0.0, min(x2, img_w - 1)) y1 max(0.0, min(y1, img_h - 1)) y2 max(0.0, min(y2, img_h - 1)) # 过滤掉宽或高小于2像素的无效框 if x2 - x1 2 or y2 - y1 2: continue cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 归一化后数值必须在(0,1]区间, 否则YOLOv8训练会直接报错或忽略该框 cx max(0.0, min(cx, 1.0)) cy max(0.0, min(cy, 1.0)) w max(0.0, min(w, 1.0)) h max(0.0, min(h, 1.0)) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) txt_path Path(out_dir) / (Path(xml_path).stem .txt) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) print(f[完成] {txt_path.name}, 有效框 {len(lines)} 个) if __name__ __main__: # 示例: 只保留 crack 一类 class_names [crack] xml_dir annotations_voc out_dir labels_yolo for xml_file in Path(xml_dir).glob(*.xml): voc_to_yolo(str(xml_file), out_dir, class_names)这段代码的核心逻辑是按比例归一化坐标但在实际处理中会有四个边界坑。第一是 XML 里size节点缺失或宽高写反这会导致所有框错位建议转换后随机抽几张图把标注框画回去检查。第二个坑是裂缝的矩形标注框长宽比极大有些框宽度差不到 3 个像素YOLOv8 的特征图下采样 32 倍后这个框在最小的特征图上几乎就是一个点所以转换时至少要过滤掉小于 2~3 像素的框。第三个坑是归一化后数值可能略大于 1原因是有标注工具允许 bndbox 的 xmax 超出图像宽度训练时 Ultralytics 会给出corrupt box警告不会报错但会把这些框丢掉导致有效样本减少。第四个坑是类别索引必须从 0 开始如果你的数据集中 crack 排在第二列写成cls_id class_names.index(name)返回的可能是 1但数据 yaml 里 crack 的索引也是 1两边一致就行最怕的是脚本里写死cls_id 0而某一类标注名称拼写错误被静默跳过。2.3 数据增强策略与类别平衡桥梁裂缝检测和常规目标检测有一个明显差异裂缝是细长结构占整张图像的面积比例很低一个 640x640 的图中裂缝框可能只有几十个像素宽。YOLOv8 默认开启 Mosaic 增强把四张图拼接在一起训练对提升小目标检测有帮助。但对裂缝而言Mosaic 拼接时如果随机裁剪位置不当可能把一条完整裂缝切成两段导致标注框语义不完整。我的做法是前 30 个 epoch 关闭 Mosaic或者把mosaic0.3调低让模型先适应完整裂缝形态后期再逐步加强。类别平衡在单类裂缝数据集里通常不是问题但你要注意“困难样本”的平衡。桥面背景里有大量类似裂缝的纹理——模板缝、划痕、钢筋锈迹流挂——这些负样本如果不单独收集模型会把所有深色线条都当成裂缝。我的经验是训练集中额外加入 20%~30% 的纯负样本图没有裂缝的桥面这些图对应的 TXT 标注文件是空文件。YOLOv8 原生支持空标注文件模型会从这些图里学到背景模式训练出的模型误检率会明显下降。3. Ubuntu 与 Windows 下的训练环境CPU 版也能跑通最小流程3.1 两种环境的准备步骤你可能拿到的项目源码通常默认你使用显卡训练但相当一部分做课程设计的人手里只有一台普通笔记本。这里有两个选择用 CPU 硬跑或者租云端 GPU 服务并上传训练。先说 CPU 方案。YOLOv8 在 CPU 上训练不是不能跑但你要控制数据量。假设你有 1500 张 640x640 的图、100 个 epoch、batch size 设为 8在我的实测中i5-1240P 笔记本大概要跑 8 到 12 小时。这个时间虽长但作为课程设计是完全可以接受的。如果你拿到的是已经训练好的权重只是要跑通推理流程CPU 完全没有问题。# 创建虚拟环境, 避免污染系统Python python -m venv yolov8_env source yolov8_env/bin/activate # Windows: yolov8_env\Scripts\activate # 安装PyTorch CPU版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装Ultralytics pip install ultralytics如果你有 NVIDIA 显卡比如 GTX 1660 Ti 或 RTX 3060就安装 CUDA 版本的 PyTorch。注意 CUDA 版本要与显卡驱动匹配安装完用python -c import torch; print(torch.cuda.is_available())验证返回 True 再继续否则后面训练会以 CPU 模式运行速度慢且你毫无察觉。我见过有人在 NVIDIA 显卡上装错了 PyTorch 版本训练了 3 个小时才发现 GPU 占用率为 0%那种心情只能用玄学来解释。3.2 训练命令与必调参数训练命令本身并不复杂Ultralytics 封装了绝大多数逻辑。把训练集和验证集的图像、标注文件按下面的结构放好再写一个 data.yaml 指向它们就可以开始训练了。yolo detect train \ databridge_crack.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch8 \ patience20 \ projectruns \ namecrack_exp \ device0对应的bridge_crack.yaml内容如下path: /home/user/bridge_crack # 数据集根目录 train: images/train val: images/val test: images/test nc: 1 names: [crack]参数说明modelyolov8n.pt是 Nano 版本参数量最小CPU 训练勉强能接受。如果你的机器配置尚可可以换yolov8s.pt精度会提升 3 到 5 个百分点但训练时间翻倍。imgsz640是输入分辨率训练时不要调太大否则 CPU 训练时间呈指数增长。patience20表示验证集指标连续 20 个 epoch 不提升就早停这个值在 CPU 训练时务必保留能帮你省掉大量无效训练时间。device0指定使用第一张显卡CPU 训练时直接删掉这行或设为cpu。3.3 训练产物解析与权重选择训练过程中Ultralytics 会在runs/detect/crack_exp/下持续写入权重文件和训练曲线图。训练结束后你需要重点看四个文件还有一个容易被忽视的坑。weights/best.pt是验证集指标最好的权重weights/last.pt是最后一轮的权重。对于毕设演示优先用 best.pt因为它泛化性通常更好。results.png里包含 loss 曲线和 mAP 曲线如果 val 的 mAP50 在训练后期还在上升但 box_loss 已经震荡说明学习率偏大可以调低lr0后重训或加cos_lrTrue让学习率平滑衰减。还有confusion_matrix.png如果背景被误检为 crack 的比例很高就要回到第 2 章说的负样本问题。真正容易被忽略的文件是args.yaml。这个文件记录了本次训练的全部超参数包括随机种子。如果你后面启用了 AMP 混合精度训练默认开启在某些老显卡上可能遇到 loss 变成 NaN 的玄学问题。这时去args.yaml里确认amp是否为 True如果是在训练命令里加上ampFalse重跑大概率能解决。4. 训练参数与可视化界面对接让检测结果能被人看懂4.1 可视化界面的技术选型PyQt5 比 Tkinter 更合适标题里强调“可视化界面”这也直接关系到毕设答辩的演示效果。常见的做法是用 PyQt5 写一个本地桌面应用图像显示控件用 QLabel 或 QGraphicsView检测结果实时画框。选 PyQt5 而不是 Tkinter原因有两个一是 PyQt5 的 QImage / QPixmap 与 OpenCV 的 BGR 图像格式转换路径很成熟网上大量代码片段可以直接抄二是 PyQt5 的布局控件更适合做“加载图片 → 显示检测结果 → 统计裂缝数量”这种三步交互界面观感也比 Tkinter 好一截。pip install pyqt5 opencv-python界面逻辑通常包含三个区域左侧是图片预览区右侧是“选择图片”和“开始检测”按钮下方是结果信息栏显示检测到多少条裂缝、平均置信度、单张推理耗时。这个布局对答辩演示足够清晰评审老师一眼就能看出系统做了什么。4.2 封装检测类实现与界面解耦不要检测代码和界面代码全写在同一个 Python 文件里那会导致后续排错时一个报错整页飘红。我一般把 YOLOv8 的模型加载和推理封装成一个类界面文件单独写。下面是一个可直接复用的检测类封装方式import cv2 import torch from ultralytics import YOLO class BridgeCrackDetector: 桥梁裂缝检测器封装: 负责模型加载、推理、结果格式化 def __init__(self, weights_path: str, conf_thres: float 0.35, device: str ): # device 时由Ultralytics自动选择GPU或CPU self.model YOLO(weights_path) self.conf_thres conf_thres self.device device if device else (0 if torch.cuda.is_available() else cpu) def detect(self, image_bgr): 输入: OpenCV读取的BGR图像 返回: 绘制结果图, 裂缝列表, 耗时(ms) t0 cv2.getTickCount() results self.model.predict( sourceimage_bgr, confself.conf_thres, deviceself.device, verboseFalse, # 关闭终端打印, 界面运行时更清爽 imgsz640, ) cost_ms (cv2.getTickCount() - t0) / cv2.getTickFrequency() * 1000 fact_list [] boxes results[0].boxes if boxes is not None: for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) fact_list.append({ bbox: [int(x1), int(y1), int(x2), int(y2)], confidence: round(conf, 3) }) rendered results[0].plot() # Ultralytics内置绘制, 返回BGR图 return rendered, fact_list, round(cost_ms, 1)封装完之后界面的“开始检测”按钮只需要调用detector.detect(frame)拿返回值并刷新界面逻辑非常干净。参数里有两个细节conf0.35是经验值。桥梁裂缝检测的代价不对称漏检一条裂缝的后果远大于误检所以置信度阈值不要设得太高0.25~0.35 是常用区间。imgsz640与训练一致不要推理时改成 1280否则检测框的位置会偏移虽然 Ultralytics 会自动做 letterbox 缩放但框坐标是在缩放后图像上的你需要额外映射回原图。用results[0].plot()的好处是它返回的就是原图像尺寸绘制结果避免了坐标映射这个最容易出错的环节。4.3 界面代码的核心路径界面端的代码不需要太长我这里给出一个精简但能跑的框架重点展示图像加载与结果显示之间的数据类型转换。import sys import cv2 from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout, QFileDialog, QTextEdit from bridge_detector import BridgeCrackDetector # 上面定义的类 class CrackApp(QWidget): def __init__(self): super().__init__() self.setWindowTitle(桥梁裂缝检测系统) self.detector BridgeCrackDetector(weights/best.pt, conf_thres0.35) self.image_label QLabel(请选择图片) self.image_label.setMinimumSize(640, 640) self.btn_open QPushButton(选择图片) self.btn_detect QPushButton(开始检测) self.result_text QTextEdit() self.result_text.setReadOnly(True) layout QVBoxLayout() layout.addWidget(self.image_label) layout.addWidget(self.btn_open) layout.addWidget(self.btn_detect) layout.addWidget(self.result_text) self.setLayout(layout) self.btn_open.clicked.connect(self.open_image) self.btn_detect.clicked.connect(self.run_detect) self.current_image None def open_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.png *.jpg *.bmp)) if path: self.current_image cv2.imdecode( cv2.imread(path), cv2.IMREAD_COLOR) self.show_image(self.current_image) def show_image(self, image_bgr): h, w, ch image_bgr.shape bytes_per_line ch * w qimg QImage(image_bgr.data, w, h, bytes_per_line, QImage.Format_BGR888) self.image_label.setPixmap(QPixmap.fromImage(qimg).scaled(640, 640)) def run_detect(self): if self.current_image is None: self.result_text.setText(请先选择图片) return rendered, facts, cost_ms self.detector.detect(self.current_image) self.show_image(rendered) msg f检测到 {len(facts)} 条裂缝, 耗时 {cost_ms} ms\n for i, f in enumerate(facts, 1): msg f [{i}] 位置: {f[bbox]}, 置信度: {f[confidence]}\n self.result_text.setText(msg) if __name__ __main__: app QApplication(sys.argv) window CrackApp() window.show() sys.exit(app.exec_())这个界面代码里有三个值得注意的关键点。第一是用QImage.Format_BGR888而不是Format_RGB888因为 OpenCV 读进来的是 BGR 顺序如果你直接用 RGB 格式显示图像颜色会整体偏蓝偏橙这是一个非常容易踩的坑。第二是QPixmap.fromImage(qimg).scaled(640, 640)界面缩放显示不会影响检测用的原始图像因为detect()始终基于原始分辨率执行。第三是在open_image里我没做读图失败判断实际使用中加上if img is None的提示会更稳妥但为了精简代码这里省略了。5. 常见踩坑与排查标注、环境、过拟合的三类事故现场5.1 标注框错位但训练不报错现象训练正常收敛但推理时检测框总是偏向裂缝的一侧甚至框在裂缝上方或下方。原因最常见的是 VOC 转 YOLO 时图像尺寸读错。某些公开数据集的 XML 里写入的 width 和 height 与实际图片不一致比如 XML 里写 512x512实际图片是 640x480导致归一化坐标计算整体偏移。另一个常见原因是把标注文件和图像文件放进了不同的子目录训练时图像索引到了但加载的 TXT 是另一个同名文件。解决不要相信转换脚本的输出日志一定要做可视化验证。用 OpenCV 读取原始图像和 TXT 标注把框画出来人工检查 30~50 张图。代码非常简单发现异常及时修正数据集而不是先训一版再回头找问题。5.2 CPU 训练慢到怀疑人生现象一个 epoch 要跑 20 分钟100 个 epoch 要 30 多个小时。原因没有配置多线程。YOLOv8 在 CPU 训练时默认使用torch.set_num_threads的系统值但如果你在 Windows 上使用 CPU 版本的 PyTorchtorch 默认线程数可能只有 4。另外 batch size 太大也会拖慢 CPU 训练因为每个 batch 的梯度计算耗时随 batch 线性增长。解决在训练脚本开头加入以下代码把线程数调到 CPU 核心数import torch torch.set_num_threads(8) # 按自己CPU核心数调整同时把batch4CPU 场景建议 4 或 8如果还是慢就把imgsz降到 480 试试。代价是裂缝这种细长目标在 480 分辨率下可能显示不清但先跑通流程再提精度这个顺序不会错。5.3 训练 loss 不降反升现象前 20 个 epoch loss 震荡不下降甚至出现 NaN。原因有四个常见来源。第一是学习率过大Ultralytics 默认lr00.01如果你的数据集很小比如只有 200 张图这个学习率容易震荡。第二是标注文件里出现了归一化坐标超过 [0,1] 的情况虽然前面转换脚本里做了裁剪但如果你拿到的数据集是别人给的建议自己写检查脚本。第三是 AMP 混合精度在旧显卡上的兼容性问题。第四是类别 ID 越界数据 yaml 里写nc1但某个 TXT 文件里出现了cls 2训练会直接报错或产生梯度爆炸。解决先把学习率调到 0.001 跑 20 个 epoch 观察。如果 loss 曲线稳定下降再逐步调回。同时用下面的脚本扫描全部标注文件检查类别 ID 是否越界# 统计所有txt中出现的最大类别ID grep -h ^[0-9] labels/train/*.txt | awk {print $1} | sort -n | tail -n 1如果输出是 0说明一切正常如果不是 0检查对应标注文件并修正。这个命令在遇到抹不平的玄学问题时能帮你快速排除数据问题节省至少半天时间。5.4 测试时对整桥图像检测效果差现象模型在验证集单张图上 mAP50 有 0.9 以上但把一张 4000x3000 的桥梁全景图喂进去几乎检测不到裂缝。原因YOLOv8 的推理会将输入图像按 letterbox 缩放为 640x640。4000x3000 的图像缩放后原本只有 20 像素宽的裂缝会变成 3 个像素宽特征图下采样后基本丢失。这是第一个坑。第二个坑是全景图中裂缝分布稀疏置信度阈值 0.35 会把大量低分框过滤掉而局部小块中裂缝的上下文特征更明显。解决对桥梁大图做滑窗推理。把原图按 640x640 切成有重叠的块重叠率 20%每块独立推理再用 NMS 合并重叠区域的检测框。Ultralytics 没有直接提供滑窗接口但实现并不复杂。这是桥梁裂缝检测从“演示能用”走向“现场能用”的关键一步也是答辩时一个很好的加分点。5.5 GPU 显存不足或 PyTorch 版本冲突现象环境配置完成后运行import torch; torch.cuda.is_available()返回 False或训练时报CUDA out of memory。原因前者通常是你装了 CPU 版 PyTorch或 CUDA 版本与 PyTorch 的预编译版本不匹配。后者常见于 4GB 显存的显卡比如 GTX 1650默认 batch16 会直接炸显存。解决显存不足时把 batch 降到 4把imgsz降到 640关闭workers0Windows 上数据加载线程过多也会占显存缓存。torch.cuda.is_available()返回 False 时先执行nvidia-smi查看驱动支持的 CUDA 版本再去 PyTorch 官网选择对应的安装命令。注意 PyTorch 的 CUDA 版本要求和驱动不是同一个东西驱动向下兼容你只需保证 PyTorch 要求的 CUDA 版本 ≤ 驱动支持的版本即可。这个坑相当典型别问我怎么知道的说多了都是泪。6. 把模型部署成可用工具验证指标与批量推理技巧6.1 模型导出从 PyTorch 权重到 ONNX训练完成后best.pt 只能在 Python 环境里使用。如果后续想用 C 调用或者部署到没有 PyTorch 的嵌入式设备上需要先导出为 ONNX 格式。这一步我建议在训练后立刻做因为 ONNX 在 CPU 上的推理速度通常比 PyTorch 原版快 20%~40%而且不会出现 PyTorch 版本升级后权重无法加载的兼容性问题。yolo export modelruns/detect/crack_exp/weights/best.pt formatonnx dynamicTrue导出完成后在同一目录下生成best.onnx。推理代码改用 onnxruntime这样目标设备上只需要安装onnxruntime和opencv-python不再需要完整的 PyTorch 和 CUDA 环境。这是一个很实用的部署技巧因为现场设备往往没有 GPU也没有足够的磁盘空间安装全套深度学习框架。6.2 批量验证用脚本统计误检率和漏检率毕设答辩时老师常问“你这个模型精度多少”你不能只说 mAP500.93还要说明这个精度是在什么数据集上、什么置信度阈值下得到的。除了 Ultralytics 自带的 val 命令建议额外写一个批量脚本统计每一张测试图上的误检框和漏检框。这个脚本的价值在于能够生成可展示的评估报告比如“在 120 张测试图中误检 12 张漏检 5 张平均置信度 0.78”比单纯 mAP 更有说服力。yolo detect val \ databridge_crack.yaml \ modelweights/best.pt \ conf0.35 \ iou0.45运行结束后在runs/detect/val/下会生成混淆矩阵和验证样本预测图。这里有一个小技巧把置信度阈值从 0.5 降到 0.25 跑一次、从 0.35 跑一次对比两次的 mAP50 变化幅度如果 mAP50 掉得很少说明模型对低置信度框的判断依然可靠可以放心调低保真度换取高召回率。6.3 接口化封装把检测功能嵌入其他系统如果你的毕设后续还要接前端页面、数据库或者要做一个桥梁检测管理系统建议把第 4 章写的BridgeCrackDetector类再做一个 Flask 接口用 HTTP POST 上传图片返回检测结果 JSON。这样做的好处是可视化界面、Web 端、离线脚本三套入口复用同一个推理逻辑以后换模型权重只需要改一个路径不用动业务代码。具体做法是在检测类上包一层 Flask 路由接收文件流调用detect()最后把裂缝列表序列化成 JSON。这个方案在部署教程里通常会作为“进阶功能”但它并不复杂大约 30 行代码却能显著提升系统的完整性和工程感。关于部署方向我再补充一个我自己的习惯拿到一个新机器部署这套系统时先不用急着跑训练直接下载或复制一份训练好的权重先把界面跑通再回头跑训练流程。这样能快速排除环境问题也能让你对系统的每个环节都有把握。我自己第一次跑通这个系统时先在 CPU 上推理测试接着在 1660Ti 上重启训练对比两种硬件的速度和精度差异对整个流程的理解远比直接拿现成权重深得多。这个项目的上限不高但把它彻底跑透、每个参数都知道为什么要这么调它带给你的不是一个能演示的界面而是一套从头到尾的问题排查方法论。希望帮到你。本文还有配套的精品资源点击获取