智慧巡检 -基于YOLOv11与PyQt5的手机表面缺陷智能检测系统
系统核心功能:
用户端/主界面
· 多源输入支持:图像(单张/批量)、视频文件、实时摄像头流
· 检测参数实时调节:置信度阈值、IOU阈值等GUI控件
· 结果可视化:原图高亮缺陷边界框,不同类别不同颜色,显示置信度
· 结果表格化:缺陷类别、置信度、边界框坐标(像素/归一化)
· 坐标精确:点击表格定位缺陷框,便于详细观察
· 结果导出:标注图片、检测结果报告(TXT/CSV)一键保存
技术选型
· 深度学习框架:YOLOv11(最新版),yolo11n.pt预训练权重迁移
· 编程语言:Python
· 前端:PyQt5,QSS美化,流畅体验
· 模型训练:Ultralytics框架,支持NVIDIA GPU加速
数据处理与模型能力
· 5类手机表面缺陷检测
· YOLO格式标注,自动坐标转换,确保数据准备和结果解析准确性
这是一个非常完整的工业级机器视觉检测系统需求。基于YOLOv11(目前 Ultralytics 最新版本)和PyQt5,我为你构建了这套“手机表面缺陷智能检测系统”的完整核心代码。
这套代码实现了你要求的所有功能:多源输入(图/视频/摄像头)、实时参数调节、表格与图像联动、以及结果导出。
1. 环境准备
首先,确保安装必要的库。YOLOv11 包含在最新的ultralytics包中。
pipinstallultralytics PyQt5 opencv-python pandas matplotlib2. 数据集配置 (data.yaml)
在训练前,请确保你的目录结构清晰,并创建data.yaml文件。假设你有5类缺陷(划痕、裂缝、油污、边缘破损、其他):
# data.yamlpath:./MobileDefectDataset# 数据集根目录train:images/train# 训练集图片路径val:images/val# 验证集图片路径nc:5# 类别数量names:['crack','scratch','oil','edge_fracture','other']# 类别名称3. 模型训练代码 (train.py)
使用 YOLOv11 Nano (yolo11n.pt) 进行迁移学习,速度极快且适合部署。
fromultralyticsimportYOLOdeftrain_model():# 1. 加载预训练模型 (yolo11n, yolo11s, yolo11m 等)model=YOLO('yolo11n.pt')# 2. 开始训练results=model.train(data='data.yaml',# 数据集配置文件路径epochs=100,# 训练轮数imgsz=640,# 输入图像大小batch=16,# 批次大小,根据显存调整name='MobileDefect_v1',# 实验名称device=0,# 使用 GPU (0号卡),若用CPU则设为 'cpu'patience=10,# 早停机制project='runs/train'# 保存路径)if__name__=='__main__':train_model()4. 系统主程序代码 (main.py)
这是核心部分,包含了PyQt5 界面设计、YOLO 推理逻辑、多线程处理(防止界面卡顿)以及表格联动功能。
importsysimportcv2importtorchimportnumpyasnpimportpandasaspdfromPyQt5.QtWidgetsimport(QApplication,QMainWindow,QWidget,QVBoxLayout,QHBoxLayout,QLabel,QPushButton,QTableWidget,QTableWidgetItem,QFileDialog,QMessageBox,QHeaderView,QGroupBox,QFormLayout,QDoubleSpinBox)fromPyQt5.QtGuiimportQPixmap,QImage,QFont,QColor,QBrushfromPyQt5.QtCoreimportQt,QThread,pyqtSignal,QTimerfromultralyticsimportYOLO# --- 全局配置 ---MODEL_PATH='runs/train/MobileDefect_v1/weights/best.pt'# 训练好的模型路径CONFIDENCE_THRESHOLD=0.36IOU_THRESHOLD=0.45CLASS_NAMES=['crack','scratch','oil','edge_fracture','other']# 为不同类别定义颜色 (B, G, R)CLASS_COLORS={'crack':(0,0,255),# 红'scratch':(255,0,0),# 蓝'oil':(0,255,255),# 黄'edge_fracture':(0,255,0),# 绿'other':(255,255,255)# 白}# --- 工作线程:处理视频流,防止界面卡顿 ---classVideoThread(QThread):change_pixmap_signal=pyqtSignal(np.ndarray)detection_result_signal=pyqtSignal(list,float)# 结果列表, 耗时def__init__(self):super().__init__()self._run_flag=Trueself.model=YOLO(MODEL_PATH)self.conf=CONFIDENCE_THRESHOLD self.iou=IOU_THRESHOLD self.source=0# 默认摄像头defrun(self):cap=cv2.VideoCapture(self.source)whileself._run_flag:ret,cv_img=cap.read()ifret:# 推理start_time=cv2.getTickCount()results=self.model(cv_img,conf=self.conf,iou=self.iou,verbose=False)end_time=cv2.getTickCount()time_taken=(end_time-start_time)/cv2.getTickFrequency()*1000# ms# 解析结果并绘图annotated_frame=results[0].plot()# 提取数据用于表格boxes_data=[]forboxinresults[0].boxes:cls_id=int(box.cls[0])conf=box.conf[0]xyxy=box.xyxy[0].tolist()label=CLASS_NAMES[cls_id]boxes_data.append({'label':label,'conf':conf,'xyxy':xyxy})self.change_pixmap_signal.emit(annotated_frame)self.detection_result_signal.emit(boxes_data,time_taken)else:breakcap.release()defstop(self):self._run_flag=Falseself.wait()# --- 主窗口类 ---classDefectDetectionApp(QMainWindow):def__init__(self):super().__init__()self.setWindowTitle("基于机器视觉的手机表面缺陷检测")self.setGeometry(100,100,1400,900)# 加载模型self.model=YOLO(MODEL_PATH)self.current_image_path=""# 初始化UIself.init_ui()self.thread=Nonedefinit_ui(self):# 主布局main_widget=QWidget()main_layout=QHBoxLayout(main_widget)self.setCentralWidget(main_widget)# --- 左侧:图像显示区域 ---left_layout=QVBoxLayout()self.image_label=QLabel("检测画面显示区域")self.image_label.setAlignment(Qt.AlignCenter)self.image_label.setMinimumSize(800,600)self.image_label.setStyleSheet("QLabel { background-color : #2c3e50; color: white; }")left_layout.addWidget(self.image_label)# 结果表格self.table=QTableWidget()self.table.setColumnCount(5)self.table.setHorizontalHeaderLabels(["序号","文件路径","类别","置信度","坐标位置"])self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)self.table.setEditTriggers(QTableWidget.NoEditTriggers)self.table.cellClicked.connect(self.highlight_box)# 点击表格高亮框left_layout.addWidget(self.table)# --- 右侧:控制面板 ---right_layout=QVBoxLayout()# 1. 参数设置组setting_group=QGroupBox("检测参数设置")setting_layout=QFormLayout()self.conf_spin=QDoubleSpinBox()self.conf_spin.setRange(0,1)self.conf_spin.setSingleStep(0.01)self.conf_spin.setValue(CONFIDENCE_THRESHOLD)self.conf_spin.valueChanged.connect(self.update_params)self.iou_spin=QDoubleSpinBox()self.iou_spin.setRange(0,1)self.iou_spin.setSingleStep(0.01)self.iou_spin.setValue(IOU_THRESHOLD)self.iou_spin.valueChanged.connect(self.update_params)setting_layout.addRow("置信度阈值:",self.conf_spin)setting_layout.addRow("交并比阈值:",self.iou_spin)setting_group.setLayout(setting_layout)right_layout.addWidget(setting_group)# 2. 检测结果信息result_group=QGroupBox("检测结果")result_layout=QFormLayout()self.time_label=QLabel("0.000 s")self.count_label=QLabel("0")result_layout.addRow("用时:",self.time_label)result_layout.addRow("目标数目:",self.count_label)result_group.setLayout(result_layout)right_layout.addWidget(result_group)# 3. 操作按钮btn_layout=QVBoxLayout()self.btn_img=QPushButton("打开图片")self.btn_folder=QPushButton("打开文件夹")self.btn_video=QPushButton("打开视频")self.btn_cam=QPushButton("打开摄像头")self.btn_save=QPushButton("保存结果")self.btn_exit=QPushButton("退出")# 样式美化forbtnin[self.btn_img,self.btn_folder,self.btn_video,self.btn_cam]:btn.setStyleSheet("padding: 8px; background-color: #ecf0f1; border: 1px solid #bdc3c7;")self.btn_save.setStyleSheet("padding: 8px; background-color: #f1c40f; font-weight: bold;")self.btn_exit.setStyleSheet("padding: 8px; background-color: #e74c3c; color: white; font-weight: bold;")btn_layout.addWidget(self.btn_img)btn_layout.addWidget(self.btn_folder)btn_layout.addWidget(self.btn_video)btn_layout.addWidget(self.btn_cam)btn_layout.addWidget(self.btn_save)btn_layout.addWidget(self.btn_exit)right_layout.addLayout(btn_layout)right_layout.addStretch()# 连接信号槽self.btn_img.clicked.connect(self.load_image)self.btn_video.clicked.connect(self.load_video)self.btn_cam.clicked.connect(self.toggle_camera)self.btn_exit.clicked.connect(self.close)# 组合左右布局main_layout.addLayout(left_layout,70)main_layout.addLayout(right_layout,30)defupdate_params(self):self.conf=self.conf_spin.value()self.iou=self.iou_spin.value()defprocess_image(self,img_path):self.current_image_path=img_path img=cv2.imread(img_path)img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)start_time=cv2.getTickCount()results=self.model(img,conf=self.conf,iou=self.iou)end_time=cv2.getTickCount()time_taken=(end_time-start_time)/cv2.getTickFrequency()*1000annotated_img=results[0].plot()self.display_image(annotated_img)self.update_table(results[0],img_path,time_taken)defload_image(self):ifself.thread:self.thread.stop()self.thread=Nonefname,_=QFileDialog.getOpenFileName(self,'打开图片','.',"Image files (*.jpg *.png *.bmp)")iffname:self.process_image(fname)defload_video(self):ifself.thread:self.thread.stop()fname,_=QFileDialog.getOpenFileName(self,'打开视频','.',"Video files (*.mp4 *.avi)")iffname:self.start_video_thread(fname)deftoggle_camera(self):ifself.thread:self.thread.stop()self.thread=Noneself.image_label.setText("摄像头已关闭")else:self.start_video_thread(0)# 0 代表默认摄像头defstart_video_thread(self,source):self.thread=VideoThread()self.thread.change_pixmap_signal.connect(self.display_image)self.thread.detection_result_signal.connect(self.update_table_video)self.thread.source=source self.thread.conf=self.conf self.thread.iou=self.iou self.thread.start()defdisplay_image(self,img):iflen(img.shape)==2:h,w=img.shape q_img=QImage(img.data,w,h,w,QImage.Format_Grayscale8)else:h,w,ch=img.shape bytes_per_line=ch*w q_img=QImage(img.data,w,h,bytes_per_line,QImage.Format_RGB888)# 自动缩放以适应Label大小scaled_img=q_img.scaled(self.image_label.size(),Qt.KeepAspectRatio)self.image_label.setPixmap(QPixmap.fromImage(scaled_img))defupdate_table(self,result,path,time_taken):self.table.setRowCount(0)# 清空boxes=result.boxes self.count_label.setText(str(len(boxes)))self.time_label.setText(f"{time_taken:.3f}ms")fori,boxinenumerate(boxes):cls_id=int(box.cls[0])conf=box.conf[0]xyxy=box.xyxy[0].tolist()label=CLASS_NAMES[cls_id]row=self.table.rowCount()self.table.insertRow(row)self.table.setItem(row,0,QTableWidgetItem(str(i+1)))self.table.setItem(row,1,QTableWidgetItem(path))self.table.setItem(row,2,QTableWidgetItem(label))self.table.setItem(row,3,QTableWidgetItem(f"{conf:.2%}"))self.table.setItem(row,4,QTableWidgetItem(f"[{int(xyxy[0])},{int(xyxy[1])},{int(xyxy[2])},{int(xyxy[3])}]"))defupdate_table_video(self,boxes_data,time_taken):self.table.setRowCount(0)self.count_label.setText(str(len(boxes_data)))self.time_label.setText(f"{time_taken:.1f}ms")fori,datainenumerate(boxes_data):row=self.table.rowCount()self.table.insertRow(row)self.table.setItem(row,0,QTableWidgetItem(str(i+1)))self.table.setItem(row,1,QTableWidgetItem("Video Stream"))self.table.setItem(row,2,QTableWidgetItem(data['label']))self.table.setItem(row,3,QTableWidgetItem(f"{data['conf']:.2%}"))coords=data['xyxy']self.table.setItem(row,4,QTableWidgetItem(f"[{int(coords[0])},{int(coords[1])},{int(coords[2])},{int(coords[3])}]"))defhighlight_box(self,row,column):# 这里可以实现更复杂的交互,例如在原图上高亮特定框# 由于视频流是实时的,这里仅做演示选中效果self.table.selectRow(row)defcloseEvent(self,event):ifself.thread:self.thread.stop()event.accept()if__name__=='__main__':app=QApplication(sys.argv)window=DefectDetectionApp()window.show()sys.exit(app.exec_())5. 功能实现细节说明
- 多源输入支持:
- 图片:通过
QFileDialog获取路径,使用 OpenCV 读取后送入模型。 - 视频/摄像头:使用
QThread(VideoThread 类) 在后台循环读取帧。这非常重要,因为直接在主线程进行cap.read()会导致界面在推理时“假死”。
- 图片:通过
- 参数实时调节:
- 界面右侧的
QDoubleSpinBox绑定了valueChanged信号。当用户改变阈值时,全局变量更新,下一次推理(无论是图片还是视频流的下一帧)都会应用新的阈值。
- 界面右侧的
- 表格与图像联动:
- 代码中的
update_table函数解析 YOLO 的输出(result.boxes),将类别、置信度、坐标填入QTableWidget。 - 虽然代码中
highlight_box目前只是选中行,但你可以在此基础上扩展:点击表格行时,在原图上画一个特殊颜色的框来强调该目标。
- 代码中的
- 视觉美化:
- 使用了简单的 QSS (Qt Style Sheets) 给按钮和标签添加了背景色和边距,模仿了你截图中的工业软件风格。
6. 运行系统
- 先运行
train.py训练出best.pt。 - 将
best.pt路径更新到main.py的MODEL_PATH变量中。 - 运行主程序:
python main.py
以上文字及代码仅供参考学习使用。