简介本资源是一套基于YOLO11深度学习构建的半导体晶圆外观缺陷检测系统面向计算机、人工智能、自动化、电子信息等专业的在校学生、教师及企业技术人员也适合作为毕业设计、课程设计或实战演示项目。系统可识别中心、甜甜圈、边缘位置、边缘环、位置、接近满、无、随机、划痕共9类晶圆外观缺陷配套PyQt5图形界面便于直观操作与结果展示。压缩包共约2000个文件以txt标注与索引文件为主另含xml、yaml配置及py源码整体约263.59MB并附带13000张标注好的数据集、训练好的模型、评估指标曲线、演示图片视频与安装使用教程开箱即用。目前已有163人学习关注。读者可直接获得完整可运行的检测方案包括界面源码、数据集组织方式、模型权重与训练评估结果既能用于快速复现实验也可在此基础上修改扩展实现其他缺陷检测功能。1. 半导体晶圆外观缺陷检测从13000张标注图到PyQt5桌面端落地半导体晶圆外观检测这个场景痛点非常具体晶圆表面缺陷种类多、对比度低、划痕和颗粒的边界模糊人工在显微镜下盯久了漏检率直线上升。我最早接触这类需求是在一家封测厂做产线视觉方案当时用传统OpenCV阈值分割换个批次的光照就集体翻车后来切到YOLO11深度学习方案才算稳住。这套「基于YOLO11深度学习的半导体晶圆外观缺陷检测系统」要解决的就是把缺陷检测从实验室脚本变成产线可用的桌面工具——Python源码、PyQt5界面、13000张标注好的数据集、训练好的模型、评估指标曲线、演示图片视频开箱即用。适合两类人一是想快速跑通一个完整缺陷检测闭环的深度学习入门者二是需要给产线做POC验证的一线工程师。下面按「数据怎么组织→模型怎么训→界面怎么接→坑在哪」的顺序拆开讲。2. 晶圆缺陷数据集的组织方式与YOLO11训练前处理2.1 13000张标注图该怎么切分和校验拿到13000张标注好的晶圆缺陷数据集第一件事不是直接开训而是做数据体检。晶圆缺陷常见类别包括划痕scratch、颗粒污染particle、边缘崩缺edge chip、针痕probe mark、雾状缺陷haze等不同类别的样本量往往严重不均衡——划痕可能占一半崩缺只有几百张。我一般先跑一个统计脚本把每类实例数和图像数打出来再决定要不要做重采样。import os from collections import Counter # 假设标注是YOLO格式每张图对应一个同名txt每行 class x_center y_center w h label_dir datasets/wafer/labels/train cls_counter Counter() img_with_defect 0 img_total 0 for txt in os.listdir(label_dir): if not txt.endswith(.txt): continue img_total 1 with open(os.path.join(label_dir, txt)) as f: lines [l.strip() for l in f if l.strip()] if lines: img_with_defect 1 for line in lines: cls_id int(line.split()[0]) cls_counter[cls_id] 1 print(类别实例分布:, dict(cls_counter)) print(f有缺陷图占比: {img_with_defect}/{img_total} {img_with_defect/img_total:.2%})这段脚本的逻辑很直白遍历标签目录统计每个类别的实例总数同时算出「有缺陷图」的比例。参数上唯一要注意的是label_dir要指向train子集val和test要分开统计。如果发现某类实例数低于总实例的5%就得考虑过采样或者用mosaic增强时给这类加权。晶圆数据还有个特殊点很多图是「无缺陷」的正常片如果正常片占比过高模型会偏向预测背景这时候要么下采样正常片要么在损失里对正样本加权。切分比例我一般用7:2:1train:val:test但晶圆场景有个坑——同一片晶圆的不同区域图如果被随机切到train和val会造成数据泄漏因为同一片的缺陷形态高度相似。正确做法是按晶圆ID切分同一片的图只能进同一个集合。这个细节在13000张规模下影响很大我见过val mAP虚高15个点的情况就是泄漏导致的。2.2 YOLO11环境配置与训练命令的必调参数YOLO11的环境配置在Windows上最容易卡在PyTorch和CUDA版本匹配上。我的习惯是先建conda环境再按官方推荐装torch最后装ultralytics。注意不要用pip直接装ultralytics就完事它会把torch也拉下来版本可能和你的CUDA对不上。# 创建环境Python版本建议3.9或3.10 conda create -n wafer_yolo11 python3.10 -y conda activate wafer_yolo11 # 先装匹配CUDA的PyTorch以CUDA 11.8为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 再装ultralytics pip install ultralytics # 验证 yolo checksyolo checks会打印出torch版本、CUDA是否可用、GPU型号。如果CUDA显示不可用八成是装成了CPU版torch重装即可。环境配好后训练命令的核心参数如下yolo detect train \ datawafer.yaml \ modelyolo11s.pt \ epochs200 \ imgsz640 \ batch16 \ lr00.01 \ patience30 \ mosaic1.0 \ mixup0.1 \ degrees180 \ fliplr0.5 \ flipud0.5 \ projectruns/wafer \ nameexp1参数逐个说modelyolo11s.pt是YOLO11的small版本晶圆缺陷目标通常不大s版本在精度和速度上比较平衡如果显存够可以上m版本。imgsz640是输入分辨率晶圆图如果原始分辨率很高比如2048直接缩到640会丢小缺陷这时候要么切图训练要么把imgsz提到1024但batch要相应降到8或4。degrees180是旋转增强晶圆缺陷方向随机这个参数很关键能显著提升泛化。flipud0.5上下翻转也要开因为晶圆图没有固定方向。patience30是早停200轮里如果30轮mAP不涨就停省时间。训练完在runs/wafer/exp1/下会有weights/best.pt和results.csv后者就是评估指标曲线的原始数据可以直接画mAP50、mAP50-95、precision、recall随epoch的变化。3. 从训练好的模型到PyQt5桌面端推理封装与界面联动3.1 用YOLO11做单图/批量推理的封装写法训练好的模型要接到PyQt5界面里中间需要一个推理封装层把「读图→预处理→推理→后处理→画框」这条链路包成一个类界面只负责调它。这样界面和模型解耦换模型不用动界面代码。from ultralytics import YOLO import cv2 import numpy as np class WaferDetector: def __init__(self, weight_path, conf0.25, iou0.45, devicecuda): self.model YOLO(weight_path) self.conf conf self.iou iou self.device device # 类别名和颜色按你的数据集类别改 self.cls_names [scratch, particle, edge_chip, probe_mark, haze] self.colors [(255,0,0),(0,255,0),(0,0,255),(255,255,0),(255,0,255)] def infer(self, img_bgr): results self.model.predict( sourceimg_bgr, confself.conf, iouself.iou, deviceself.device, verboseFalse )[0] boxes results.boxes detections [] if boxes is not None: xyxy boxes.xyxy.cpu().numpy() confs boxes.conf.cpu().numpy() clss boxes.cls.cpu().numpy().astype(int) for (x1,y1,x2,y2), c, k in zip(xyxy, confs, clss): detections.append({ bbox: [int(x1),int(y1),int(x2),int(y2)], conf: float(c), cls: int(k), name: self.cls_names[k] if k len(self.cls_names) else str(k) }) return detections def draw(self, img_bgr, detections): img img_bgr.copy() for d in detections: x1,y1,x2,y2 d[bbox] color self.colors[d[cls] % len(self.colors)] cv2.rectangle(img, (x1,y1), (x2,y2), color, 2) label f{d[name]} {d[conf]:.2f} cv2.putText(img, label, (x1, max(y1-8,12)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) return imgconf0.25和iou0.45是推理时的两个关键阈值。conf太低会出大量误检太高会漏检小缺陷晶圆场景我一般从0.25起步根据误检情况微调。iou是NMS的阈值缺陷密集时比如一片上十几个颗粒要适当调低到0.4避免相邻框被误合并。devicecuda在有GPU时用没有就改cpu但CPU推理单张可能要几百毫秒界面会卡后面会讲怎么用线程解决。3.2 PyQt5界面布局图像显示、参数调节与结果导出PyQt5界面这块核心是三个区域左侧图像显示区、右侧参数面板、底部结果列表。图像显示用QLabel配合QPixmap参数面板用QSlider和QDoubleSpinBox结果列表用QTableWidget。下面是一个最小可用的主窗口骨架。from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSlider, QDoubleSpinBox, QTableWidget, QTableWidgetItem, QFileDialog) from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import Qt import cv2 class MainWindow(QMainWindow): def __init__(self, detector): super().__init__() self.detector detector self.setWindowTitle(晶圆缺陷检测系统) self.resize(1200, 700) central QWidget() self.setCentralWidget(central) main_layout QHBoxLayout(central) # 左图像显示 self.img_label QLabel(请加载图片) self.img_label.setAlignment(Qt.AlignCenter) self.img_label.setMinimumSize(800, 600) self.img_label.setStyleSheet(border:1px solid #ccc;) main_layout.addWidget(self.img_label, stretch3) # 右参数与结果 right QVBoxLayout() right.addWidget(QLabel(置信度阈值)) self.conf_spin QDoubleSpinBox() self.conf_spin.setRange(0.05, 0.95) self.conf_spin.setSingleStep(0.05) self.conf_spin.setValue(0.25) right.addWidget(self.conf_spin) right.addWidget(QLabel(IOU阈值)) self.iou_spin QDoubleSpinBox() self.iou_spin.setRange(0.1, 0.9) self.iou_spin.setSingleStep(0.05) self.iou_spin.setValue(0.45) right.addWidget(self.iou_spin) self.btn_load QPushButton(加载图片) self.btn_load.clicked.connect(self.load_image) right.addWidget(self.btn_load) self.btn_detect QPushButton(开始检测) self.btn_detect.clicked.connect(self.run_detect) right.addWidget(self.btn_detect) self.table QTableWidget(0, 3) self.table.setHorizontalHeaderLabels([类别, 置信度, 坐标]) right.addWidget(self.table) main_layout.addLayout(right, stretch1) self.current_img None def load_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.png *.jpg *.bmp)) if not path: return self.current_img cv2.imread(path) self.show_image(self.current_img) def show_image(self, img_bgr): rgb cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch*w, QImage.Format_RGB888) self.img_label.setPixmap(QPixmap.fromImage(qimg).scaled( self.img_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) def run_detect(self): if self.current_img is None: return self.detector.conf self.conf_spin.value() self.detector.iou self.iou_spin.value() dets self.detector.infer(self.current_img) vis self.detector.draw(self.current_img, dets) self.show_image(vis) self.table.setRowCount(len(dets)) for i, d in enumerate(dets): self.table.setItem(i, 0, QTableWidgetItem(d[name])) self.table.setItem(i, 1, QTableWidgetItem(f{d[conf]:.3f})) self.table.setItem(i, 2, QTableWidgetItem(str(d[bbox])))这段代码的关键点show_image里把OpenCV的BGR转成RGB再转QImage注意ch*w是每行字节数写错会花屏。参数面板的conf和iou直接绑定到detector实例上点检测时读取当前值这样调参不用重启。结果表格三列分别是类别、置信度、坐标方便导出。实际产线用的时候我会再加一个「导出CSV」按钮把table内容写文件方便追溯。注意PyQt5里如果在主线程跑推理大图或CPU推理会让界面假死。正确做法是把推理放到QThread里通过信号槽把结果传回主线程更新UI。这个改造不难但新手容易忽略导致点一下检测界面就卡住。4. 晶圆缺陷检测落地时的避坑与排查清单4.1 小缺陷漏检从imgsz到切图推理的取舍现象模型在val上mAP看着不错但实际跑产线图细划痕和微小颗粒大量漏检。原因通常是输入分辨率不够——晶圆原图可能2048×2048缩到640后一个10像素的缺陷只剩3像素特征基本没了。解决办法有三条路一是把imgsz提到1024或1280代价是显存和推理时间翻倍二是切图推理把大图切成640×640的块每块单独推理再合并重叠区域用NMS去重三是用YOLO11的小目标增强模块比如加P2层检测头。我一般先试切图因为不改模型结构见效快。切图时overlap设0.2左右太小会切断缺陷太大推理量暴涨。4.2 误检爆炸背景纹理被当成缺陷现象正常晶圆图上也框出一堆「缺陷」置信度还不低。原因是训练集里正常片太少或者正常片的花纹和某类缺陷纹理接近。解决一是补充正常片让模型见过足够多的负样本二是提高conf阈值但这是治标三是在数据增强里加亮度、对比度扰动让模型不依赖特定光照。晶圆场景还有个隐蔽原因——标注时把一些边缘反光误标成了缺陷这种脏标注必须回头清洗否则模型学到的就是错的。4.3 界面卡死与显存泄漏推理线程和模型复用现象连续检测几十张图后界面越来越卡最后无响应。原因通常是每次推理都新建了模型实例或者图像数据没释放。解决模型在程序启动时加载一次全局复用推理放QThread每张图处理完显式del掉中间变量。显存泄漏在PyTorch里常见于没加torch.no_grad()推理时一定要包在with torch.no_grad():里ultralytics的predict内部已经处理了但如果你自己写后处理就要注意。4.4 类别不均衡导致的「只认划痕」现象模型对划痕检测很准但崩缺、针痕几乎检不出。原因就是样本不均衡划痕占了大多数。解决一是在数据集层面过采样小类二是在损失里给小类加权YOLO11支持通过cls_pw之类的参数调但更直接的是在数据yaml里复制小类样本三是用focal loss思路但ultralytics默认损失已经够用优先调数据。我一般先把小类样本复制到和划痕差不多的量级再训一版看效果。4.5 评估指标好看但产线不认域偏移问题现象test集mAP 0.9拿到产线新批次图上准确率掉到0.6。原因是训练数据和产线数据存在域偏移——光照、相机、晶圆批次不同。解决一是在产线图上标注一批做微调二是训练时加更强的颜色和亮度增强三是用无监督域适应但这个门槛高。最务实的做法是先拿产线图跑一遍把误检和漏检的图挑出来人工标一批再微调20轮通常能拉回不少。5. 把评估曲线用起来从results.csv到产线阈值的定标技巧训练完的results.csv里有一列列mAP50、mAP50-95、precision、recall很多人只看最后一行其实这条曲线能告诉你很多事。我一般会画三张图mAP随epoch的变化、precision-recall曲线、以及不同conf阈值下的F1曲线。第三张最实用——它直接告诉你conf设多少时F1最高。晶圆场景里漏检的代价通常比误检高所以我会把conf定在F1最高点偏左一点宁可多框几个让复检确认。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/wafer/exp1/results.csv) df.columns [c.strip() for c in df.columns] fig, axes plt.subplots(1, 3, figsize(15,4)) axes[0].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[0].plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) axes[0].set_xlabel(epoch); axes[0].legend(); axes[0].set_title(mAP曲线) axes[1].plot(df[epoch], df[metrics/precision(B)], labelprecision) axes[1].plot(df[epoch], df[metrics/recall(B)], labelrecall) axes[1].set_xlabel(epoch); axes[1].legend(); axes[1].set_title(P/R曲线) # F1近似2PR/(PR) p df[metrics/precision(B)] r df[metrics/recall(B)] f1 2*p*r/(pr1e-9) axes[2].plot(df[epoch], f1, colorgreen) axes[2].set_xlabel(epoch); axes[2].set_title(F1曲线) plt.tight_layout() plt.savefig(eval_curves.png, dpi150)这段代码读results.csv画三张图。注意列名里的空格ultralytics导出的csv列名有时带前导空格用strip()清一下。F1曲线能帮你判断模型是否过拟合——如果F1在某个epoch后持续下降说明开始过拟合早停点应该选在F1峰值附近。另外如果你有test集的预测结果可以用yolo detect val配合不同conf跑几遍画出conf-F1曲线那个比训练曲线更贴近实际部署。我自己的习惯是每次训完模型先把这三张图存下来和上一版对比确认提升是真实的而不是随机波动。然后拿产线图跑50张人工数漏检和误检算一个「产线通过率」。只有这个通过率达标才会把模型打包进PyQt5界面交付。这套流程跑下来比单纯看mAP靠谱得多。希望帮到你。本文还有配套的精品资源点击获取