摘要

本文提出并实现了一种基于深度学习与Web技术的智能化、高精度布料缺陷检测系统。系统核心结合了前沿的目标检测算法YOLOv8、YOLOv10、YOLOv11及YOLOv12,构建了高性能的布料缺陷识别模型。后端采用SpringBoot框架搭建了稳定、可扩展的RESTful API服务,前端则通过现代化的Web技术构建了交互友好、功能丰富的用户界面,整体架构遵循前后端分离的设计原则,确保了系统的可维护性与高并发性能。此外,系统创新性地集成了DeepSeek大语言模型的智能分析能力,为检测结果提供语义化解释与生产决策建议。系统支持对布料生产过程中常见的六类缺陷(带纱、断纱、棉球、破洞、脱纱、污渍)进行精准识别与分类,并提供了图片、视频流及实时摄像头检测的完整解决方案。所有检测记录均持久化存储于MySQL数据库中。系统还配备了完善的用户权限管理体系与个人中心模块,保障了数据安全与操作合规性。实验表明,该系统在数据集(包含1650张训练图像与467张验证图像)上取得了优异的检测精度与实时性能,有效提升了纺织品生产质量控制的自动化与智能化水平。

详细功能展示视频

布料缺陷检测系统(YOLOv8/YOLOv10/YOLOv11/YOLOv12+深度学习+web界面+计算机视觉+DeepSeek智能分析)_哔哩哔哩_bilibili

布料缺陷检测系统(YOLOv8/YOLOv10/YOLOv11/YOLOv12+深度学习+web界面+计算机视觉+DeepSeek智能分析)_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1cB67BQEnd/?spm_id_from=333.999.0.0&vd_source=549d0b4e2b8999929a61a037fcce3b0f

https://www.bilibili.com/video/BV1cB67BQEnd/

目录

  摘要

详细功能展示视频

一、引言

1. 深度学习检测核心

2. 系统架构与Web交互

3. 核心功能模块

二、 系统核心特性概述

功能模块

登录注册模块

可视化模块

图像检测模块

视频检测模块

实时检测模块

图片识别记录管理

视频识别记录管理

摄像头识别记录管理

用户管理模块

数据管理模块(MySQL表设计)

模型训练结果

YOLO概述

YOLOv8

YOLOv10

YOLOv11

YOLOv12

前端代码展示

后端代码展示

 详细功能展示视频


一、引言

纺织制造业作为国民经济的重要支柱产业,其产品质量直接关系到企业的经济效益与市场竞争力。布料在生产过程中,因机械故障、原料问题或工艺参数不当,极易产生各类缺陷,如带纱、断纱、棉球、破洞、脱纱、污渍等。传统的人工质检方法效率低下、主观性强、易疲劳,且难以满足现代化高速生产线的实时检测需求。随着计算机视觉与深度学习技术的飞速发展,基于人工智能的自动缺陷检测(ADI)技术已成为工业质检领域的研究热点与变革方向。

YOLO(You Only Look Once)系列算法因其在速度与精度间的卓越平衡,成为实时目标检测任务的标杆。从YOLOv8到最新推出的YOLOv12,每一代都在网络结构、损失函数、训练策略等方面进行了持续优化,为复杂工业场景下的缺陷检测提供了更强大的基础模型。然而,单一的算法模型难以覆盖所有应用场景的需求,且缺乏与生产管理系统的有效集成。因此,构建一个能够灵活切换模型、具备友好人机交互、并能与业务系统深度融合的检测平台,具有重要的理论价值与现实意义。

与此同时,大语言模型(LLM)如DeepSeek在自然语言理解与生成方面的突破性进展,为工业AI应用开辟了新路径。将LLM的智能分析能力融入视觉检测系统,可以实现从“检测出缺陷”到“理解缺陷成因并给出建议”的跨越,极大地提升了系统的辅助决策价值。

基于上述背景,本研究设计并实现了一个基于多版本YOLO与SpringBoot的智能布料缺陷检测系统。本系统旨在解决以下关键问题:

  1. 模型灵活性与先进性:集成YOLOv8/v10/v11/v12多个版本,允许用户根据具体缺陷类型、精度与速度要求灵活选择最佳模型,并便于未来新模型的平滑接入。

  2. 系统集成与易用性:采用前后端分离的SpringBoot架构,提供统一的Web交互界面,将复杂的深度学习模型封装为易用的在线服务,降低使用门槛。

  3. 检测模式全覆盖:支持单张图片上传检测、视频文件逐帧分析、以及摄像头实时流媒体检测。

  4. 智能增强与决策支持:集成DeepSeek API,对检测出的缺陷进行智能分析,生成包含可能成因、影响及处理建议的文本报告,实现“检测+分析”一体化。

  5. 数据化管理与可视化:将所有的检测任务、识别结果、用户操作完整记录于数据库,并通过丰富的图表进行可视化展示,为生产质量追溯、缺陷统计分析及工艺优化提供数据支撑。

本文后续章节将详细阐述系统的整体架构设计、关键模块实现、数据集构建与模型训练过程、系统功能展示以及性能测试与分析。本研究不仅为布料缺陷检测提供了一个功能完备、技术先进的解决方案,也为其他工业视觉检测项目的系统化开发提供了可资借鉴的参考范式。

项目概述与核心功能详述

本项目是一个集成了最新计算机视觉算法、大型语言模型智能分析与现代Web开发技术的全栈式工业应用系统。其核心目标是打造一个高效、精准、易用且智能化的布料生产质量检测平台。

1. 深度学习检测核心
  • 模型库:系统内置了YOLOv8, YOLOv10, YOLOv11, YOLOv12四个版本的预训练与微调模型。用户可以在Web界面上一键切换,以适应不同场景下对检测速度(如实时监控)和检测精度(如高标准抽检)的差异化需求。

  • 缺陷类别:针对布料生产中的常见瑕疵,系统专门训练识别六类缺陷:DaiSha(带纱)、DuanSha(断纱)、MianQiu(棉球)、PoDong(破洞)、TuoSha(脱纱)、WuZi(污渍)。

  • 数据集:项目使用自构建的高质量布料缺陷数据集,包含总计2117张精细标注的图像。其中1650张用于模型训练,467张用于验证与调优,确保了模型在实际场景中的泛化能力与鲁棒性。

2. 系统架构与Web交互
  • 前后端分离架构:前端采用Vue.js框架构建响应式界面,提供流畅的用户体验;后端基于SpringBoot构建,提供高内聚、低耦合的API服务。

3. 核心功能模块
  • 用户体系

    • 登录注册:支持用户注册与登录,并与MySQL数据库交互。包含密码强度检测机制以提升安全性。

    • 个人中心:用户可在此修改个人信息,包括姓名、头像上传、密码修改等。

    • 用户管理(管理员):系统管理员拥有专属后台,可对所有注册用户进行增、删、改、查等管理操作,并分配不同角色与权限。

  • 多模态检测

    • 图像检测:用户上传布料图片,系统调用选定的YOLO模型进行缺陷识别,并以可视化的方式(边界框、类别标签、置信度)返回结果。

    • 视频检测:支持上传布料生产视频文件,系统自动逐帧分析。

    • 实时摄像头检测:满足生产线连续监控的需求。

  • 智能AI分析(DeepSeek集成)

    • 在完成视觉检测后,系统可将检测结果(缺陷类型、数量)发送至DeepSeek大语言模型API。

    • DeepSeek模型基于其深厚的知识库,生成一段针对性的分析文本,例如解释该类缺陷的常见成因、对布料品级的影响、以及推荐的质量控制或修复措施,极大地提升了系统的实用价值。

  • 数据管理

    • 检测记录管理:所有通过图片、视频、摄像头完成的检测任务,其原始数据、检测结果、分析报告、时间戳及操作用户信息均被结构化地保存至MySQL数据库。系统提供独立的模块供用户按时间、类型等条件查询、检索和删除这些历史记录。

二、 系统核心特性概述

功能模块


✅ 用户登录注册:支持密码检测,保存到MySQL数据库。

✅ 支持四种YOLO模型切换,YOLOv8、YOLOv10、YOLOv11、YOLOv12。

✅ 信息可视化,数据可视化。

✅ 图片检测支持AI分析功能,deepseek

✅ 支持图像检测、视频检测和摄像头实时检测,检测结果保存到MySQL数据库。

✅ 图片识别记录管理、视频识别记录管理和摄像头识别记录管理。

✅ 用户管理模块,管理员可以对用户进行增删改查。

✅ 个人中心,可以修改自己的信息,密码姓名头像等等。


 

登录注册模块

可视化模块

图像检测模块

  • YOLO模型集成 (v8/v10/v11/v12)

  • DeepSeek多模态分析

  • 支持格式:JPG/PNG/MP4/RTSP

视频检测模块

实时检测模块

图片识别记录管理

视频识别记录管理

摄像头识别记录管理

用户管理模块

数据管理模块(MySQL表设计)

  • users - 用户信息表

  • imgrecords- 图片检测记录表

  • videorecords- 视频检测记录表

  • camerarecords- 摄像头检测记录表

模型训练结果

#coding:utf-8
#根据实际情况更换模型
# yolon.yaml (nano):轻量化模型,适合嵌入式设备,速度快但精度略低。
# yolos.yaml (small):小模型,适合实时任务。
# yolom.yaml (medium):中等大小模型,兼顾速度和精度。
# yolob.yaml (base):基本版模型,适合大部分应用场景。
# yolol.yaml (large):大型模型,适合对精度要求高的任务。
 
from ultralytics import YOLO
 
model_path = 'pt/yolo12s.pt'
data_path = 'data.yaml'
 
if __name__ == '__main__':
    model = YOLO(model_path)
    results = model.train(data=data_path,
                          epochs=500,
                          batch=64,
                          device='0',
                          workers=0,
                          project='runs',
                          name='exp',
                          )
 
 
 
 
 
 
 
 

YOLO概述

YOLOv8

YOLOv8 由 Ultralytics 于 2023 年 1 月 10 日发布,在准确性和速度方面提供了尖端性能。基于先前 YOLO 版本的进步,YOLOv8 引入了新功能和优化,使其成为各种应用中目标检测任务的理想选择。

YOLOv8 的主要特性

  • 高级骨干和颈部架构: YOLOv8 采用最先进的骨干和颈部架构,从而改进了特征提取和目标检测性能。
  • 无锚点分离式 Ultralytics Head: YOLOv8 采用无锚点分离式 Ultralytics head,与基于锚点的方法相比,这有助于提高准确性并提高检测效率。
  • 优化的准确性-速度权衡: YOLOv8 专注于在准确性和速度之间保持最佳平衡,适用于各种应用领域中的实时对象检测任务。
  • 丰富的预训练模型: YOLOv8提供了一系列预训练模型,以满足各种任务和性能要求,使您更容易为特定用例找到合适的模型。

YOLOv10

YOLOv10 由 清华大学研究人员基于 Ultralytics Python构建,引入了一种新的实时目标检测方法,解决了先前 YOLO 版本中存在的后处理和模型架构缺陷。通过消除非极大值抑制 (NMS) 并优化各种模型组件,YOLOv10 以显著降低的计算开销实现了最先进的性能。大量实验表明,它在多个模型尺度上都具有卓越的精度-延迟权衡。

概述

实时目标检测旨在以低延迟准确预测图像中的对象类别和位置。YOLO 系列因其在性能和效率之间的平衡而一直处于这项研究的前沿。然而,对 NMS 的依赖和架构效率低下阻碍了最佳性能。YOLOv10 通过引入用于无 NMS 训练的一致双重分配和整体效率-准确性驱动的模型设计策略来解决这些问题。

架构

YOLOv10 的架构建立在之前 YOLO 模型优势的基础上,同时引入了几项关键创新。该模型架构由以下组件组成:

  1.  骨干网络:负责特征提取,YOLOv10 中的骨干网络使用增强版的 CSPNet (Cross Stage Partial Network),以改善梯度流并减少计算冗余。
  2. Neck:Neck 的设计目的是聚合来自不同尺度的特征,并将它们传递到 Head。它包括 PAN(路径聚合网络)层,用于有效的多尺度特征融合。
  3. One-to-Many Head:在训练期间为每个对象生成多个预测,以提供丰富的监督信号并提高学习准确性。
  4. 一对一头部:在推理时为每个对象生成一个最佳预测,以消除对NMS的需求,从而降低延迟并提高效率。

主要功能

  1. 免NMS训练:利用一致的双重分配来消除对NMS的需求,从而降低推理延迟。
  2. 整体模型设计:从效率和准确性的角度对各种组件进行全面优化,包括轻量级分类 Head、空间通道解耦下采样和秩引导块设计。
  3. 增强的模型功能: 结合了大内核卷积和部分自注意力模块,以提高性能,而无需显着的计算成本。

YOLOv11

YOLO11 是 Ultralytics YOLO 系列实时目标检测器的最新迭代版本,它以前沿的精度、速度和效率重新定义了可能性。YOLO11 在之前 YOLO 版本的显著进步基础上,在架构和训练方法上进行了重大改进,使其成为各种计算机视觉任务的多功能选择。

主要功能

  • 增强的特征提取: YOLO11 采用改进的 backbone 和 neck 架构,从而增强了特征提取能力,以实现更精确的目标检测和复杂的任务性能。
  • 优化效率和速度: YOLO11 引入了改进的架构设计和优化的训练流程,从而提供更快的处理速度,并在精度和性能之间保持最佳平衡。
  • 更高精度,更少参数: 随着模型设计的进步,YOLO11m 在 COCO 数据集上实现了更高的 平均精度均值(mAP),同时比 YOLOv8m 少用 22% 的参数,在不牺牲精度的情况下提高了计算效率。
  • 跨环境的适应性: YOLO11 可以无缝部署在各种环境中,包括边缘设备、云平台和支持 NVIDIA GPU 的系统,从而确保最大的灵活性。
  • 广泛支持的任务范围: 无论是目标检测、实例分割、图像分类、姿势估计还是旋转框检测 (OBB),YOLO11 都旨在满足各种计算机视觉挑战。

Ultralytics YOLO11 在其前代产品的基础上进行了多项重大改进。主要改进包括:

  • 增强的特征提取: YOLO11 采用了改进的骨干网络和颈部架构,增强了特征提取能力,从而实现更精确的目标检测。
  • 优化的效率和速度: 改进的架构设计和优化的训练流程提供了更快的处理速度,同时保持了准确性和性能之间的平衡。
  • 更高精度,更少参数: YOLO11m 在 COCO 数据集上实现了更高的平均 精度均值 (mAP),同时比 YOLOv8m 少用 22% 的参数,在不牺牲精度的情况下提高了计算效率。
  • 跨环境的适应性: YOLO11 可以部署在各种环境中,包括边缘设备、云平台和支持 NVIDIA GPU 的系统。
  • 广泛支持的任务范围: YOLO11 支持各种计算机视觉任务,例如目标检测、实例分割、图像分类、姿势估计和旋转框检测 (OBB)。

YOLOv12

YOLO12引入了一种以注意力为中心的架构,它不同于之前YOLO模型中使用的传统基于CNN的方法,但仍保持了许多应用所需的实时推理速度。该模型通过在注意力机制和整体网络架构方面的新颖方法创新,实现了最先进的目标检测精度,同时保持了实时性能。尽管有这些优势,YOLO12仍然是一个社区驱动的版本,由于其沉重的注意力模块,可能表现出训练不稳定、内存消耗增加和CPU吞吐量较慢的问题,因此Ultralytics仍然建议将YOLO11用于大多数生产工作负载。

主要功能

  • 区域注意力机制: 一种新的自注意力方法,可以有效地处理大型感受野。它将 特征图 分成 l 个大小相等的区域(默认为 4 个),水平或垂直,避免复杂的运算并保持较大的有效感受野。与标准自注意力相比,这大大降低了计算成本。
  • 残差高效层聚合网络(R-ELAN):一种基于 ELAN 的改进的特征聚合模块,旨在解决优化挑战,尤其是在更大规模的以注意力为中心的模型中。R-ELAN 引入:
    • 具有缩放的块级残差连接(类似于层缩放)。
    • 一种重新设计的特征聚合方法,创建了一个类似瓶颈的结构。
  • 优化的注意力机制架构:YOLO12 精简了标准注意力机制,以提高效率并与 YOLO 框架兼容。这包括:
    • 使用 FlashAttention 来最大限度地减少内存访问开销。
    • 移除位置编码,以获得更简洁、更快速的模型。
    • 调整 MLP 比率(从典型的 4 调整到 1.2 或 2),以更好地平衡注意力和前馈层之间的计算。
    • 减少堆叠块的深度以改进优化。
    • 利用卷积运算(在适当的情况下)以提高其计算效率。
    • 在注意力机制中添加一个7x7可分离卷积(“位置感知器”),以隐式地编码位置信息。
  • 全面的任务支持: YOLO12 支持一系列核心计算机视觉任务:目标检测、实例分割、图像分类、姿势估计和旋转框检测 (OBB)。
  • 增强的效率: 与许多先前的模型相比,以更少的参数实现了更高的准确率,从而证明了速度和准确率之间更好的平衡。
  • 灵活部署: 专为跨各种平台部署而设计,从边缘设备到云基础设施。

主要改进

  1. 增强的 特征提取:

    • 区域注意力: 有效处理大型感受野,降低计算成本。
    • 优化平衡: 改进了注意力和前馈网络计算之间的平衡。
    • R-ELAN:使用 R-ELAN 架构增强特征聚合。
  2. 优化创新:

    • 残差连接:引入具有缩放的残差连接以稳定训练,尤其是在较大的模型中。
    • 改进的特征集成:在 R-ELAN 中实现了一种改进的特征集成方法。
    • FlashAttention: 整合 FlashAttention 以减少内存访问开销。
  3. 架构效率:

    • 减少参数:与之前的许多模型相比,在保持或提高准确性的同时,实现了更低的参数计数。
    • 简化的注意力机制:使用简化的注意力实现,避免了位置编码。
    • 优化的 MLP 比率:调整 MLP 比率以更有效地分配计算资源。

前端代码展示

首页界面一小部分代码:

<template>
	<div class="home-container layout-pd">
		<el-row :gutter="15" class="home-card-two mb15">
			<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
				<div class="home-card-item">
					<div style="height: 100%" ref="homeLineRef"></div>
				</div>
			</el-col>
			<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="home-media">
				<div class="home-card-item">
					<div style="height: 100%" ref="homePieRef"></div>
				</div>
			</el-col>
		</el-row>
		<el-row :gutter="15" class="home-card-three">
			<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="home-media">
				<div class="home-card-item">
					<div style="height: 100%" ref="homeradarRef"></div>
				</div>
			</el-col>
			<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
				<div class="home-card-item">
					<div class="home-card-item-title">实时缺陷检测记录</div>
					<div class="home-monitor">
						<div class="flex-warp">
							<el-table :data="state.paginatedData" style="width: 100%" height="360" v-loading="state.loading">
								<el-table-column prop="username" label="操作用户" align="center" width="120" />
								<el-table-column prop="label" label="缺陷类型" align="center" width="120">
									<template #default="scope">
										<el-tag 
											:type="getDefectType(scope.row.label)"
											effect="light"
										>
											{{ formatLabel(scope.row.label) }}
										</el-tag>
									</template>
								</el-table-column>
								<el-table-column prop="confidence" label="置信度" align="center" width="120">
									<template #default="scope">
										{{ formatConfidence(scope.row.confidence) }}
									</template>
								</el-table-column>
								<el-table-column prop="weight" label="模型权重" align="center" width="120" />
								<el-table-column prop="conf" label="检测阈值" align="center" width="120" />
								<el-table-column prop="startTime" label="检测时间" align="center" width="180" />
								<el-table-column label="操作" align="center" width="100">
									<template #default="scope">
										<el-button link type="primary" size="small" @click="handleViewDetail(scope.row)">
											详情
										</el-button>
									</template>
								</el-table-column>
							</el-table>
							<div class="pagination-container">
								<el-pagination
									v-model:current-page="state.currentPage"
									v-model:page-size="state.pageSize"
									:page-sizes="[10, 20, 50, 100]"
									:small="true"
									:layout="layout"
									:total="state.total"
									@size-change="handleSizeChange"
									@current-change="handleCurrentChange"
								/>
							</div>
						</div>
					</div>
				</div>
			</el-col>
		</el-row>

		<!-- 详情弹窗 -->
		<el-dialog
			v-model="state.detailDialogVisible"
			:title="`缺陷检测记录详情 - ${state.selectedRecord?.username || ''}`"
			width="80%"
			:close-on-click-modal="false"
			:close-on-press-escape="false"
			center
		>
			<div class="detail-container" v-loading="state.detailLoading">
				<el-row :gutter="20">
					<!-- 布匹图片 -->
					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
						<div class="detail-section">
							<h3 class="detail-title">原始图片</h3>
							<div class="image-container">
								<div class="img-wrapper" @click="previewImage(getImageUrl(state.selectedRecord?.inputImg), '原始图片')">
									<img 
										:src="getImageUrl(state.selectedRecord?.inputImg)" 
										alt="原始图片" 
										class="detection-image"
										v-if="state.selectedRecord?.inputImg"
									/>
									<div class="img-overlay" v-if="state.selectedRecord?.inputImg">
										<el-icon><View /></el-icon>
									</div>
									<div v-else class="image-placeholder">
										<el-icon><Picture /></el-icon>
										<span>暂无原始图片</span>
									</div>
								</div>
							</div>
						</div>
					</el-col>
					
					<!-- 检测信息 -->
					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
						<div class="detail-section">
							<h3 class="detail-title">检测信息</h3>
							<el-descriptions :column="1" border>
								<el-descriptions-item label="操作用户">
									{{ state.selectedRecord?.username || '未知' }}
								</el-descriptions-item>
								
								<el-descriptions-item label="缺陷类型">
									<el-tag 
										:type="getDefectType(state.selectedRecord?.label || '')"
										effect="light"
									>
										{{ formatLabel(state.selectedRecord?.label || '') }}
									</el-tag>
								</el-descriptions-item>
								
								<el-descriptions-item label="置信度">
									{{ formatConfidence(state.selectedRecord?.confidence || '') }}
								</el-descriptions-item>
								
								<el-descriptions-item label="模型权重">
									{{ state.selectedRecord?.weight || '未知' }}
								</el-descriptions-item>
								
								<el-descriptions-item label="检测阈值">
									{{ state.selectedRecord?.conf || '未知' }}
								</el-descriptions-item>
								
								<el-descriptions-item label="检测时间">
									{{ state.selectedRecord?.startTime || '未知' }}
								</el-descriptions-item>
								
								<el-descriptions-item label="缺陷分析详情" v-if="hasDetectionDetails">
									<div class="detection-details">
										<div 
											v-for="(item, index) in getDetectionDetails()" 
											:key="index"
											class="detail-item"
										>
											<span class="detail-label">{{ item.label }}:</span>
											<span class="detail-value">{{ item.confidence }}</span>
										</div>
									</div>
								</el-descriptions-item>
							</el-descriptions>
						</div>
					</el-col>
				</el-row>
				
				<!-- 原图与检测结果对比 -->
				<el-row :gutter="20" v-if="state.selectedRecord?.inputImg || state.selectedRecord?.outImg">
					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
						<div class="detail-section">
							<h3 class="detail-title">原始图片</h3>
							<div class="image-container">
								<div class="img-wrapper" @click="previewImage(getImageUrl(state.selectedRecord.inputImg), '原始图片')">
									<img 
										:src="getImageUrl(state.selectedRecord.inputImg)" 
										alt="原始图片" 
										class="detection-image"
										v-if="state.selectedRecord?.inputImg"
									/>
									<div class="img-overlay" v-if="state.selectedRecord?.inputImg">
										<el-icon><View /></el-icon>
									</div>
									<div v-else class="image-placeholder">
										<el-icon><Picture /></el-icon>
										<span>暂无原始图片</span>
									</div>
								</div>
							</div>
						</div>
					</el-col>
					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
						<div class="detail-section">
							<h3 class="detail-title">标注图片</h3>
							<div class="image-container">
								<div class="img-wrapper" @click="previewImage(getImageUrl(state.selectedRecord.outImg), '标注图片')">
									<img 
										:src="getImageUrl(state.selectedRecord.outImg)" 
										alt="标注图片" 
										class="detection-image"
										v-if="state.selectedRecord?.outImg"
									/>
									<div class="img-overlay" v-if="state.selectedRecord?.outImg">
										<el-icon><View /></el-icon>
									</div>
									<div v-else class="image-placeholder">
										<el-icon><Picture /></el-icon>
										<span>暂无标注图片</span>
									</div>
								</div>
							</div>
						</div>
					</el-col>
				</el-row>
			</div>
			
			<template #footer>
				<span class="dialog-footer">
					<el-button @click="state.detailDialogVisible = false">关闭</el-button>
					<el-button type="primary" @click="handleDownloadImage" :disabled="!state.selectedRecord?.inputImg">
						<el-icon><Download /></el-icon>
						下载检测图片
					</el-button>
				</span>
			</template>
		</el-dialog>

		<!-- 图片预览弹窗 -->
		<el-dialog 
			v-model="state.previewDialog.visible" 
			:title="state.previewDialog.title" 
			width="60%"
			align-center
			class="image-preview-dialog">
			<div class="preview-content">
				<img :src="state.previewDialog.imageUrl" :alt="state.previewDialog.title" class="preview-image" />
			</div>
		</el-dialog>
	</div>
</template>

<script setup lang="ts" name="home">
import { reactive, onMounted, ref, watch, nextTick, onActivated, markRaw, computed } from 'vue';
import * as echarts from 'echarts';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Picture, Download, View } from '@element-plus/icons-vue';
import request from '/@/utils/request';

// 定义变量内容
const homeLineRef = ref();
const homePieRef = ref();
const homeradarRef = ref();
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);

// 缺陷类型定义
const DEFECT_TYPES = ['带纱', '断纱', '棉球', '破洞', '脱纱', '污渍'];

// 缺陷颜色映射 - 使用更符合工业检测的颜色
const DEFECT_COLORS: Record<string, string> = {
  '带纱': '#1890ff',     // 蓝色
  '断纱': '#52c41a',     // 绿色
  '棉球': '#fa8c16',     // 橙色
  '破洞': '#ff4d4f',     // 红色
  '脱纱': '#722ed1',     // 紫色
  '污渍': '#13c2c2',     // 青色
};

const state = reactive({
	data: [] as any,
	paginatedData: [] as any,
	loading: false,
	currentPage: 1,
	pageSize: 10,
	total: 0,
	global: {
		homeChartOne: null,
		homeChartTwo: null,
		homeCharFour: null,
		dispose: [null, '', undefined],
	} as any,
	myCharts: [] as any[],
	charts: {
		theme: '',
		bgColor: '',
		color: '#303133',
	},
	// 详情弹窗相关
	detailDialogVisible: false,
	detailLoading: false,
	selectedRecord: null as any,
	// 图片预览弹窗
	previewDialog: {
		visible: false,
		title: '',
		imageUrl: '',
	},
});

// 响应式分页数据
const layout = computed(() => {
	return window.innerWidth < 768 ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper';
});

// 获取图片URL
const getImageUrl = (imagePath: string) => {
	if (!imagePath) return '';
	if (imagePath.startsWith('http')) return imagePath;
	return `/api${imagePath.startsWith('/') ? '' : '/'}${imagePath}`;
};

// 是否有检测详情
const hasDetectionDetails = computed(() => {
	if (!state.selectedRecord) return false;
	try {
		const labels = JSON.parse(state.selectedRecord.label || '[]');
		const confidences = JSON.parse(state.selectedRecord.confidence || '[]');
		return labels.length > 0 && confidences.length > 0;
	} catch {
		return false;
	}
});

// 获取检测详情
const getDetectionDetails = () => {
	if (!state.selectedRecord) return [];
	try {
		const labels = JSON.parse(state.selectedRecord.label || '[]');
		const confidences = JSON.parse(state.selectedRecord.confidence || '[]');
		
		return labels
			.map((label: string, index: number) => {
				// 如果是缺陷索引,转换为缺陷名称
				const defectIndex = parseInt(label);
				const defectLabel = defectIndex >= 0 && defectIndex < DEFECT_TYPES.length 
					? DEFECT_TYPES[defectIndex] 
					: `${label}`;
				
				return {
					label: defectLabel,
					confidence: confidences[index] ? `${(parseFloat(confidences[index]) * 100).toFixed(1)}%` : '0%',
					color: DEFECT_COLORS[defectLabel] || '#1890ff'
				};
			})
			.filter((item: any, index: number) => labels[index] !== undefined);
	} catch {
		return [];
	}
};

// 图片预览
const previewImage = (imageUrl: string, title: string) => {
	if (!imageUrl) {
		ElMessage.warning('没有可预览的图片');
		return;
	}
	state.previewDialog.imageUrl = imageUrl;
	state.previewDialog.title = title;
	state.previewDialog.visible = true;
};

// 分页处理
const handleSizeChange = (val: number) => {
	state.pageSize = val;
	state.currentPage = 1;
	updatePaginatedData();
};

const handleCurrentChange = (val: number) => {
	state.currentPage = val;
	updatePaginatedData();
};

const updatePaginatedData = () => {
	const start = (state.currentPage - 1) * state.pageSize;
	const end = start + state.pageSize;
	state.paginatedData = state.data.slice(start, end);
};

// 格式化标签显示 - 缺陷检测逻辑
const formatLabel = (label: string) => {
	try {
		const labels = JSON.parse(label);
		if (labels.length > 0) {
			const firstLabel = labels[0];
			const defectIndex = parseInt(firstLabel);
			if (defectIndex >= 0 && defectIndex < DEFECT_TYPES.length) {
				return DEFECT_TYPES[defectIndex];
			}
			return `${firstLabel}`;
		}
		return '无缺陷';
	} catch {
		if (label && label.length > 0 && label !== '[]' && label !== '""') {
			// 尝试直接转换
			const defectIndex = parseInt(label);
			if (!isNaN(defectIndex) && defectIndex >= 0 && defectIndex < DEFECT_TYPES.length) {
				return DEFECT_TYPES[defectIndex];
			}
			return '检测到缺陷';
		}
		return '无缺陷';
	}
};

// 根据检测结果设置标签类型 - 缺陷检测逻辑
const getDefectType = (label: string) => {
	try {
		const labels = JSON.parse(label);
		if (labels.length > 0) {
			const defectIndex = parseInt(labels[0]);
			if (defectIndex >= 0 && defectIndex < DEFECT_TYPES.length) {
				const defect = DEFECT_TYPES[defectIndex];
				// 根据缺陷严重程度设置不同的tag样式
				switch(defect) {
					case '破洞': return 'danger';      // 严重缺陷
					case '断纱': return 'warning';     // 中等缺陷
					case '脱纱': return 'warning';     // 中等缺陷
					case '带纱': return 'info';       // 轻微缺陷
					case '棉球': return 'info';       // 轻微缺陷
					case '污渍': return 'info';       // 轻微缺陷
					default: return 'primary';
				}
			}
			return 'primary';
		}
		return 'success'; // 无缺陷
	} catch {
		if (label && label.length > 0 && label !== '[]' && label !== '""') {
			return 'primary';
		}
		return 'success'; // 无缺陷
	}
};

// 格式化置信度显示
const formatConfidence = (confidence: string) => {
	try {
		const confidences = JSON.parse(confidence);
		if (confidences.length === 0) return '0%';
		
		const maxConfidence = Math.max(...confidences.map((conf: any) => {
			if (typeof conf === 'number') return conf * 100;
			if (typeof conf === 'string') {
				const num = parseFloat(conf.replace('%', ''));
				return isNaN(num) ? 0 : num;
			}
			return 0;
		}));
		return `${maxConfidence.toFixed(1)}%`;
	} catch {
		if (typeof confidence === 'number') {
			return `${(confidence * 100).toFixed(1)}%`;
		}
		return confidence || '0%';
	}
};

// 查看详情
const handleViewDetail = async (row: any) => {
	state.selectedRecord = row;
	state.detailDialogVisible = true;
	state.detailLoading = true;
	
	try {
		const res = await request.get(`/api/imgRecords/${row.id}`);
		if (res.code == 0) {
			const record = res.data;
			state.selectedRecord = {
				...record,
				inputImg: record.inputImg || record.imagePath,
				outImg: record.outImg || record.resultImagePath
			};
		}
	} catch (error) {
		console.error('获取详情失败:', error);
		state.selectedRecord = row;
	} finally {
		state.detailLoading = false;
	}
};

// 下载图片
const handleDownloadImage = async () => {
	if (!state.selectedRecord?.inputImg) {
		ElMessage.warning('没有可下载的图片');
		return;
	}
	
	try {
		const imageUrl = getImageUrl(state.selectedRecord.inputImg);
		const response = await fetch(imageUrl);
		const blob = await response.blob();
		
		const url = window.URL.createObjectURL(blob);
		const a = document.createElement('a');
		a.href = url;
		
		const filename = state.selectedRecord.inputImg.split('/').pop() || 
			`defect_detection_${state.selectedRecord.username}_${state.selectedRecord.startTime?.replace(/[: ]/g, '-') || 'unknown'}.jpg`;
		
		a.download = filename;
		document.body.appendChild(a);
		a.click();
		window.URL.revokeObjectURL(url);
		document.body.removeChild(a);
		
		ElMessage.success('图片下载成功');
	} catch (error) {
		console.error('下载图片失败:', error);
		ElMessage.error('图片下载失败');
	}
};

// 折线图 - 近十日检测数量
const initLineChart = () => {
	if (!state.global.dispose.some((b: any) => b === state.global.homeChartOne)) state.global.homeChartOne?.dispose();
	state.global.homeChartOne = markRaw(echarts.init(homeLineRef.value, state.charts.theme));
	
	// 统计每天的检测数量
	const counts: Record<string, number> = {};
	state.data.forEach((detection: any) => {
		if (detection.startTime) {
			const date = detection.startTime.split(' ')[0];
			counts[date] = (counts[date] || 0) + 1;
		}
	});

	const sortedDatesDesc = Object.keys(counts).sort((a, b) => b.localeCompare(a));
	const latestDatesDesc = sortedDatesDesc.slice(0, 10);
	const latestDates = latestDatesDesc.sort((a, b) => a.localeCompare(b));

	const result = {
		dateData: latestDates,
		valueData: latestDates.map(date => counts[date])
	};

	const option = {
		backgroundColor: state.charts.bgColor,
		title: {
			text: '近十日缺陷检测数量趋势',
			x: 'left',
			textStyle: { fontSize: 15, color: state.charts.color },
		},
		grid: { top: 70, right: 20, bottom: 30, left: 30 },
		tooltip: { 
			trigger: 'axis',
			formatter: (params: any) => {
				const data = params[0];
				return `${data.name}<br/>缺陷检测数量: ${data.value}`;
			}
		},
		xAxis: {
			data: result.dateData,
			axisLabel: {
				color: state.charts.color,
				rotate: 45
			},
		},
		yAxis: [
			{
				type: 'value',
				name: '检测数量',
				splitLine: { show: true, lineStyle: { type: 'dashed', color: state.charts.theme === 'dark' ? '#444' : '#f5f5f5' } },
				axisLabel: {
					color: state.charts.color,
				},
			},
		],
		series: [
			{
				name: '缺陷检测数量',
				type: 'line',
				symbolSize: 6,
				symbol: 'circle',
				smooth: true,
				data: result.valueData,
				lineStyle: { color: '#1890ff' }, // 使用蓝色表示缺陷检测
				itemStyle: { color: '#1890ff', borderColor: '#1890ff' },
				areaStyle: {
					color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
						{ offset: 0, color: '#1890ffb3' },
						{ offset: 1, color: '#1890ff03' },
					]),
				},
			},
		],
	};

	state.global.homeChartOne.setOption(option);
	state.myCharts.push(state.global.homeChartOne);
};

// 饼图 - 缺陷类型分布
const initPieChart = () => {
	if (!state.global.dispose.some((b: any) => b === state.global.homeChartTwo)) state.global.homeChartTwo?.dispose();
	state.global.homeChartTwo = markRaw(echarts.init(homePieRef.value, state.charts.theme));
	
	const defectCounts: Record<string, number> = {};
	
	state.data.forEach((detection: any) => {
		try {
			const labels = JSON.parse(detection.label || '[]');
			if (labels.length > 0) {
				const defectIndex = parseInt(labels[0]);
				if (defectIndex >= 0 && defectIndex < DEFECT_TYPES.length) {
					const defect = DEFECT_TYPES[defectIndex];
					defectCounts[defect] = (defectCounts[defect] || 0) + 1;
				} else if (labels[0] in DEFECT_COLORS) {
					// 如果已经是缺陷名称
					defectCounts[labels[0]] = (defectCounts[labels[0]] || 0) + 1;
				}
			}
		} catch {
			// 如果解析失败,尝试直接使用
			if (detection.label in DEFECT_COLORS) {
				defectCounts[detection.label] = (defectCounts[detection.label] || 0) + 1;
			}
		}
	});

	// 确保所有缺陷类型都有数据
	DEFECT_TYPES.forEach(defect => {
		if (!defectCounts[defect]) {
			defectCounts[defect] = 0;
		}
	});

	const pieData = DEFECT_TYPES.map(defect => ({
		name: defect,
		value: defectCounts[defect],
		itemStyle: {
			color: DEFECT_COLORS[defect]
		}
	})).filter(item => item.value > 0);

	const option = {
		backgroundColor: state.charts.bgColor,
		title: {
			text: '缺陷类型分布统计',
			x: 'left',
			textStyle: { fontSize: '15', color: state.charts.color },
		},
		legend: {
			top: 'bottom',
			textStyle: {
				color: state.charts.color
			}
		},
		tooltip: {
			trigger: 'item',
			formatter: '{a} <br/>{b}: {c} ({d}%)'
		},
		series: [
			{
				type: 'pie',
				radius: ['40%', '70%'],
				center: ['50%', '50%'],
				avoidLabelOverlap: true,
				itemStyle: {
					borderRadius: 10,
					borderColor: state.charts.bgColor,
					borderWidth: 2
				},
				label: {
					show: true,
					formatter: '{b}: {c}次',
					color: state.charts.color
				},
				emphasis: {
					label: {
						show: true,
						fontSize: '14',
						fontWeight: 'bold'
					}
				},
				data: pieData
			}
		]
	};

	state.global.homeChartTwo.setOption(option);
	state.myCharts.push(state.global.homeChartTwo);
};

// 雷达图 - 用户置信度分析
const initradarChart = () => {
	if (!state.global.dispose.some((b: any) => b === state.global.homeCharFour)) state.global.homeCharFour?.dispose();
	state.global.homeCharFour = markRaw(echarts.init(homeradarRef.value, state.charts.theme));
	
	const userConfidenceStats: Record<string, { total: number, count: number }> = {};
	
	state.data.forEach((detection: any) => {
		const username = detection.username || '未知用户';
		let avgConfidence = 0;
		
		try {
			const confidences = JSON.parse(detection.confidence || '[]');
			if (confidences.length > 0) {
				const validConfidences = confidences.filter((conf: any) => {
					if (typeof conf === 'number') return true;
					if (typeof conf === 'string') {
						const num = parseFloat(conf.replace('%', ''));
						return !isNaN(num);
					}
					return false;
				});
				
				if (validConfidences.length > 0) {
					const numericConfidences = validConfidences.map((conf: any) => {
						if (typeof conf === 'number') return conf;
						if (typeof conf === 'string') {
							const num = parseFloat(conf.replace('%', '')) / 100;
							return isNaN(num) ? 0 : num;
						}
						return 0;
					});
					
					avgConfidence = numericConfidences.reduce((sum: number, conf: number) => sum + conf, 0) / numericConfidences.length;
				}
			}
		} catch {
			if (typeof detection.confidence === 'number') {
				avgConfidence = detection.confidence;
			} else if (typeof detection.confidence === 'string') {
				const num = parseFloat(detection.confidence.replace('%', '')) / 100;
				avgConfidence = isNaN(num) ? 0 : num;
			}
		}
		
		if (!userConfidenceStats[username]) {
			userConfidenceStats[username] = { total: avgConfidence, count: 1 };
		} else {
			userConfidenceStats[username].total += avgConfidence;
			userConfidenceStats[username].count += 1;
		}
	});

	const userAvgConfidences = Object.keys(userConfidenceStats).map(username => ({
		username,
		avgConf: userConfidenceStats[username].total / userConfidenceStats[username].count,
		count: userConfidenceStats[username].count
	}));

	const topUsers = userAvgConfidences
		.filter(user => user.count >= 3)
		.sort((a, b) => b.avgConf - a.avgConf)
		.slice(0, 7);

	if (topUsers.length === 0) {
		const option = {
			backgroundColor: state.charts.bgColor,
			title: {
				text: '用户检测置信度分析',
				x: 'left',
				textStyle: { fontSize: '15', color: state.charts.color },
			},
			graphic: {
				type: 'text',
				left: 'center',
				top: 'center',
				style: {
					text: '数据不足,无法生成置信度分析',
					fontSize: 14,
					fill: state.charts.color
				}
			}
		};
		state.global.homeCharFour.setOption(option);
		state.myCharts.push(state.global.homeCharFour);
		return;
	}

	const data = topUsers.map(user => Number((user.avgConf * 100).toFixed(2)));
	const indicatorNames = topUsers.map(user => user.username);

	const indicator = indicatorNames.map((name) => ({ 
		name, 
		max: 100 
	}));

	const option = {
		backgroundColor: state.charts.bgColor,
		title: {
			text: '用户检测置信度分析',
			x: 'left',
			textStyle: { fontSize: '15', color: state.charts.color },
		},
		tooltip: {
			formatter: (params: any) => {
				const userIndex = indicatorNames.findIndex(name => name === params.name);
				const user = topUsers[userIndex];
				return `${params.name}<br/>平均置信度: ${params.value}%<br/>检测次数: ${user?.count || 0}次`;
			}
		},
		radar: {
			radius: '65%',
			splitNumber: 4,
			indicator: indicator,
			axisName: {
				color: state.charts.color,
				fontSize: 12
			},
			splitArea: {
				areaStyle: {
					color: ['rgba(24,144,255,0.1)', 'rgba(24,144,255,0.05)'],
				}
			},
			splitLine: {
				lineStyle: {
					color: 'rgba(24,144,255,0.3)'
				}
			},
			axisLine: {
				lineStyle: {
					color: 'rgba(24,144,255,0.5)'
				}
			}
		},
		series: [{
			type: 'radar',
			data: [{
				value: data,
				name: '平均置信度',
				areaStyle: {
					color: 'rgba(24,144,255,0.3)'
				},
				lineStyle: {
					color: '#1890ff'
				},
				itemStyle: {
					color: '#1890ff'
				},
				label: {
					show: true,
					formatter: (params: any) => {
						return params.value + '%';
					}
				}
			}]
		}]
	};

	state.global.homeCharFour.setOption(option);
	state.myCharts.push(state.global.homeCharFour);
};

后端代码展示

 详细功能展示视频

布料缺陷检测系统(YOLOv8/YOLOv10/YOLOv11/YOLOv12+深度学习+web界面+计算机视觉+DeepSeek智能分析)_哔哩哔哩_bilibili

布料缺陷检测系统(YOLOv8/YOLOv10/YOLOv11/YOLOv12+深度学习+web界面+计算机视觉+DeepSeek智能分析)_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1cB67BQEnd/?spm_id_from=333.999.0.0&vd_source=549d0b4e2b8999929a61a037fcce3b0f

https://www.bilibili.com/video/BV1cB67BQEnd/

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐