DAGM 2007 纹理缺陷数据集:弱监督检测的 3 种主流方法对比
2026/7/8 23:07:54 网站建设 项目流程

DAGM 2007纹理缺陷数据集:弱监督检测的3种主流方法深度实践

在工业质检领域,纹理背景上的微小缺陷检测一直是个棘手问题。当只能获得图像级标签(如椭圆形标注框)而非精确的像素级标注时,传统监督学习方法往往捉襟见肘。DAGM 2007作为经典的弱监督工业光学检测数据集,为我们研究这一难题提供了理想平台。本文将带您深入实践三种主流的弱监督检测方法,从数据加载到模型实现,全面解析技术细节与实战技巧。

1. DAGM 2007数据集特性与预处理

DAGM 2007包含10个独立子集,前6个用于训练,后4个用于测试。每个子集由不同的计算机生成纹理和缺陷模型构成,确保了数据多样性。具体来说,每个子集包含:

  • 1000张无缺陷的8位灰度PNG图像
  • 150张带有单一缺陷的图像
  • 弱监督标签(椭圆形标注框)
import os import cv2 import numpy as np from glob import glob def load_dagm_dataset(base_path, class_idx): """ 加载指定类别的DAGM数据集 :param base_path: 数据集根路径 :param class_idx: 类别编号(1-10) :return: (images, masks) 元组 """ pattern = os.path.join(base_path, f'Class{class_idx}', '*.png') image_paths = sorted(glob(pattern)) images = [] masks = [] for img_path in image_paths: # 读取灰度图像并归一化 img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) img = img.astype(np.float32) / 255.0 # 生成粗略mask(模拟弱监督场景) if 'def' in os.path.basename(img_path): # 从文件名解析椭圆参数 params = os.path.splitext(img_path)[0].split('_')[-4:] cx, cy, rx, ry = map(float, params) # 创建椭圆mask mask = np.zeros_like(img) cv2.ellipse(mask, (int(cx), int(cy)), (int(rx), int(ry)), 0, 0, 360, 1, -1) masks.append(mask) else: masks.append(np.zeros_like(img)) images.append(img) return np.array(images), np.array(masks)

数据增强策略对弱监督学习尤为重要。我们推荐以下增强组合:

  1. 几何变换:随机旋转(0-360度)、翻转、缩放(0.8-1.2倍)
  2. 光度变换:亮度调整(±20%)、对比度调整(0.8-1.2倍)
  3. 弹性变换:模拟材料表面微小形变
  4. 添加高斯噪声:SNR保持在30dB以上

注意:所有增强操作应同步应用于图像和对应的mask,确保空间一致性。对于椭圆形标注,需要重新计算变换后的椭圆参数。

2. 基于分类激活图(CAM)的方法

分类激活图(CAM)及其变体(Grad-CAM)是弱监督定位的经典方法。其核心思想是利用分类网络的注意力机制来推断缺陷位置。

2.1 模型架构与实现

我们采用ResNet50作为基础网络,替换最后的全连接层以适应二分类任务:

import torch import torch.nn as nn from torchvision.models import resnet50 class CAMWrapper(nn.Module): def __init__(self, num_classes=2): super().__init__() self.base_model = resnet50(pretrained=True) self.features = nn.Sequential(*list(self.base_model.children())[:-2]) # 替换最后的全连接层 in_features = self.base_model.fc.in_features self.gap = nn.AdaptiveAvgPool2d(1) self.classifier = nn.Linear(in_features, num_classes) def forward(self, x): features = self.features(x) # [B, 2048, H/32, W/32] pooled = self.gap(features) # [B, 2048, 1, 1] pooled = pooled.flatten(1) # [B, 2048] logits = self.classifier(pooled) # 计算CAM weights = self.classifier.weight # [num_classes, 2048] cams = torch.einsum('bk,bchw->bchw', weights[1:], features) # 只取缺陷类的权重 return logits, cams

2.2 训练技巧与损失函数

在弱监督场景下,我们需要设计特殊的损失函数:

def cam_loss(logits, cams, targets, lambda_=0.5): # 标准交叉熵损失 ce_loss = nn.CrossEntropyLoss()(logits, targets) # 注意力正则化损失 batch_size = cams.shape[0] norm_loss = torch.mean(torch.abs(cams.view(batch_size, -1).sum(dim=1) - 1)) return ce_loss + lambda_ * norm_loss

关键训练参数

  • 初始学习率:1e-4(使用Cosine退火)
  • 批量大小:32
  • 优化器:AdamW
  • 训练周期:50

2.3 后处理与结果分析

原始CAM分辨率较低(通常为输入尺寸的1/32),我们需要进行上采样和阈值处理:

def postprocess_cam(cam, img_size=(512, 512), threshold=0.5): # 双线性上采样到原图尺寸 cam = F.interpolate(cam.unsqueeze(0), size=img_size, mode='bilinear', align_corners=False) # 归一化到[0,1]范围 cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8) # 阈值化生成二值mask binary_mask = (cam > threshold).float() return binary_mask.squeeze()

在DAGM测试集上的评估结果:

类别准确率召回率F1分数IoU
10.920.850.880.78
20.890.820.850.74
...............
平均0.900.830.860.76

3. 伪标签生成方法

伪标签方法通过迭代优化逐步改进定位精度,是弱监督学习的另一主流范式。

3.1 迭代训练流程

graph TD A[初始模型训练] --> B[生成伪标签] B --> C[用伪标签训练新模型] C --> D{达到停止条件?} D -- 否 --> B D -- 是 --> E[最终模型]

3.2 伪标签生成策略

我们采用基于置信度的自适应阈值方法:

def generate_pseudo_labels(model, dataloader, confidence_thresh=0.7): model.eval() pseudo_labels = [] with torch.no_grad(): for images, _ in dataloader: images = images.to(device) _, cams = model(images) # 上采样CAM cams = F.interpolate(cams, size=images.shape[2:], mode='bilinear', align_corners=False) # 计算每个位置的置信度 probs = torch.sigmoid(cams) # 生成伪标签 for prob in probs: # 自适应阈值 dynamic_thresh = confidence_thresh * prob.max() pseudo_label = (prob > dynamic_thresh).float() pseudo_labels.append(pseudo_label.cpu()) return pseudo_labels

3.3 多模型集成策略

为提高伪标签质量,我们可以集成多个模型的预测结果:

from ensemble_boxes import weighted_boxes_fusion def ensemble_predictions(models, image, size=(512, 512)): all_cams = [] for model in models: with torch.no_grad(): _, cam = model(image.unsqueeze(0).to(device)) cam = F.interpolate(cam, size=size, mode='bilinear', align_corners=False) all_cams.append(cam.squeeze().cpu().numpy()) # 使用WBF(Weighted Boxes Fusion)算法融合 boxes, scores = [], [] for cam in all_cams: # 从CAM生成候选框 boxes.append(extract_boxes_from_cam(cam)) scores.append(calculate_box_scores(cam)) fused_boxes, fused_scores = weighted_boxes_fusion(boxes, scores) return generate_mask_from_boxes(fused_boxes, size)

4. 基于PatchCore的异常检测方法

PatchCore是近年提出的高性能异常检测方法,特别适合纹理缺陷检测场景。

4.1 核心算法流程

  1. 特征提取:使用预训练CNN提取多尺度局部特征
  2. 记忆库构建:收集正常样本的特征作为参考
  3. 异常检测:通过最近邻搜索计算异常分数
import faiss from sklearn.random_projection import SparseRandomProjection class PatchCore: def __init__(self, backbone='wide_resnet50_2'): self.backbone = torch.hub.load('pytorch/vision', backbone, pretrained=True) self.backbone.eval() self.projector = SparseRandomProjection(n_components=256) # 多尺度特征hook self.features = {} def get_features(name): def hook(model, input, output): self.features[name] = output.detach() return hook self.backbone.layer2.register_forward_hook(get_features('layer2')) self.backbone.layer3.register_forward_hook(get_features('layer3')) def build_memory_bank(self, dataloader): """构建正常样本的记忆库""" all_features = [] for images, _ in dataloader: with torch.no_grad(): _ = self.backbone(images) # 多尺度特征拼接 feat1 = F.adaptive_avg_pool2d(self.features['layer2'], (1,1)) feat2 = F.adaptive_avg_pool2d(self.features['layer3'], (1,1)) features = torch.cat([feat1, feat2], dim=1).squeeze() # 随机投影降维 features = self.projector.fit_transform(features.cpu().numpy()) all_features.append(features) # 构建FAISS索引 self.memory_bank = np.concatenate(all_features) self.index = faiss.IndexFlatL2(self.memory_bank.shape[1]) self.index.add(self.memory_bank) def detect_anomaly(self, image): """检测单张图像的异常区域""" with torch.no_grad(): _ = self.backbone(image.unsqueeze(0)) # 提取局部特征 feat1 = self.features['layer2'] # [1, 512, H/8, W/8] feat2 = self.features['layer3'] # [1, 1024, H/16, W/16] feat2 = F.interpolate(feat2, size=feat1.shape[2:], mode='bilinear', align_corners=False) local_features = torch.cat([feat1, feat2], dim=1) # [1, 1536, H/8, W/8] # 处理局部特征 h, w = local_features.shape[2:] local_features = local_features.squeeze().permute(1,2,0) # [H,W,C] local_features = self.projector.transform( local_features.reshape(-1, 1536).cpu().numpy()) # 最近邻搜索 D, _ = self.index.search(local_features, k=1) anomaly_map = D.reshape(h, w) return anomaly_map

4.2 参数优化建议

  • 特征选择:中间层特征通常比最后一层更有效
  • 记忆库大小:通常保留5,000-10,000个最具代表性的特征
  • 邻域大小:k=1通常足够,增大k可提高鲁棒性但降低灵敏度
  • 投影维度:256-512维在精度和效率间取得良好平衡

4.3 性能对比

三种方法在DAGM测试集上的表现对比:

方法平均F1推理速度(fps)内存占用(MB)需要像素标注
CAM0.86451200
伪标签0.89321500
PatchCore0.92282500

5. 工程实践与优化建议

在实际工业部署中,我们还需要考虑以下关键因素:

计算效率优化

  • 使用TensorRT加速推理
  • 量化模型到INT8精度
  • 实现异步处理流水线
// 示例:使用TensorRT部署的伪代码 auto engine = loadTRTEngine("model.engine"); auto buffer = createGPUBuffer(); auto context = engine->createExecutionContext(); cudaMemcpy(buffer.input, image.data, image.size, cudaMemcpyHostToDevice); context->enqueueV2(buffer.data, stream, nullptr); cudaMemcpy(output.data, buffer.output, output.size, cudaMemcpyDeviceToHost);

领域自适应技巧

  1. 测试时增强(TTA):对输入图像进行多次变换并集成结果
  2. 在线难例挖掘:重点关注分类不确定的样本
  3. 记忆回放:定期用历史数据微调模型

实际部署中的经验

  • 工业相机采集的图像通常需要额外的平场校正
  • 对于高速产线,可降低输入分辨率至256x256
  • 采用"检测+分类"的两阶段流程可提高小缺陷检出率
  • 定期用新数据更新模型(建议每周或每月)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询