项目说明本研究针对传统轮胎缺陷检测方法效率低下、准确性不足的问题提出了一种基于深度学习的轮胎缺陷检测系统。研究首先构建了包含5000张轮胎图像的大规模数据集涵盖正常轮胎和五种常见缺陷类型划痕、鼓包、裂纹、异物嵌入和磨损并通过数据增强技术扩充样本多样性。在算法方面本研究对传统YOLOv5模型进行了系统性改进引入了深度可分离卷积替代标准卷积显著降低了计算复杂度采用多尺度特征融合机制增强对小尺寸缺陷的感知能力并通过通道注意力机制提高网络对缺陷特征的敏感性。同时将全连接网络替换为带有残差连接的全局平均池化结构并引入多任务学习机制同时优化缺陷分类、定位和严重程度估计任务。实验结果表明改进后的模型在测试集上达到92.3%的mAP0.5比现有先进模型YOLOv5高出3.2个百分点同时保持42 FPS的实时性能。特别是在对裂纹和鼓包这类形状不规则、特征不明显的缺陷检测上改进模型的mAP分别达到91.5%和90.8%提升显著。本研究还开发了完整的轮胎缺陷检测系统实现了从图像采集、预处理到缺陷检测和结果展示的全流程自动化已在多家轮胎制造企业进行试点应用显著提高了缺陷检测的准确率和效率降低了人工成本。研究成果为轮胎质量检测提供了高效、准确的智能化解决方案对推动轮胎制造业向智能化、自动化发展具有重要意义图片效果数据集信息本研究使用轮胎缺陷检测专用数据集该数据集包含5000张轮胎图像涵盖正常轮胎和五种常见缺陷类型划痕、鼓包、裂纹、异物嵌入和磨损。各类别样本分布相对均衡其中正常轮胎1500张缺陷轮胎3500张各缺陷类型约700张。图像分辨率为1920×1080像素采用JPEG格式存储文件大小平均为2-5MB。数据预处理流程首先进行数据清洗剔除模糊、过曝或过暗的图像以及包含无关物体的图像。通过计算图像清晰度评分(基于拉普拉斯算子)和直方图分布筛选出质量较高的图像作为实验数据。清洗后数据集保留4600张图像其中正常轮胎1400张缺陷轮胎3200张。数据集划分采用分层抽样法确保各类别比例在训练集、验证集和测试集中保持一致。具体划分比例为70%用于训练15%用于验证15%用于测试。最终得到训练集3220张图像验证集690张图像测试集690张图像。图像预处理包括尺寸归一化、标准化和增强处理。首先将所有图像统一调整为512×512像素保持长宽比不变采用填充方式处理。然后进行像素值标准化将像素值从[0,255]范围缩放到[0,1]范围并应用均值(0.485,0.456,0.406)和标准差(0.229,0.224,0.225)的标准化处理以匹配预训练模型的输入要求针对缺陷检测任务的特殊性本研究还进行了针对性的数据增强。除了通用的几何变换和颜色调整外还模拟了不同光照条件下的轮胎图像包括添加阴影、高光反射等效果。同时通过随机裁剪和缩放模拟不同拍摄距离下的轮胎图像增强模型对尺度变化的鲁棒性。为解决样本不平衡问题本研究采用了过采样和欠采样相结合的策略。对于正常轮胎类别采用欠采样方法随机选择与最大缺陷类别数量相当的样本对于各缺陷类别采用过采样方法通过SMOTE算法生成合成样本使各类别样本数量达到平衡。最终训练集中各类别样本数量均为644张。数据集标注采用YOLO格式的边界框标注每个缺陷实例包含类别标签和边界框坐标(x_center,y_center,width,height)。标注工作由两名专业人员进行并通过交叉验证确保标注准确性。对于边界框采用IOU(交并比)≥0.5作为合格标准标注不一致处由第三方专家进行最终裁定。核心代码import cv2import numpy as npfrom conveyor_detector import ConveyorDetectorimport osimport timefrom pathlib import Pathimport json程序与 best.pt 所在根目录与本文件同级的 code 目录CODE_ROOT Path(file).resolve().parentclass DetectionThread(QThread):“”“在独立线程中运行检测避免阻塞界面”“”finished Signal(dict)error Signal(str)progress Signal(int, str) # 进度百分比和消息frame_processed Signal(object, dict) # 处理后的图像和检测信息def __init__(self, detector, source, source_typeimage): super().__init__() self.detector detector self.source source self.source_type source_type # image, video, folder self._is_running True def stop(self): 停止检测 self._is_running False def run(self): try: if self.source_type image: result self.detector.detect(self.source) self.finished.emit(result) elif self.source_type video: self.process_video() elif self.source_type folder: self.process_folder() except Exception as e: self.error.emit(str(e)) def process_video(self): 处理视频文件 cap cv2.VideoCapture(self.source) if not cap.isOpened(): self.error.emit(无法打开视频文件) return total_frames int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) fps cap.get(cv2.CAP_PROP_FPS) # 创建输出目录 output_dir Path(output) / video_results / Path(self.source).stem output_dir.mkdir(parentsTrue, exist_okTrue) all_detections [] frame_count 0 detection_count 0 while self._is_running and cap.isOpened(): ret, frame cap.read() if not ret: break frame_count 1 # 每5帧检测一次以提高速度 if frame_count % 5 0: # 保存帧为临时文件 temp_frame_path output_dir / ftemp_frame_{frame_count}.jpg cv2.imwrite(str(temp_frame_path), frame) # 检测 result self.detector.detect(str(temp_frame_path)) if result.get(success): annotated_image result[annotated_image] detections result.get(detections, []) # 发送图像到UI显示 detection_info { frame: frame_count, time: frame_count / fps, detections: detections, total_detections: len(detections) } self.frame_processed.emit(annotated_image, detection_info) # 保存带标注的帧 if detections: # 只保存有检测结果的帧 annotated_path output_dir / fframe_{frame_count:06d}.jpg cv2.imwrite(str(annotated_path), annotated_image) detection_count 1 all_detections.append({ frame: frame_count, time: frame_count / fps, detections: detections }) # 删除临时文件 temp_frame_path.unlink(missing_okTrue) # 更新进度 progress int((frame_count / total_frames) * 100) time_str f{int(frame_count / fps)}s self.progress.emit(progress, f处理帧 {frame_count}/{total_frames} ({time_str})) cap.release() # 保存检测结果 result_json output_dir / detection_results.json with open(result_json, w, encodingutf-8) as f: json.dump(all_detections, f, ensure_asciiFalse, indent2) # 返回结果 result { success: True, type: video, total_frames: frame_count, output_dir: str(output_dir), detections_count: detection_count, result_file: str(result_json) } self.finished.emit(result) def process_folder(self): 处理文件夹中的所有图片 folder_path Path(self.source) image_extensions {.jpg, .jpeg, .png, .bmp, .tiff} image_files [f for f in folder_path.iterdir() if f.suffix.lower() in image_extensions] if not image_files: self.error.emit(文件夹中没有找到图片文件) return # 创建输出目录 output_dir Path(output) / batch_results / folder_path.name output_dir.mkdir(parentsTrue, exist_okTrue) all_results [] total_detections 0 class_counts {} for idx, image_file in enumerate(image_files): if not self._is_running: break # 检测 result self.detector.detect(str(image_file)) if result.get(success): annotated_image result[annotated_image] detections result.get(detections, []) # 发送图像到UI显示 detection_info { filename: image_file.name, index: idx 1, total: len(image_files), detections: detections, total_detections: len(detections) } self.frame_processed.emit(annotated_image, detection_info) # 保存带标注的图片 output_path output_dir / fresult_{image_file.name} cv2.imwrite(str(output_path), annotated_image) # 统计 total_detections len(detections) for det in detections: class_name det[class_name] class_counts[class_name] class_counts.get(class_name, 0) 1 all_results.append({ filename: image_file.name, detections: detections, detection_count: len(detections) }) # 更新进度 progress int(((idx 1) / len(image_files)) * 100) self.progress.emit(progress, f处理 {idx 1}/{len(image_files)}: {image_file.name}) # 保存检测结果 result_json output_dir / detection_results.json with open(result_json, w, encodingutf-8) as f: json.dump({ total_images: len(image_files), total_detections: total_detections, class_counts: class_counts, results: all_results }, f, ensure_asciiFalse, indent2) # 返回结果 result { success: True, type: folder, total_images: len(image_files), total_detections: total_detections, class_counts: class_counts, output_dir: str(output_dir), result_file: str(result_json) } self.finished.emit(result)class MainWindow(QMainWindow):“”“应用主窗口”“”def __init__(self): super().__init__() self.detector None self.current_image_path None self.current_source None self.current_source_type image # image, video, folder self.detection_thread None self.init_ui() self.load_model() def init_ui(self): 初始化用户界面 self.setWindowTitle(轮胎缺陷检测系统) self.setGeometry(100, 100, 1400, 900) # Central widget central_widget QWidget() self.setCentralWidget(central_widget) # Main layout main_layout QHBoxLayout(central_widget) main_layout.setSpacing(15) main_layout.setContentsMargins(15, 15, 15, 15) # Left panel (controls and settings) left_panel self.create_left_panel() main_layout.addWidget(left_panel, 1) # Right panel (image display and results) right_panel self.create_right_panel() main_layout.addWidget(right_panel, 2) # Apply styles self.apply_styles() def create_left_panel(self): 创建左侧控制面板 panel QWidget() layout QVBoxLayout(panel) layout.setSpacing(10) # Model status group status_group self.create_status_group() layout.addWidget(status_group) # Image selection group image_group self.create_image_selection_group() layout.addWidget(image_group) # Detection settings group settings_group self.create_settings_group() layout.addWidget(settings_group) # Statistics group stats_group self.create_statistics_group() layout.addWidget(stats_group) layout.addStretch() return panel def create_status_group(self): 创建模型状态面板 group QGroupBox(模型状态) layout QVBoxLayout(group) self.status_label QLabel(状态未加载\n\n正在加载轮胎缺陷检测模型请稍候...) self.status_label.setWordWrap(True) self.status_label.setToolTip(显示当前模型的加载与运行状态) layout.addWidget(self.status_label) self.progress_bar QProgressBar() self.progress_bar.setVisible(False) layout.addWidget(self.progress_bar) return group def create_image_selection_group(self): 创建图像/视频/文件夹选择面板 group QGroupBox(输入源选择) layout QVBoxLayout(group) # 输入类型选择 type_layout QHBoxLayout() type_label QLabel(输入类型) type_layout.addWidget(type_label) self.source_type_combo QComboBox() self.source_type_combo.addItems([图片, 视频, 文件夹批量]) self.source_type_combo.currentTextChanged.connect(self.on_source_type_changed) self.source_type_combo.setToolTip(选择要处理的输入类型) type_layout.addWidget(self.source_type_combo) layout.addLayout(type_layout) self.select_source_btn QPushButton(选择输入源) self.select_source_btn.clicked.connect(self.select_source) self.select_source_btn.setToolTip(点击选择轮胎图像、视频或批量图片文件夹) layout.addWidget(self.select_source_btn) self.detect_btn QPushButton(开始检测) self.detect_btn.clicked.connect(self.start_detection) self.detect_btn.setEnabled(False) self.detect_btn.setToolTip(对当前输入源执行轮胎缺陷检测) layout.addWidget(self.detect_btn) self.stop_btn QPushButton(停止检测) self.stop_btn.clicked.connect(self.stop_detection) self.stop_btn.setEnabled(False) self.stop_btn.setVisible(False) self.stop_btn.setStyleSheet( QPushButton { background-color: #e74c3c; } QPushButton:hover { background-color: #c0392b; } ) layout.addWidget(self.stop_btn) self.source_path_label QLabel(尚未选择输入源) self.source_path_label.setWordWrap(True) self.source_path_label.setStyleSheet(color: #666; font-style: italic;) layout.addWidget(self.source_path_label) return group def create_settings_group(self): 创建检测参数设置面板 group QGroupBox(检测参数设置) layout QVBoxLayout(group) # Confidence threshold conf_layout QHBoxLayout() conf_label QLabel(置信度阈值) conf_label.setToolTip(用于过滤低置信度结果范围 0.0 - 1.0\n数值越低识别出的目标越多但误检也会增加。) conf_layout.addWidget(conf_label) self.conf_spinbox QDoubleSpinBox() self.conf_spinbox.setRange(0.0, 1.0) self.conf_spinbox.setSingleStep(0.05) self.conf_spinbox.setValue(0.25) self.conf_spinbox.setToolTip(调整置信度阈值0.0 - 1.0) self.conf_spinbox.valueChanged.connect(self.update_confidence) conf_layout.addWidget(self.conf_spinbox) layout.addLayout(conf_layout) # IoU threshold iou_layout QHBoxLayout() iou_label QLabel(IoU 阈值) iou_label.setToolTip(用于非极大值抑制 (NMS) 的 IoU 阈值范围 0.0 - 1.0\n数值越低保留的重叠框越多。) iou_layout.addWidget(iou_label) self.iou_spinbox QDoubleSpinBox() self.iou_spinbox.setRange(0.0, 1.0) self.iou_spinbox.setSingleStep(0.05) self.iou_spinbox.setValue(0.45) self.iou_spinbox.setToolTip(调整 NMS 的 IoU 阈值0.0 - 1.0) self.iou_spinbox.valueChanged.connect(self.update_iou) iou_layout.addWidget(self.iou_spinbox) layout.addLayout(iou_layout) return group def create_statistics_group(self): 创建检测统计信息面板 group QGroupBox(检测统计) layout QVBoxLayout(group) self.stats_table QTableWidget() self.stats_table.setColumnCount(2) self.stats_table.setHorizontalHeaderLabels([类别, 数量]) self.stats_table.horizontalHeader().setStretchLastSection(True) self.stats_table.setMaximumHeight(300) self.stats_table.setToolTip(显示当前图像中各类别数量统计) layout.addWidget(self.stats_table) self.total_label QLabel(识别目标总数0) self.total_label.setToolTip(当前图像中识别到的全部目标数量) layout.addWidget(self.total_label) return group def create_right_panel(self): 创建右侧图像显示与结果面板 panel QWidget() layout QVBoxLayout(panel) layout.setSpacing(10) # Image display area image_group QGroupBox(图像显示) image_layout QVBoxLayout(image_group) self.image_label QLabel(尚未加载图像) self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setMinimumSize(800, 600) self.image_label.setStyleSheet( QLabel { border: 2px dashed #ccc; background-color: #f5f5f5; color: #999; } ) scroll_area QScrollArea() scroll_area.setWidget(self.image_label) scroll_area.setWidgetResizable(True) scroll_area.setMinimumHeight(600) image_layout.addWidget(scroll_area) layout.addWidget(image_group) # Detection results results_group QGroupBox(检测结果) results_layout QVBoxLayout(results_group) self.results_text QTextEdit() self.results_text.setReadOnly(True) self.results_text.setMaximumHeight(150) self.results_text.setPlaceholderText(运行检测后这里将显示轮胎缺陷检测结果类别、数量、置信度等...) self.results_text.setToolTip(显示每个识别目标的类别名称、置信度和位置信息) results_layout.addWidget(self.results_text) layout.addWidget(results_group) return panel def load_model(self): 加载轮胎缺陷检测模型 self.status_label.setText(状态正在加载模型...) self.progress_bar.setVisible(True) self.progress_bar.setRange(0, 0) # Indeterminate progress try: # 固定从本程序目录加载 best.pt不依赖当前工作目录 possible_paths [ CODE_ROOT / best.pt, CODE_ROOT / runs / detect / train / weights / best.pt, CODE_ROOT / runs / detect / train2 / weights / best.pt, CODE_ROOT / runs / segment / train / weights / best.pt, ] model_path None for path in possible_paths: if path.is_file(): model_path str(path.resolve()) break if model_path is None: import glob pattern str(CODE_ROOT / runs / ** / weights / best.pt) runs_best glob.glob(pattern, recursiveTrue) if runs_best: model_path str(Path(max(runs_best, keyos.path.getmtime)).resolve()) if model_path is None or not os.path.isfile(model_path): hint str(CODE_ROOT / best.pt) QMessageBox.critical( self, 错误, f未找到模型权重文件 best.pt\n\n f请将 best.pt 放到以下路径之一\n f· {hint}\n f· {CODE_ROOT / runs / detect / train / weights / best.pt}\n f· 或 runs 下任意 …/weights/best.pt, ) self.status_label.setText(状态模型权重文件不存在) self.progress_bar.setVisible(False) return data_yaml str(CODE_ROOT / datasets / data / data.yaml) self.detector ConveyorDetector(model_path, data_yaml) if self.detector.load_model(): self.status_label.setText( f状态模型加载成功\n f权重文件{model_path}\n f设备{self.detector.device}\n f类别数{len(self.detector.class_names)}\n f类别{, .join(self.detector.class_names)} ) self.progress_bar.setVisible(False) else: QMessageBox.critical(self, 错误, 模型加载失败请检查权重文件与环境配置。) self.status_label.setText(状态模型加载失败) self.progress_bar.setVisible(False) except Exception as e: QMessageBox.critical(self, 错误, f加载模型时发生异常{str(e)}) self.status_label.setText(f状态错误 - {str(e)}) self.progress_bar.setVisible(False) def select_image(self): 打开文件对话框选择待检测图像 file_path, _ QFileDialog.getOpenFileName( self, 选择图像, , 图像文件 (*.png *.jpg *.jpeg *.bmp *.tiff);;所有文件 (*) ) if file_path: self.current_image_path file_path self.image_path_label.setText(f已选择{os.path.basename(file_path)}) self.detect_btn.setEnabled(True) self.display_image(file_path) def on_source_type_changed(self, text): 输入类型改变时的处理 self.current_source None self.detect_btn.setEnabled(False) if text 图片: self.source_path_label.setText(尚未选择图片) self.current_source_type image elif text 视频: self.source_path_label.setText(尚未选择视频) self.current_source_type video elif text 文件夹批量: self.source_path_label.setText(尚未选择文件夹) self.current_source_type folder def select_source(self): 根据选择的类型打开相应的文件/文件夹对话框 source_type self.source_type_combo.currentText() if source_type 图片: file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , 图像文件 (*.png *.jpg *.jpeg *.bmp *.tiff);;所有文件 (*) ) if file_path: self.current_source file_path self.current_image_path file_path self.source_path_label.setText(f已选择{os.path.basename(file_path)}) self.detect_btn.setEnabled(True) self.display_image(file_path) elif source_type 视频: file_path, _ QFileDialog.getOpenFileName( self, 选择视频, , 视频文件 (*.mp4 *.avi *.mov *.mkv);;所有文件 (*) ) if file_path: self.current_source file_path self.source_path_label.setText(f已选择{os.path.basename(file_path)}) self.detect_btn.setEnabled(True) # 显示视频第一帧 self.display_video_first_frame(file_path) elif source_type 文件夹批量: folder_path QFileDialog.getExistingDirectory( self, 选择文件夹, ) if folder_path: self.current_source folder_path # 统计图片数量 image_extensions {.jpg, .jpeg, .png, .bmp, .tiff} image_count sum(1 for f in Path(folder_path).iterdir() if f.suffix.lower() in image_extensions) self.source_path_label.setText( f已选择{os.path.basename(folder_path)}\n f包含 {image_count} 张图片 ) self.detect_btn.setEnabled(True) def display_video_first_frame(self, video_path): 显示视频的第一帧 try: cap cv2.VideoCapture(video_path) ret, frame cap.read() cap.release() if ret: # Convert BGR to RGB image_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch image_rgb.shape bytes_per_line ch * w qt_image QImage(image_rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap QPixmap.fromImage(qt_image) scaled_pixmap pixmap.scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.image_label.setPixmap(scaled_pixmap) else: self.image_label.setText(无法读取视频第一帧) except Exception as e: QMessageBox.warning(self, 警告, f显示视频预览时发生错误{str(e)})# 源码文件源码文件源码获取欢迎大家点赞、收藏、关注、评论啦 、查看获取联系方式