简介:本资源是一套基于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:1(train: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 python=3.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 \ data=wafer.yaml \ model=yolo11s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=30 \ mosaic=1.0 \ mixup=0.1 \ degrees=180 \ fliplr=0.5 \ flipud=0.5 \ project=runs/wafer \ name=exp1参数逐个说:model=yolo11s.pt是YOLO11的small版本,晶圆缺陷目标通常不大,s版本在精度和速度上比较平衡,如果显存够可以上m版本。imgsz=640是输入分辨率,晶圆图如果原始分辨率很高(比如2048),直接缩到640会丢小缺陷,这时候要么切图训练,要么把imgsz提到1024,但batch要相应降到8或4。degrees=180是旋转增强,晶圆缺陷方向随机,这个参数很关键,能显著提升泛化。flipud=0.5上下翻转也要开,因为晶圆图没有固定方向。patience=30是早停,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, conf=0.25, iou=0.45, device="cuda"): 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( source=img_bgr, conf=self.conf, iou=self.iou, device=self.device, verbose=False )[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 imgconf=0.25和iou=0.45是推理时的两个关键阈值。conf太低会出大量误检,太高会漏检小缺陷,晶圆场景我一般从0.25起步,根据误检情况微调。iou是NMS的阈值,缺陷密集时(比如一片上十几个颗粒)要适当调低到0.4,避免相邻框被误合并。device="cuda"在有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, stretch=3) # 右:参数与结果 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, stretch=1) 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)"], label="mAP50") axes[0].plot(df["epoch"], df["metrics/mAP50-95(B)"], label="mAP50-95") axes[0].set_xlabel("epoch"); axes[0].legend(); axes[0].set_title("mAP曲线") axes[1].plot(df["epoch"], df["metrics/precision(B)"], label="precision") axes[1].plot(df["epoch"], df["metrics/recall(B)"], label="recall") axes[1].set_xlabel("epoch"); axes[1].legend(); axes[1].set_title("P/R曲线") # F1近似:2PR/(P+R) p = df["metrics/precision(B)"] r = df["metrics/recall(B)"] f1 = 2*p*r/(p+r+1e-9) axes[2].plot(df["epoch"], f1, color="green") axes[2].set_xlabel("epoch"); axes[2].set_title("F1曲线") plt.tight_layout() plt.savefig("eval_curves.png", dpi=150)这段代码读results.csv画三张图。注意列名里的空格,ultralytics导出的csv列名有时带前导空格,用strip()清一下。F1曲线能帮你判断模型是否过拟合——如果F1在某个epoch后持续下降,说明开始过拟合,早停点应该选在F1峰值附近。另外,如果你有test集的预测结果,可以用yolo detect val配合不同conf跑几遍,画出conf-F1曲线,那个比训练曲线更贴近实际部署。
我自己的习惯是:每次训完模型,先把这三张图存下来,和上一版对比,确认提升是真实的而不是随机波动。然后拿产线图跑50张,人工数漏检和误检,算一个「产线通过率」。只有这个通过率达标,才会把模型打包进PyQt5界面交付。这套流程跑下来,比单纯看mAP靠谱得多。希望帮到你。
本文还有配套的精品资源,点击获取