☰
布料缺陷检测系统实战:YOLOv12训练与Web部署全记录
2026/10/20 10:13:22 网站建设 项目流程

这段时间一直在折腾一个布料缺陷识别检测系统,从数据标注一路做到 Web 界面,算是一个完整的 YOLOv12 落地项目。整套系统包含 YOLOv12 检测模型、YOLO 格式数据集、Python 工程源码、Web UI 界面和登录注册模块,同时保留了训练好的模型权重文件。这篇东西不是来科普 YOLOv12 原理的,重点讲实际做系统时遇到的问题、取舍思路和那些文档里不会写的坑。适合两类人看:一是正在做相关课程设计或毕业设计的同学,想把项目从"只跑通一个 demo"推进到"像个完整产品";二是工厂里想用视觉检测替代部分人工质检的技术人员,需要一个能直接部署试运行的底子。

1. 从产线质检到智能检测:为什么最后选了 YOLOv12 这条路

1.1 人工质检的真实痛点

布料缺陷检测这事,看起来不起眼,真正接触过才知道有多折磨人。纺织产线上的质检工位通常是强光下看布面,眼睛盯着高速移动的布匹,寻找破洞、污渍、褶皱、色差这类缺陷。流水线速度快起来,人的注意力撑不了太长时间,漏检率会随着疲劳快速上升。很多工厂实际靠的是"抽检"而不是"全检",抽检漏掉的次品一旦流到下游,轻则退货,重则整批赔偿。

我当时去现场看了一圈,回来就确定这个项目必须做视觉检测替代。布料缺陷和通用物体检测还不一样:布料本身是纹理密集的材质,缺陷和正常纹理之间的对比度往往很低,一个浅色污渍混在白色坯布上,人眼都要凑近才能发现,算法如果只套用通用目标检测模型,很容易在纹理背景上产生大量误检。这也是为什么不能简单拿一个预训练模型直接上产线,必须用专门的布料数据做训练。

1.2 算法选型:为什么是 YOLOv12

做检测方案,第一反应肯定是 YOLO 系列,毕竟生态成熟、部署方便、社区资料多。但在具体选哪个版本上,我纠结了一阵。YOLOv8 已经很稳定,网上教程最多,出了问题好查资料;YOLOv11 在特征融合上做了优化,精度有小幅提升;而 YOLOv12 是当时最新的迭代版本,最大的变化是在网络结构里引入了注意力机制的相关改进,用更高效的计算方式替代了一部分传统卷积模块,官方给出的收益是同等算力下精度更好,或者同等精度下推理更快。

我把几个版本放在同一批数据上简单对比过,结果如下:

版本训练收敛速度小目标缺陷表现部署生态备注
YOLOv8快一般很成熟中规中矩,最稳妥
YOLOv11较快较好较成熟在 Neck 部分有优化
YOLOv12中等好官方仓库维护中注意力机制有优势,值得实践

最终选 YOLOv12,不是单纯追求"用最新版",而是布料缺陷恰好有它的用武之地:破洞和细小污渍属于中小目标,注意力机制对这类特征的捕捉比纯卷积更敏感。而且项目本身的价值之一就是跑通新模型,踩坑经验对读者也有参考意义。需要提醒的是,如果你的目标是上线生产且没有专人维护,YOLOv8 依然是低风险选择;如果是做毕设、实践项目、技术预研,YOLOv12 完全值得上。

1.3 系统整体骨架

整套系统的结构并不复杂,但麻雀虽小五脏俱全。我把它分成四个模块:

  • 数据模块:布料缺陷图片集,标注结果统一成 YOLO 格式的 txt 文件。
  • 模型模块:YOLOv12 训练脚本、环境配置、训练好的 best.pt 权重。
  • 后端模块:Python + Web 框架,负责处理登录注册、图片上传、调用模型推理、返回检测结果。
  • 前端模块:浏览器端的 UI 界面,包含登录页、注册页、检测页、结果展示和历史记录。

四个模块的依赖关系很简单:后端起一个服务,加载模型权重,接收前端请求后跑推理,把画好框的图片和检测统计信息返回给页面。数据库只用来存用户信息和检测日志,不参与模型逻辑。

这就是标题里那串关键词对应的实际内容。下面我从数据、训练、Web 落地三个层面展开说。

2. 布料缺陷数据集:决定系统上限的往往是数据而不是模型

2.1 常见布料缺陷类型与标注规范

做布料缺陷检测,第一步不是急着训练,而是确定要检测哪些缺陷类别。我参考了多个公开的织物疵点数据集,结合工厂实际反馈,把最常见的缺陷归纳为六大类。

缺陷类型视觉特征检测难点
破洞布面出现孔洞,周围纹理断裂小目标,容易漏检
油污/污渍颜色异常块,边界不清晰与纹理阴影混淆
褶皱/折痕条状明暗变化易与正常光影误判
色差/色斑大面积颜色不均类别边界模糊
飞花/毛球表面纤维堆积目标密度大、尺寸小
异纤/杂质混入的异常纤维或颗粒外观变化大

标注规范上,建议所有缺陷都用矩形框,不要用多边形,因为 YOLO 系列本身输出就是矩形框,多边形标注最终也要转成外接矩形,徒增工作量还容易引入框偏差。类别编号从 0 开始排序,比如 hole=0、stain=1、fold=2,这个编号要和后面训练配置文件里的 names 顺序保持一致,否则训练出来全部乱套。

2.2 把标注结果统一成 YOLO 格式

现在标注工具大多用 LabelImg 或 Labelme。LabelImg 可以直接导出 YOLO 格式,省事;但很多人前期用的是 Labelme 的 json 格式,那就需要转换脚本。YOLO 格式每一行表示一个目标,结构是:类别编号 + 归一化中心点 x + 归一化中心点 y + 归一化框宽 w + 归一化框高 h。归一化意味着所有坐标值都除以图片宽高,这样不同分辨率的图片训练时不会出现坐标尺度混乱。

import json import os def labelme_to_yolo(json_path, output_dir, class_id_map): with open(json_path, encoding="utf-8") as f: data = json.load(f) img_w = data["imageWidth"] img_h = data["imageHeight"] lines = [] for shape in data["shapes"]: label = shape["label"] points = shape["points"] # 多边形或多段线顶点,取包围盒 xs = [p[0] for p in points] ys = [p[1] for p in points] min_x, max_x = min(xs), max(xs) min_y, max_y = min(ys), max(ys) cx = ((min_x + max_x) / 2.0) / img_w cy = ((min_y + max_y) / 2.0) / img_h bw = (max_x - min_x) / img_w bh = (max_y - min_y) / img_h class_id = class_id_map[label] lines.append(f"{class_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") image_name = os.path.splitext(os.path.basename(json_path))[0] + ".txt" with open(os.path.join(output_dir, image_name), "w", encoding="utf-8") as f: f.write("\n".join(lines))

转换完成后务必抽几十个 txt 打开人工检查,再写个小脚本把框画回图片上看一眼,确认没有坐标越界、框和缺陷位置偏移。这一步别省,我见过太多人标注时手滑框错位置,转换后也没复核,最后模型表现差还以为是训练参数的问题。

2.3 数据增强:用几百张图撑起一个可用模型

布料缺陷数据集不像通用物体数据集那么好找,公开数据量大都不够,需要自己拍摄或收集。我当时的原始数据大概只有小一千张图,缺陷分布也不均匀,直接训练容易过拟合。解决办法是数据增强做足。

增强策略我按重要性排序:

  • Mosaic 增强:把多张图拼成一张训练,相当于变相增大 batch 的上下文信息,对小目标检测帮助明显。
  • 随机亮度、对比度调整:模拟车间不同光照条件。布料的反光特性决定了光照变化对检测影响很大。
  • 轻微旋转和水平翻转:转动角度控制在 15 度以内。注意褶皱和破洞这类缺陷不能转太多,否则语义会变奇怪。
  • 高斯模糊:模拟快速产线上运动模糊的情况。
  • Copy-Paste:把同一张图里的典型缺陷复制粘贴到干净布料图上,能有效缓解样本不足的问题。

注意增强不是越狠越好。有次我把旋转角度开到 45 度,模型训练是收敛了,但实测时纯横纹布的正常纹理反而被误判成褶皱。这是因为过度增强改变了布料纹路的先验分布,模型学到了"旋转后的异常形状",而不是"缺陷本身"。

2.4 标注里的隐形坑

标了三天数据之后,我才意识到最大的坑不是标得累,而是标得不统一。不同人标同一张破洞图,有人框得很紧,有人留了很大边距;有人把一大片连续污渍拆成三个框,有人只框最明显的那块。模型训练时,同一个目标两种标注方式,等于给了相互矛盾的监督信号。

我的建议是:标注前写一份极简标注规范,打印出来贴在工位上。规定统一框到缺陷边缘外扩 3~5 个像素;连续缺陷算一个目标,除非明显分离;一个图里有多处同类型缺陷必须全部标出。另外,每类缺陷的实例数量尽量超过 200 个,低于这个量级,YOLOv12 再强也很难把类内特征学扎实。数据分布严重不均衡时,可以考虑给样本少的类别在 Loss 里加权重,但那是后话,先尽量补数据更实在。

还有一个容易忽视的点:背景负样本。如果所有图片里都至少有一个缺陷,模型会把"布料"和"有缺陷布料"混在一起,容易在干净布上乱框。要在训练集里掺入 10%~15% 的干净布料图,类型标注为空,让模型学会区分"没有目标"的语义。

3. 训练 YOLOv12:参数、监控与调优实操

3.1 环境准备与训练启动

训练环境用的是 Python 3.8 + PyTorch + CUDA 11.8,显存 12G 的卡,跑 YOLOv12s 这个规格很从容。如果显存只有 6G,建议把 imgsz 降到 640 以下,或者选更小的 n 规格模型,强行加大 batch 只会频繁 OOM。

数据目录结构如下:

dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/

然后在项目里建一个 fabric.yaml 数据集配置:

train: ./dataset/images/train val: ./dataset/images/val nc: 6 names: ['hole', 'stain', 'fold', 'color_difference', 'fluff', 'foreign_fiber']

数据划分按 8:1:1 拆成训练、验证、测试三份。我习惯用脚本固定随机种子划分,不然每次跑结果不一致,排查问题会很痛苦。

训练命令按官方仓库的接口来,大致是这样的形式:

python train.py --data fabric.yaml --weights yolov12s.pt --batch 16 --imgsz 640 --epochs 200

如果环境里有预训练权重,强烈建议加载,不要随机初始化从头训。YOLOv12 的预训练特征对纹理和边缘敏感,布料缺陷虽然属于工业场景,但底层特征和自然图像是相通的,迁移学习能显著缩短收敛时间。

3.2 训练曲线的正确读法

训练不是进程跑完就完事了,必须盯指标。我最关注四个指标:box_loss、cls_loss、mAP50、mAP50-95。

  • box_loss 和 cls_loss 持续下降且验证集不掉头,说明模型在学习。
  • mAP50 是判断缺陷"是否被检出"的主要依据,布料的验收标准建议至少 0.85 以上,否则现场误检漏检会多到没法用。
  • mAP50-95 反映框的定位精度。注意,布料缺陷检测对框的精确位置要求不算极端,差几个像素也能接受,所以 mAP50 比 mAP50-95 更重要。
  • Precision 高不代表 Recall 高,实际使用更看重在保证可接受误检率的前提下尽量提高 Recall。漏检流到客户那边是大事,误检顶多多看一眼产品。

训练日志里还有一个容易被忽略的细节:验证集 mAP 在网络还欠拟合时就一路飙升,但到中后期开始振荡,这是正常现象。重点看最后 50 个 epoch 的平均水平,不要被某个突然冲到高点的 epoch 迷惑,要选验证集 mAP50 最高的那个权重,而不是最后一个 epoch 的权重。

3.3 三个"练不动"问题的排查思路

训练过程中我遇到过几个典型问题,这里把排查链路写出来,方便复现。

第一个是 loss 一直不降,k线几乎是一条直线。先检查学习率,当 warmup 结束后学习率过大,loss 会在某个水平附近震荡,改小初始学习率能解决;如果学习率正常,就检查数据,把训练集里的 txt 标注和原图对齐看一遍,多半是标注有严重错误。我当时是类别编号映射错了,第 0 类被当成第 5 类,模型学了几天学了个寂寞。

第二个是 mAP 始终为 0。这个要警惕,可能原因有两个:训练集里完全没有某一类的正样本,导致该类 AP 为 0,拉低均值;或者验证集图片路径配错,模型看到的是从未见过的空白图。我建议训练前先单独写一段代码,分别统计训练集和验证集每个类别的实例数,缺了就补。

第三个是训练集 loss 很低、验证指标很差,典型的过拟合。解决方法是增强数据、提高 dropout,或者换成更小的模型规格。布料数据的背景纹理高度重复,模型很容易死记纹理细节而不是泛化缺陷概念,所以 dropout 的作用比通用检测任务更明显。

4. 从权重到产品:Web 界面与登录注册的落地

4.1 为什么做成 Web 而不是桌面程序

模型训练好,只是完成了第一步。标题里还有 UI 界面和登录注册界面,这说明项目是奔着"给人用"去的。做一个浏览器访问的 Web 系统,而不是 PyQt 桌面程序,主要原因是部署成本和质检场景的适配性。

工厂里最常见的形态是:一台服务器部署模型,车间里的质检工位各有一台普通电脑甚至平板,只要浏览器能打开配置好的地址,就能用系统。Web 方案免安装、免配置、多终端同时访问,出新版本只需要在服务器上更新代码,客户端全都不用动。桌面程序虽然启动更快,但每台机器都要装 Python 环境、装依赖库、拷模型文件,这些在工业现场都是麻烦。

4.2 登录注册模块的技术细节

登录注册模块用 Python Web 框架做后端,数据库我用 SQLite 起步,后续要换 MySQL 也就改个连接字符串的事。密码不能明文存,必须用哈希算法加密。代码里我用的是开发框架自带的 werkzeug 密码哈希工具,内部会自动加盐,安全级别足够。

from werkzeug.security import check_password_hash, generate_password_hash @app.route("/register", methods=["POST"]) def register(): username = request.form.get("username", "").strip() password = request.form.get("password", "").strip() if not username or not password: return jsonify({"code": 1, "msg": "用户名和密码不能为空"}) if find_user(username): return jsonify({"code": 2, "msg": "用户名已存在"}) hashed = generate_password_hash(password) create_user(username, hashed) return jsonify({"code": 0, "msg": "注册成功"})

登录逻辑类似,比对哈希通过后,用 Web 框架自带的 session 机制保持登录状态。需要注意 session 的密钥要单独配置一份,不要写在代码里提交到仓库,这一点常被忽略,但对系统安全影响很大。

前端登录注册页面的逻辑不复杂:一个表单、两次密码校验、一个跳转。真正有价值的地方是错误提示要具体,比如"用户已存在"和"密码太短"分开提示,而不是笼统地显示"注册失败",这样用户不会以为系统坏了。

4.3 检测上传与结果展示流程

检测流程是整个 Web 应用的核心。前端上传一张图片,后端接收后把图片从字节流转成 OpenCV 能读的 numpy 数组,送进 YOLOv12 模型推理,把检测结果画到图片上,再把图片转成 base64 编码返回给前端显示。

from flask import Flask, render_template, request, jsonify import cv2 import numpy as np from ultralytics import YOLO app = Flask(__name__) model = YOLO("weights/best.pt") @app.route("/detect", methods=["POST"]) def detect(): if "image" not in request.files: return jsonify({"code": 1, "msg": "没有上传图片"}) file = request.files["image"] img_bytes = np.frombuffer(file.read(), np.uint8) img = cv2.imdecode(img_bytes, cv2.IMREAD_COLOR) results = model.predict(img, conf=0.35, iou=0.5, imgsz=640, verbose=False) boxes = [] for r in results: for box in r.boxes: x1, y1, x2, y2 = [round(v) for v in box.xyxy[0].tolist()] conf = round(float(box.conf[0]), 3) cls_id = int(box.cls[0]) boxes.append({"bbox": [x1, y1, x2, y2], "conf": conf, "cls": cls_id}) return jsonify({"code": 0, "boxes": boxes, "count": len(boxes)})

用 base64 直接传输图片比生成临时文件再返回 URL 更简洁,也不用处理临时文件的清理问题。前端拿到坐标后,在 Canvas 上画框或者直接把坐标渲染成 DOM 元素都行。我个人推荐在前端画框,因为后端已经返回了检测坐标,前端显示更灵活,用户可以自由切换"只看框"和"看原图"。

4.4 并发与性能兜底

Web 后端直接调用模型推理,有一个绕不开的问题:Python 的全局解释器锁会限制多线程并行,如果同时有多个用户上传图片,所有推理请求会排在一个队列里,表现为页面卡顿。我的处理方式是把模型推理放到一个独立的工作进程里,Web 主进程只负责接收请求和返回结果,推理进程通过消息队列消费任务。

具体的做法是:启动一个专用的推理进程,初始化一次模型,循环读取待处理图片路径,执行推理后把结果写入结果队列。Flask 这边收到用户请求后,把图片保存到临时目录,同时把任务 ID 和图片路径放入队列,再轮询等待结果。这样避免了模型重复加载和显存反复申请的问题,实测并发 5 个请求时,单张图推理耗时基本稳定在 100ms 以内(模型规格和硬件决定,这里是参考值)。

一个容易被忽略的性能坑:模型第一次推理会触发权重加载和 warmup,响应时间明显长于后续请求。要在服务启动时主动调用一次空推理,把模型预热,我管这个叫"热起来",否则第一个用户会以为系统卡死了。

5. 实测中的发现与踩坑经验

5.1 误检漏检的典型场景

系统搭完拿去试跑,最先暴露的是两个极端案例。一个是对反光纹理的误检,深色布料在强光下会产生一条高光带,YOLOv12 很容易把它当成褶皱。另一个是小污渍的漏检,如果污渍和布料底色接近,模型置信度一直压在阈值附近,时检出时不检出。

针对反光误检,一开始我的想法是增加反光样本做数据增强,但后来发现更有效的是在图像预处理阶段做一次直方图均衡化,压缩光照差异带来的干扰。这个改动让反光误检减少了大约一半。针对低对比度漏检,单纯调低 confidence 阈值会误检爆炸,更合理的做法是保持阈值不变,额外加一个"低阈值二次确认"流程:先用默认阈值检测,如果图片尺寸较大但目标数为零,再用低阈值跑一遍,把候选框交给用户人工复核。

5.2 关键后处理参数调节

confidence 阈值和 NMS 的 IOU 阈值是部署阶段需要反复调的两个参数。经验值仅供参考,还是要以现场数据为准。

参数作用我最终选择的值调整思路
confidence过滤低置信度预测框0.35现场漏检代价高,但不能太低导致满屏误检
NMS IOU合并重叠框0.5缺陷目标密集时适当调低到 0.4
最小框面积过滤过小的噪声框按图片尺寸动态计算破洞很小,但小于 20x20 像素的基本是噪声

最小框面积这个过滤条件容易被忽略。YOLOv12 的预测头偶尔会在背景纹理上输出一些面积很小的候选框,置信度不高不低,画在页面上非常干扰视线。加一个动态阈值:以原图面积的千分之一为底限,低于这个面积的预测框一律丢弃。实测下来对界面观感和统计准确率都有明显改善。

5.3 部署到现场前的几个工程化改造

如果要把这个系统真正推进到产线试用,还有几件事值得做。第一是模型导出加速,YOLOv12 支持导出 ONNX 格式,配合 GPU 或 TensorRT 推理,能在不损失太多精度的前提下把单张检测时间压缩到几十毫秒级别,这对高速产线是硬需求。第二是检测历史记录,每次检测的图片、缺陷框、置信度、操作员账号、时间都写入数据库,方便事后追溯漏检和误检的责任链条。第三是阈值可配置化,把 confidence、IOU 这些参数放到一个系统设置页面,而不是写死在代码里,现场人员调参不需要找你改代码重启服务。

我也尝试过把检测结果按缺陷类别统计成日报表,用来反馈给前道工序做工艺改进。这个功能短期看不出价值,但用久了会发现,大量同类缺陷的集中出现,往往意味着产线某个环节出了问题,比单纯靠人工记录直观得多。

最后分享一个我自己的体会:项目真正难的不是让模型 mAP 从 0.87 涨到 0.89,而是让现场的人愿意用、用得顺手。阈值可调、结果可见、出错时能告诉用户"为什么这样判断",这些体验层面的东西,比模型指标提升一两个点更能推动项目落地。如果你现在也在做类似的系统,我的建议是先端到端跑通最小闭环——哪怕是只有十几张图的 demo 也好,确认每个环节都通了,再回头慢慢补数据、调参数、打磨界面。这个过程走下来,你收获的远不止一个训练好的模型权重。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询