如何使用深度学习进行缺陷检测
共 6112字,需浏览 13分钟
·
2024-05-20 11:30
机器学习自动检测异常已成为一个有着高度直接影响的有趣且具有潜力的研究领域,特别是在视觉检测领域。深度学习技术已成为此任务的最佳方法。深度学习技术可以通过训练图像数据集来提供一个可以检测表面异常的模型。
DAGM数据集中不同背景纹理上的表面缺陷
介绍
在一些工业中,通过SEM/EDX检查表面或检查材料中的杂质是质量控制的一步。通常,这个检查过程需要质量人员手动检查表面。
这需要训练QC检查员识别整个复杂缺陷范围。这是耗时、低效的,可能会导致生产等待时间,甚至偶尔错误分类缺陷,导致客户投诉或领域故障,从而导致产品召回。过去,传统的图像处理方法足以解决这些问题(Paniagua等人,2010;Bulnes等人,2016)。然而,工业4.0范例倾向于泛化生产线,需要快速适应新产品(Oztemel和Gursev,2018)。在本文中,我们探讨了基于2D卷积神经网络的U-Net架构来检测缺陷。
U-Net
UNET是由Olaf Ronneberger等人为生物医学图像分割开发的。该架构包含两条路径。第一条路径是收缩路径(也称为编码器),用于捕获图像的上下文。编码器只是传统的卷积和最大池化层堆栈。第二条路径是对称扩展路径(也称为解码器),用于使用转置卷积进行精确的定位。因此,它是一个端到端的,完全卷积的网络(FCN),即它只包含卷积层,不包含任何稠密层,因此它可以接受任何大小的图像。
在原始论文中,UNET描述如下:
数据集
给大家推荐一个缺陷检测的数据集,具体网址如下:https://conferences.mpi-inf.mpg.de/dagm/2007/prizes.html
这个数据集是人工生成的,但类似于真实世界的问题。它包含多个数据集,每个数据集包含1000个图像,显示没有缺陷的背景纹理和150个带有一个标记缺陷的背景纹理图像。单个数据集中的图像非常相似,但每个数据集是由不同的纹理模型和缺陷模型生成的。
并不是所有纹理偏差都是缺陷。算法将需要在训练阶段使用提供的弱标签来学习表征缺陷的属性。
Python库:
matplotlib
xmltodict
sklearn
tensorflow
scipy
克隆代码库
将代码库克隆到本地文件夹:
git clone https://github.com/AdarshGouda/Surface-Defect-Detection.git
cd Surface-Defect-Detection
helper函数将有助于定位缺陷并使用位于./utils文件夹中的椭圆遮罩它。此文件夹及其内容将根据需要在以下代码中导入。
下载数据集并解压缩:
wget https://resources.mpi-inf.mpg.de/conference/dagm/2007/Class1_def.zip
根据我们的网络连接性,数据集需要几分钟时间才能下载。
unzip -q Class1_def.zip -d .
让我们来看看./Class1_def文件夹中的图像。
请注意第一个图像1.png左上角的缺陷。./utils文件夹中的辅助函数将帮助定位图像中的这些缺陷并创建相应的掩模作为标签。
Surface-Defect-Detection.ipynb文件中的以下代码块将对您想要测试的任何图像绘制分割标签。在这里,我已经测试了第一个图像1.png。
DataIO.py脚本中的load_images_masks()函数从Class1_def文件夹中获取原始图像文件,并返回图像及其分割标签。
如上所示,共有150个大小为512 x 512且通道为1的图像(灰度图像而非RGB)。
接下来,让我们查看前两个数据点的X和y。
如上所示,分割标签正确地识别了原始图像中缺陷的位置。
训练测试的划分
定义一个简化版的U-net,以简化计算和训练。
损失函数和平滑的Dice系数:
对于图像分割任务,一个常见的损失函数是基于Dice系数的,它本质上是两个样本之间重叠的度量。这个度量的范围是从0到1,其中Dice系数为1表示完美且完全重叠。Dice系数最初是为二进制数据开发的,可以计算为:
其中|A∩B|表示集合A和B之间的公共元素,|A||A|表示集合A中元素的数量(对于集合B同样如此)。
对于预测分割掩码评估Dice系数的情况,我们可以将|A∩B||A∩B|近似为预测掩码和目标掩码的逐元素乘积,然后对结果矩阵求和。
因为我们的目标掩码是二进制的,所以我们有效地将不在目标掩码中“激活”的任何像素都置为零。对于剩余的像素,我们实际上是惩罚低置信度的预测;这个式子的更高值,也就是分子中的部分,会导致更好的Dice系数。
为了量化|A|和|B|,一些研究人员使用简单的求和,而其他研究人员更喜欢使用平方和来计算。我没有实践经验,不知道在各种任务中哪种方法表现更好,所以我让你们尝试两种方法,看哪种效果更好。
如果你好奇,那么在计算Dice系数的分子中有一个2,是因为我们的分母“双重计算”了两个集合之间的公共元素。为了制定可以最小化的损失函数,我们将使用1−Dice。这个损失函数被称为平滑的Dice损失,因为我们直接使用了预测概率,而不是将它们阈值化并将它们转换为二进制掩码。
关于神经网络输出,分子关注于我们的预测和目标掩码之间的公共激活,而分母关注于每个掩码中的激活数量。这就有了一个根据目标掩码的大小来归一化损失的效果,以便平滑的Dice损失不会在图像中具有较少空间表示的类中学习。
让我们定义smooth_dice_coeff()函数来计算损失并编译模型:
训练
在接下来的部分,我们将看到作者在这个项目中的训练结果和测试效果。作者选择了批量大小为10和60个epochs。批量大小为10有助于在RTX3070(笔记本电脑)上运行训练。学习曲线表现不错,没有欠拟合或过拟合的迹象,这是一个好兆头。
测试
接下来我们使用 predict_evaulation() 函数来检查测试集上的结果。
结论
这个项目更多是一个概念的证明,训练图像是人工生成的。在现实世界中,从相机或数字显微镜获得的图像可能具有不同的对比度或亮度值,这可能会使缺陷检测变得困难。在训练过程中使用数据增强技术可能有助于为真实的工业应用程序准备训练模型。在这篇文章中,结果比作者预想的要好。
参考文献:
U-Net: 用于生物医学图像分割的卷积网络 https://arxiv.org/abs/1505.04597
Tabernik,D.,Šela,S.,Skvarč,J.等。基于分割的深度学习方法用于表面缺陷检测。J Intell Manuf 31,759-776(2020)。https://doi.org/10.1007/s10845-019-01476-x
NVIDIA端到端深度学习平台
The One Hundred Layers Tiramisu:全卷积密集网络用于语义分割 https://arxiv.org/abs/1611.09326
智能制造中的边缘人工智能:缺陷检测及其它应用
下面附上UNet网络的代码
def conv2d_block(input_tensor, n_filters, kernel_size = 3, batchnorm = True):
to add 2 convolutional layers with the parameters passed to it"""
# first layer
x = Conv2D(filters = n_filters, kernel_size = (kernel_size, kernel_size),\
kernel_initializer = 'he_normal', padding = 'same')(input_tensor)
if batchnorm:
x = BatchNormalization()(x)
x = Activation('relu')(x)
# second layer
x = Conv2D(filters = n_filters, kernel_size = (kernel_size, kernel_size),\
kernel_initializer = 'he_normal', padding = 'same')(input_tensor)
if batchnorm:
x = BatchNormalization()(x)
x = Activation('relu')(x)
return x
def get_unet(input_img, n_filters = 16, dropout = 0.1, batchnorm = True):
# Contracting Path
c1 = conv2d_block(input_img, n_filters * 1, kernel_size = 3, batchnorm = batchnorm)
p1 = MaxPooling2D((2, 2))(c1)
p1 = Dropout(dropout)(p1)
c2 = conv2d_block(p1, n_filters * 2, kernel_size = 3, batchnorm = batchnorm)
p2 = MaxPooling2D((2, 2))(c2)
p2 = Dropout(dropout)(p2)
c3 = conv2d_block(p2, n_filters * 4, kernel_size = 3, batchnorm = batchnorm)
p3 = MaxPooling2D((2, 2))(c3)
p3 = Dropout(dropout)(p3)
c4 = conv2d_block(p3, n_filters * 8, kernel_size = 3, batchnorm = batchnorm)
p4 = MaxPooling2D((2, 2))(c4)
p4 = Dropout(dropout)(p4)
c5 = conv2d_block(p4, n_filters = n_filters * 16, kernel_size = 3, batchnorm = batchnorm)
# Expansive Path
u6 = Conv2DTranspose(n_filters * 8, (3, 3), strides = (2, 2), padding = 'same')(c5)
u6 = concatenate([u6, c4])
u6 = Dropout(dropout)(u6)
c6 = conv2d_block(u6, n_filters * 8, kernel_size = 3, batchnorm = batchnorm)
u7 = Conv2DTranspose(n_filters * 4, (3, 3), strides = (2, 2), padding = 'same')(c6)
u7 = concatenate([u7, c3])
u7 = Dropout(dropout)(u7)
c7 = conv2d_block(u7, n_filters * 4, kernel_size = 3, batchnorm = batchnorm)
u8 = Conv2DTranspose(n_filters * 2, (3, 3), strides = (2, 2), padding = 'same')(c7)
u8 = concatenate([u8, c2])
u8 = Dropout(dropout)(u8)
c8 = conv2d_block(u8, n_filters * 2, kernel_size = 3, batchnorm = batchnorm)
u9 = Conv2DTranspose(n_filters * 1, (3, 3), strides = (2, 2), padding = 'same')(c8)
u9 = concatenate([u9, c1])
u9 = Dropout(dropout)(u9)
c9 = conv2d_block(u9, n_filters * 1, kernel_size = 3, batchnorm = batchnorm)
outputs = Conv2D(1, (1, 1), activation='sigmoid')(c9)
model = Model(inputs=[input_img], outputs=[outputs])
return model