news 2026/10/5 5:23:36

芯片表面缺陷检测:Mask R-CNN+UNet像素级分割方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
芯片表面缺陷检测:Mask R-CNN+UNet像素级分割方案

简介:面向半导体制造与质量控制领域的工程师、算法研究者及计算机视觉学习者,这份项目实战包聚焦芯片表面bump、dent、dot三类缺陷的像素级检测。方案融合Mask RCNN的实例分割能力与UNet的精细分割优势,覆盖数据预处理、模型训练、结果评估到应用部署的完整流程,适合希望快速落地深度学习视觉检测方案的读者参考。资源共73个文件,压缩包约4.03MB,以Python脚本(py/pyc)、模型与性能分析Notebook(ipynb)、标注与训练数据CSV等为主,另有配置XML及Markdown说明文档,目录按UNet和MaskRCNN两套模型模块化组织,便于对照源码复现实验。目前已有119人学习下载,可作为芯片质检智能化、工业视觉缺陷检测方向的优质项目模板。

1. 芯片表面缺陷检测:bump、dent、dot 为什么需要像素级方案

芯片表面缺陷检测,难不在“看出来”,难在“说清楚”。bump 是凸起点,dent 是凹坑,dot 是微小点状异物,它们往往只有几十到几百像素,还长在金属走线、引脚和字符附近,单靠目标框根本没法描述缺陷的真实边界。基于 Mask R-CNN + UNet 的像素级检测算法,是把这件事拆成两步:Mask R-CNN 先定位可能缺陷区域,UNet 再对区域做逐像素分割,最终输出带轮廓、面积和类别的结果,而不是一个模糊的矩形框。对做芯片测试、AOI 复判和良率分析的人来说,这套方案能直接回答“这个 bump 直径多少像素、缺陷面积占比多少”,也方便后续接机械臂或光学复检。适合手里已有几千张标注图、想从普通目标检测往像素级检测升级的视觉工程师,也适合刚接触分割网络的入门者照着搭一个能跑通的基线。

2. Mask R-CNN + UNet 联合:两个模型的分工与选型理由

2.1 为什么单模型搞不定小缺陷

很多人一开始会用纯目标检测,比如 YOLO 或者 Faster R-CNN,跑完发现 mAP 不差,但真正拿到产线复判时,缺陷面积算不准。原因很直接:目标检测输出的是水平矩形框,而 bump 和 dot 的形状是类圆或不规则块,框住它的矩形框里可能一半是背景。以 IoU 为标准评估时,一个 5×5 像素的 dot,哪怕框偏移 2 个像素,IoU 就跌破 0.5,检测头很难稳定收敛。

反过来单用 UNet 做全图分割也有问题。UNet 擅长像素级分类,但它是对整张图做密集预测,在芯片这种纹理密集的场景里,金属走线、字符边缘、引脚反光都会产生和缺陷相近的局部梯度,网络容易把“像缺陷”的东西也分出来。更关键的是,bump 在整图里只占不到 0.1% 的像素,UNet 的损失函数会被背景主导,训练一段时间后预测结果几乎全是背景,也就是常说的“假收敛”。单模型不是不能做,而是要在小目标召回和像素级精度之间反复调参,调到最后往往还是需要两个模型配合。

2.2 两个网络的分工:检测负责召回,分割负责精度

我一般会这样定义职责:Mask R-CNN 负责召回,UNet 负责精度。Mask R-CNN 自带 RPN 和 FPN,对多尺度缺陷的定位能力比纯分割网络强很多,它先输出一批候选框,候选框里可能有背景,也可能 IoU 不算高,但只要缺陷主体在框内,后续就有救。UNet 的输入是 Mask R-CNN 框出来的局部图像块,这时候缺陷在图像块里的占比可能从不到 0.1% 变成 5% 到 20%,类别极度不平衡的问题被大幅缓解,UNet 可以专心学习“这个局部区域里哪些像素是缺陷”。

很多人会问,Mask R-CNN 自己也有 mask 分支,为什么还要再接一层 UNet?因为 Mask R-CNN 的 mask head 输出分辨率默认只有 28×28,通过双线性插值放大回原图尺寸后,边缘非常粗糙。对于 dent 这种边界要靠阴影过渡来判断的缺陷,28×28 的 mask 基本无法给出准确轮廓。UNet 的跳跃连接能把浅层高分辨率特征和深层语义特征融合,在同样的感受野下能输出更精细的边界,这也是它在医学图像分割里被广泛使用的原因。所以在我的落地配置里,Mask R-CNN 更接近一个“区域提议网络”的增强版,UNet 才是真正的像素级分割主力。

2.3 级联 pipeline 的接口与对齐细节

连接两个模型时,最容易出错的是坐标对齐。Mask R-CNN 输出的 box 是原图坐标系,裁剪时如果直接对坐标取整,再 resize 到 UNet 输入尺寸,回贴到原图时会出现几个像素的偏移,这在缺陷检测里可能直接把一个小 dot 从 6 像素变成 4 像素。我一般会在裁剪时把 box 向外扩 margin,同时记录原始矩形的位置和尺寸,而不是只记录一个 resize 后的固定尺寸。

def extract_roi(image, box, margin=16, target_size=(256, 256)): # 原始 box 坐标,来自 Mask R-CNN 输出 x1, y1, x2, y2 = [int(round(v)) for v in box] # 向外扩 margin,避免缺陷正好卡在边界,也让 UNet 看到周围环境 x1, y1 = max(0, x1 - margin), max(0, y1 - margin) x2, y2 = min(image.shape[1], x2 + margin), min(image.shape[0], y2 + margin) roi = image[y1:y2, x1:x2] orig_h, orig_w = roi.shape[:2] # 统一到 256x256,UNet 的输入不需要保持原图宽高比 roi_resized = cv2.resize(roi, target_size, interpolation=cv2.INTER_LINEAR) return roi_resized, (x1, y1, orig_w, orig_h)

这个函数的逻辑是:先把 box 转成整数坐标并扩展边距,然后从原图裁剪。扩展 margin 非常重要,很多 dot 缺陷的边缘就是几个像素,如果不加 margin,裁剪时很可能把缺陷的一部分切出去,UNet 永远学不到完整缺陷。返回的(x1, y1, orig_w, orig_h)是回贴 mask 时必不可少的映射信息,推理阶段要用它把 UNet 输出的概率图 resize 回原始裁剪区域大小,再放到整图 mask 的对应位置。如果两个候选框有重叠,回贴时按像素取最大值,而不是直接覆盖,否则重叠区域的缺陷面积会被低估。

3. 准备像素级标注数据:从标注 JSON 到双模型输入

3.1 bump、dent、dot 的标注规范与类别映射

像素级检测的第一步不是写模型,而是定标注规范。我和标注同学对齐的规则是:bump 以明暗交界的亮边内沿为准,dent 以凹陷阴影的内侧边界为准,dot 只要像素面积大于 3 就标,小于 3 的先记录不计入训练标签。标注工具用 VIA 或 LabelMe 都行,关键是输出必须是多边形,而不是矩形框。芯片表面有大量重复的 die、引脚和字符,这些都要明确排除,尤其是字符边缘的反光,很多人会误标成 dot。

类别映射我建议固定下来:bump=1, dent=2, dot=3,0 保留给背景。这个映射从数据准备阶段就不要改,否则后面 Mask R-CNN 的分类头、UNet 的二值 mask、评估脚本都得跟着动,非常容易出错。另外,标注时我要求每个缺陷必须是独立的多边形,不能把同一片区域里的多个 dot 合并成一个多边形,否则 UNet 学习到的是“一片区域”,而不是“一个个点”,后处理阶段没法按连通域拆分。

3.2 VIA/LabelMe 标注转 COCO:转换脚本与边界坑

Mask R-CNN 标准训练格式是 COCO JSON,UNet 需要的是二值 mask,所以需要把 VIA 导出的 JSON 一次转换成两套数据。下面是我常用的转换脚本:

import json, os, cv2 import numpy as np from pycocotools import mask as mask_util cls_map = {'bump': 1, 'dent': 2, 'dot': 3} def convert_via2_to_coco(via_json, image_dir, out_coco, out_mask_dir): with open(via_json, 'r', encoding='utf-8') as f: data = json.load(f) images, annotations = [], [] ann_id = 1 os.makedirs(out_mask_dir, exist_ok=True) for img_id, (key, meta) in enumerate(data.items(), 1): filename = meta['filename'] img = cv2.imread(os.path.join(image_dir, filename)) if img is None: print(f'skip {filename}, check path') continue h, w = img.shape[:2] images.append({'id': img_id, 'file_name': filename, 'height': h, 'width': w}) regions = meta['regions'] # VIA 2 的单区域导出的 regions 可能是 dict,需要兼容 if isinstance(regions, dict): regions = [regions] for region in regions: shape_attr = region.get('shape_attributes', {}) if shape_attr.get('name') != 'polygon': continue region_attr = region.get('region_attributes', {}) cls_name = region_attr.get('type', region_attr.get('class', 'bump')) if cls_name not in cls_map: print(f'unknown class {cls_name}, skip') continue xs = shape_attr['all_points_x'] ys = shape_attr['all_points_y'] poly = np.stack([xs, ys], axis=1).astype(np.int32) mask_img = np.zeros((h, w), dtype=np.uint8) cv2.fillPoly(mask_img, [poly], 1) # 转 RLE,COCO 里 polygon 坐标可能超出图像边界,RLE 更稳 rle = mask_util.encode(np.asfortranarray(mask_img)) x, y, poly_w, poly_h = cv2.boundingRect(poly) annotations.append({ 'id': ann_id, 'image_id': img_id, 'category_id': cls_map[cls_name], 'segmentation': rle, 'bbox': [x, y, poly_w, poly_h], 'area': int(mask_img.sum()), 'iscrowd': 0 }) # 同步保存 UNet 用的独立二值 mask cv2.imwrite( os.path.join(out_mask_dir, f'{img_id:05d}_{ann_id:05d}.png'), mask_img * 255 ) ann_id += 1 coco = { 'images': images, 'annotations': annotations, 'categories': [ {'id': 1, 'name': 'bump'}, {'id': 2, 'name': 'dent'}, {'id': 3, 'name': 'dot'} ] } with open(out_coco, 'w', encoding='utf-8') as f: json.dump(coco, f)

逻辑说明:脚本把 VIA 的 regions 统一成 list,兼容单区域和多区域导出;对每个多边形生成一张二值 mask,再把 mask 转成 COCO 的 RLE 格式。用 RLE 而不是直接用 polygon,是因为 VIA 的多边形点可能落在图像外,RLE 能天然处理这种越界。annotations里的bbox由cv2.boundingRect计算,不需要自己求 min/max,避免漏掉凹多边形的极端点。同时导出的二值 mask 按image_id + ann_id命名,后面 UNet 训练时可以直接按文件名索引到原图和标注。

3.3 patch 切分与数据增强策略

芯片原图分辨率通常在 2000×2000 以上,直接送进 UNet 训练会把显存吃满,而且小缺陷在这么大一张图上被反复下采样后几乎消失。我的做法是把原图切成 512×512 的 patch,相邻 patch 之间重叠 64 像素,这样缺陷如果正好在切分线上,不至于被切成两半。切出来的 patch 里,包含缺陷的留下,不包含缺陷但包含字符、引脚、走线的也留一部分作为负样本,负样本数量控制在正样本的 1.5 倍以内,太多会让 UNet 偏向背景。

数据增强方面,随机翻转、90 度旋转、亮度对比度调整是基础。对 dot 这种样本量极少的类别,我建议做复制粘贴增强:从现有的 dot 标注中随机裁一小块缺陷,贴到无缺陷 patch 的随机位置,并同步生成对应的 mask。这样相当于人为增加了 dot 的样本数量和位置多样性。注意增强必须同步作用于图像和 mask,不能只转图像不转 mask,否则 Mask R-CNN 训练时目标框和 mask 对不上,UNet 训练时损失函数直接混乱。

4. 训练细节:Mask R-CNN 检测分支和 UNet 分割分支

4.1 用 torchvision 跑通 Mask R-CNN 的最小训练配置

Mask R-CNN 的训练可以直接基于 torchvision 的实现,不需要从零搭网络结构。我们只需要把分类头和 mask 头替换成自己的类别数。背景算一类,加上 bump、dent、dot 三个缺陷类,所以num_classes=4。

import torch from torchvision.models.detection import maskrcnn_resnet50_fpn from torchvision.models.detection.faster_rcnn import FastRCNNPredictor from torchvision.models.detection.mask_rcnn import MaskRCNNPredictor def build_maskrcnn(num_classes=4): model = maskrcnn_resnet50_fpn(weights='COCO_V1') # 替换分类头,原来预测 COCO 80 类,现在只有 4 类 in_features = model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes) # 替换 mask 头,输出通道数也要等于 num_classes in_features_mask = model.roi_heads.mask_predictor.conv5_mask.in_channels model.roi_heads.mask_predictor = MaskRCNNPredictor( in_features_mask, 256, num_classes ) return model

参数说明:weights='COCO_V1'表示加载在 COCO 上预训练好的权重,这个预训练对自然图像的通用特征很有帮助,尤其 FPN 层的参数可以直接复用。如果芯片图像和自然图像差异极大,把pretrained改成False从头训练也可以,但需要更多数据和更长的训练时间,我一般还是选择微调预训练权重。替换 mask 头时,256是中间隐藏层通道数,不需要动,重点是num_classes必须和分类头一致。

这里有一个容易踩的坑:数据加载时必须把masks转成FloatTensor并保持[N, H, W]的形状,torchvision 的 Mask R-CNN 接受target的masks是布尔或浮点型。下面是一个最简训练循环:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = build_maskrcnn().to(device) optimizer = torch.optim.SGD( model.parameters(), lr=1e-4, momentum=0.9, weight_decay=1e-4 ) lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5) def train_one_epoch(dataloader): model.train() for images, targets in dataloader: images = [img.to(device) for img in images] targets = [{k: v.to(device) for k, v in t.items()} for t in targets] loss_dict = model(images, targets) losses = sum(loss for loss in loss_dict.values()) optimizer.zero_grad() losses.backward() optimizer.step()

逻辑说明:torchvision 的 model 在训练模式下接受images和targets,返回的是一个loss_dict,包含 RPN 分类损失、RPN 框回归损失、ROI 分类损失、ROI 框回归损失和 mask 损失。把几个损失直接求和再反传,这是最简单也最稳定的做法。学习率用 1e-4 而不是默认的 1e-2,是因为我们数据量不大,太大的学习率会让预训练权重在早期就被破坏。

4.2 UNet 训练:Dice + BCE 双损失和边界权重

UNet 输入是 Mask R-CNN 框出来的局部图块,输出是单通道概率图。因为缺陷像素在局部图块里占比仍然偏低,只用 BCE 很容易让模型倾向于全背景。我用 Dice Loss 加 BCE 的组合,Dice 对前景占比较小的场景更敏感,BCE 能保持训练初期的稳定性。

import torch import torch.nn as nn class DiceLoss(nn.Module): def __init__(self, smooth=1.0): super().__init__() self.smooth = smooth def forward(self, logits, mask): pred = torch.sigmoid(logits).reshape(logits.size(0), -1) mask = mask.reshape(mask.size(0), -1) inter = (pred * mask).sum(dim=1) union = pred.sum(dim=1) + mask.sum(dim=1) dice = (2 * inter + self.smooth) / (union + self.smooth) return 1 - dice.mean() bce = nn.BCEWithLogitsLoss() def combined_loss(logits, mask): return bce(logits, mask) + DiceLoss().to(logits.device)(logits, mask)

参数说明:smooth取 1.0 是为了防止预测和标注都是空时出现除零错误。reshape(logits.size(0), -1)把每个样本的所有像素展平,这样 Dice 是按样本计算而不是按整个 batch 混合计算,避免某些样本的缺陷像素被另一些样本的背景像素稀释。

训练循环很短:

optimizer = torch.optim.AdamW(unet.parameters(), lr=1e-4) for epoch in range(num_epochs): for patch, mask in unet_loader: patch = patch.float().to(device) mask = mask.float().to(device) logits = unet(patch) loss = combined_loss(logits, mask) optimizer.zero_grad() loss.backward() optimizer.step()

逻辑说明:patch是裁剪并 resize 到 256×256 的图像块,mask是对应的二值 mask,尺寸必须和输入一致。UNet 输出的是原始 logits,没有经过 sigmoid,因为BCEWithLogitsLoss内部会做 sigmoid,DiceLoss 里再单独对 logits 做一次 sigmoid。两个损失相加后反传,UNet 在训练初期不会因为 Dice 的梯度不稳定而完全不动,后期 BCE 又不会让稀疏前景被完全忽略。

4.3 显存、学习率与 epoch:几个更实用的参数建议

下面是几组我实际用下来比较稳的参数,可以直接作为起点:

参数项推荐值说明
Mask R-CNN backboneResNet-50-FPN兼顾速度和精度,芯片缺陷不追求超大模型
训练输入尺寸512×512 patch原图太大时先切 patch,避免小目标被下采样掉
Mask R-CNN 学习率1e-4数据量小,用低学习率微调预训练权重
Mask R-CNN batch size每卡 2超过 2 很容易显存溢出,用梯度累积代替
UNet 输入尺寸256×256来自 ROI 裁剪,不需要更大的尺寸
UNet 学习率1e-4AdamW,配合 Dice+BCE 比较稳
UNet batch size16ROI 小,显存压力小,可以大一点
训练 epoch50 到 100加早停,观察验证集 IoU,不要盲目跑满

显存控制的常见做法是,先离线把 Mask R-CNN 推理得到的 ROI 和对应 mask 保存成文件,再单独训练 UNet。这样两个模型不会同时占显存,也方便调整 UNet 的输入样本。训练 Mask R-CNN 时如果显存不足,我会把 batch size 降到 1,然后用梯度累积accumulation_steps=4模拟 batch size 4,效果比直接撑爆显存好得多。

5. 像素级缺陷检测避坑:训练和部署中常见的 5 个问题

5.1 模型全预测背景:小样本类别的“假收敛”

现象:训练 Loss 在下降,但验证集预测结果全是背景,bump 偶尔能检出,dent 和 dot 完全没有输出。

原因:缺陷像素在全图占比极低,尤其是 dot,一个 6 像素的点在 512×512 patch 里占比不到 0.02%。BCE 或常规分类损失被背景主导,模型发现“全部输出背景”也能把损失降到很低,于是陷入局部最优。Mask R-CNN 的分类头也同样会被大量负样本带偏。

解决:先按类别做样本重采样,保证每个 batch 里都包含 dent 和 dot 样本,不能简单按文件顺序加载。如果 dot 样本量实在不够,用复制粘贴增强生成更多 dot patch。训练时给少数类的损失加权重,比如在分类损失里把 dent 和 dot 的权重设为 3 到 5。我会在训练开始前先统计每个类别的标注数量,低于 500 的类别优先做增强。

5.2 检测框和分割边缘对不上:ROI 对齐误差

现象:UNet 输出的缺陷轮廓比实际边缘偏了 2 到 3 个像素,或者边缘出现明显的台阶状错位,叠加到原图上怎么看怎么别扭。

原因:在 ROI 裁剪时直接对 box 坐标取整,回贴 mask 时又采用不同的插值方式,比如裁剪用INTER_NEAREST,resize 回原图用INTER_LINEAR,两种插值的边缘位移叠加起来,缺陷边界就偏了。另一个常见原因是裁剪 box 没加 margin,缺陷被切到边缘,UNet 是在残缺目标上学习。

解决:所有坐标处理统一用浮点计算,裁剪时不取整,只在切片时通过int截断;回贴 mask 时用cv2.resize的INTER_LINEAR,并且根据原始 ROI 的(x1, y1, orig_w, orig_h)精确映射。每轮训练结束,我习惯随机抽 5 张图把预测 mask 以半透明红色叠加到原图上,用眼睛确认边缘错位情况,这种可视化比只看 IoU 指标直接得多。

5.3 反光、引脚干扰导致的误检

现象:模型把芯片表面的字符、引脚边缘、金属走线的反光当成 bump 或 dot,产线误检率居高不下。

原因:这些区域在局部 ROI 里和缺陷的灰度梯度非常相似,UNet 学到的是“有亮暗边界就是缺陷”。尤其是字符,笔画边缘的反光在二值化后和小型 bump 几乎无法区分。

解决:在标注阶段把所有字符、引脚、走线区域标记为“排除区域”,UNet 损失计算时跳过这些位置的像素,这样模型不会被这些非缺陷边缘反复带偏。训练数据里必须加入大量无缺陷但包含字符和引脚的负样本 patch,让网络知道“看起来像缺陷的未必是缺陷”。推理时,我还习惯用 Mask R-CNN 的置信度分数做第一道过滤,低于 0.7 的候选框不送入 UNet,虽然会漏掉一点真缺陷,但误检率下降更明显。

5.4 训练集涨点,产线翻车

现象:测试集上 mAP 到 0.9,IoU 到 0.85,但同一套权重换到新一批芯片上,误检和漏检同时增加。

原因:芯片表面缺陷检测的数据分布高度依赖采集条件,固定光源、固定角度拍出来的图和产线换了一盏灯、换了一个批次后的图差别很大。随机打乱数据划分验证集时,同一批次不同图片被分到训练和验证里,看起来指标很漂亮,但换个批次就露馅。

解决:按批次而不是按单张图划分验证集,保证验证集里的芯片批次完全没有参与过训练。采集环节要覆盖不同站位、不同光源角度和不同氧化程度的芯片。训练时做光照域随机化,把亮度、对比度、色温的随机范围调大,模型对光照变化的适应会好很多。我现在做项目会特意保留一个“新批次盲测集”,每次训练完先用盲测集跑一遍,再决定要不要调阈值。

5.5 显存溢出与训练时间失控

现象:Mask R-CNN 和 UNet 同时训练,显存很快占满,一个 epoch 要跑几十分钟,训练进度几乎没法推进。

原因:直接把大分辨率原图送进 Mask R-CNN,又把每张图的所有 ROI 放大到 256×256 送进 UNet,两个模型的前向和反向同时占显存,谁也跑不动。

解决:把两个模型拆成分阶段训练。先训练 Mask R-CNN,然后用它离线推理原始训练集,把所有候选框的 ROI 裁剪保存为图片和对应 mask,再单独训练 UNet。UNet 训练时依赖的是一批已经准备好的小图片,显存压力很小,迭代速度也快。推理时如果单张芯片图检测出超过 10 个候选框,我只看置信度最高的 top 10,或者按坐标聚合成几个大区域再送 UNet,避免一次性把所有 ROI 都放进去。

6. 后处理与效果验证:从二值图到像素级缺陷报告

模型输出的概率图不能直接拿去算缺陷面积,必须先做后处理。我会先按类别单独二值化,再做形态学闭运算和小连通域剔除,最后通过连通域分析得到每个缺陷的包围框、像素面积和类别。

import cv2 import numpy as np def postprocess_probmap(prob, class_id=1, thresh=0.5, min_area=12): # prob: HxW 的 float32 概率图 mask = (prob > thresh).astype(np.uint8) # 闭运算把断裂的边缘接起来,dot 不需要这一步,bump/dent 建议加 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 连通域分析,把每个缺陷单独拆出来 num, labels, stats, _ = cv2.connectedComponentsWithStats(mask, 8) results = [] for i in range(1, num): area = stats[i, cv2.CC_STAT_AREA] if area < min_area: continue left, top = stats[i, cv2.CC_STAT_LEFT], stats[i, cv2.CC_STAT_TOP] w, h = stats[i, cv2.CC_STAT_WIDTH], stats[i, cv2.CC_STAT_HEIGHT] results.append({ 'class_id': class_id, 'bbox': [left, top, w, h], 'area_px': int(area), 'score': float(prob[labels == i].max()), }) return results

参数说明:thresh默认 0.5,但 dot 类因为目标太小,预测概率往往整体偏低,我会把它降到 0.3。min_area默认 12 像素,小于这个值的基本是反光噪点或标注差异。闭运算的核大小 3×3 对 bump 和 dent 足够,对 dot 不要做闭运算,因为两个距离近的 dot 可能会被闭运算连成一个缺陷。

验证时不能只看像素 IoU。我会同时统计三类指标:像素级 IoU/Dice 说明分割精度;按连通域计算的召回率和虚警率说明产线实用效果;还有一个边界容差指标,允许预测边界和标注边界偏差 1 到 2 像素内算对,因为芯片缺陷边界本来就有过渡带,不同标注员画出的边界可能差 1 到 2 像素。我最早把 IoU 阈值卡得很死,结果验收时产生很大分歧,后来统一改成带容差的评估口径,才跟产线对齐了标准。最后抽查 100 张原始图,把预测轮廓叠加到图上人工看一眼,这一步永远不能省。

希望这套从标注、训练到后处理的路径能帮你把像素级缺陷检测真正落地。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 5:23:16

花卉识别系统开发实战:深度学习图像分类全流程解析

简介&#xff1a;这是一套基于Python深度学习的花卉识别系统设计源码&#xff0c;面向图像识别开发者、深度学习初学者与毕业设计选题人群&#xff0c;以花卉图像分类为核心&#xff0c;解决从数据预处理、模型训练到推理部署的完整流程问题。压缩包共1048个文件&#xff0c;大…

作者头像 李华
网站建设 2026/10/5 5:22:56

易拉罐缺陷检测实战:基于YOLOv8的数据集与训练全流程解析

简介&#xff1a;面向易拉罐外观质检场景的缺陷识别数据集&#xff0c;原始图像集中于罐身划痕、罐底压痕等常见瑕疵&#xff0c;配套YOLOv8格式标注&#xff0c;可直接用于目标检测模型的训练、验证与评估。包内共1709个文件&#xff0c;包含854张JPG原图、854个对应的TXT标注…

作者头像 李华
网站建设 2026/10/5 5:22:51

MiMo-V2.6 技术拆解:强化学习规模化与自我改进的工程实践

1. 从"能对话"到"会进化"&#xff1a;MiMo-V2.6 到底在解决什么真问题大模型这两年卷得厉害&#xff0c;但如果你真在一线做训练或调优&#xff0c;会发现一个尴尬的现实&#xff1a;绝大多数开源模型的迭代路径&#xff0c;本质上还是"堆数据、堆算力…

作者头像 李华
网站建设 2026/10/5 5:22:14

实测8大AI引擎:个人网站如何被AI引用?GEO优化指南

1. 当AI开始“挑食”&#xff1a;为什么你的个人网站不被引用先抛一个我实测下来的反直觉结论&#xff1a;你的个人网站不被AI引用&#xff0c;大概率不是因为内容质量差&#xff0c;而是因为AI根本“读不懂”你的网站结构。过去大半年&#xff0c;我一直在折腾一个事情——把自…

作者头像 李华