如何使用深度学习进行缺陷检测

点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达983d8d67b19517eece234e48ec2e5069.jpeg

机器学习自动检测异常已成为一个有着高度直接影响的有趣且具有潜力的研究领域,特别是在视觉检测领域。深度学习技术已成为此任务的最佳方法。深度学习技术可以通过训练图像数据集来提供一个可以检测表面异常的模型。

90d00a79c2603c9de2dc6749168d68b1.png

DAGM数据集中不同背景纹理上的表面缺陷

介绍

在一些工业中,通过SEM/EDX检查表面或检查材料中的杂质是质量控制的一步。通常,这个检查过程需要质量人员手动检查表面。

c6112f9697ac67c7668215e9ef3b7e5b.png

这需要训练QC检查员识别整个复杂缺陷范围。这是耗时、低效的,可能会导致生产等待时间,甚至偶尔错误分类缺陷,导致客户投诉或领域故障,从而导致产品召回。过去,传统的图像处理方法足以解决这些问题(Paniagua等人,2010;Bulnes等人,2016)。然而,工业4.0范例倾向于泛化生产线,需要快速适应新产品(Oztemel和Gursev,2018)。在本文中,我们探讨了基于2D卷积神经网络的U-Net架构来检测缺陷。

U-Net

UNET是由Olaf Ronneberger等人为生物医学图像分割开发的。该架构包含两条路径。第一条路径是收缩路径(也称为编码器),用于捕获图像的上下文。编码器只是传统的卷积和最大池化层堆栈。第二条路径是对称扩展路径(也称为解码器),用于使用转置卷积进行精确的定位。因此,它是一个端到端的,完全卷积的网络(FCN),即它只包含卷积层,不包含任何稠密层,因此它可以接受任何大小的图像。

在原始论文中,UNET描述如下:

808a09af06dc1fd9a83c834d222871c5.png

数据集

给大家推荐一个缺陷检测的数据集,具体网址如下: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文件夹中的图像。

33b6cf1dde8028bb554c69e87162079c.png

c56d52f2f872cc8315d1842a7f0081d9.png

请注意第一个图像1.png左上角的缺陷。./utils文件夹中的辅助函数将帮助定位图像中的这些缺陷并创建相应的掩模作为标签。

Surface-Defect-Detection.ipynb文件中的以下代码块将对您想要测试的任何图像绘制分割标签。在这里,我已经测试了第一个图像1.png。

1b263155470a385ca3acee9082846f42.png

DataIO.py脚本中的load_images_masks()函数从Class1_def文件夹中获取原始图像文件,并返回图像及其分割标签。

81197437605c89af9758576dc8f5ad76.png

如上所示,共有150个大小为512 x 512且通道为1的图像(灰度图像而非RGB)。

接下来,让我们查看前两个数据点的X和y。

737a593423cbbd9f93828c21c0bfadc5.png

如上所示,分割标签正确地识别了原始图像中缺陷的位置。

训练测试的划分

f3f0be0b3ccd56d669eb934f38dd09af.png

定义一个简化版的U-net,以简化计算和训练。

84e343a5fe68598754e4ee57f96ea035.png

e6c5e080e11d682c97ccf0c283f58454.png

损失函数和平滑的Dice系数:

对于图像分割任务,一个常见的损失函数是基于Dice系数的,它本质上是两个样本之间重叠的度量。这个度量的范围是从0到1,其中Dice系数为1表示完美且完全重叠。Dice系数最初是为二进制数据开发的,可以计算为:

93a5ed318d8f9a8c26e5e5263134deea.png

其中|A∩B|表示集合A和B之间的公共元素,|A||A|表示集合A中元素的数量(对于集合B同样如此)。

对于预测分割掩码评估Dice系数的情况,我们可以将|A∩B||A∩B|近似为预测掩码和目标掩码的逐元素乘积,然后对结果矩阵求和。

badee0dd618f9aac040b70033c5ec9f2.png

因为我们的目标掩码是二进制的,所以我们有效地将不在目标掩码中“激活”的任何像素都置为零。对于剩余的像素,我们实际上是惩罚低置信度的预测;这个式子的更高值,也就是分子中的部分,会导致更好的Dice系数。

为了量化|A|和|B|,一些研究人员使用简单的求和,而其他研究人员更喜欢使用平方和来计算。我没有实践经验,不知道在各种任务中哪种方法表现更好,所以我让你们尝试两种方法,看哪种效果更好。

7b32dbfff8f67f33dcfe1d7978d3ef35.png

如果你好奇,那么在计算Dice系数的分子中有一个2,是因为我们的分母“双重计算”了两个集合之间的公共元素。为了制定可以最小化的损失函数,我们将使用1−Dice。这个损失函数被称为平滑的Dice损失,因为我们直接使用了预测概率,而不是将它们阈值化并将它们转换为二进制掩码。

关于神经网络输出,分子关注于我们的预测和目标掩码之间的公共激活,而分母关注于每个掩码中的激活数量。这就有了一个根据目标掩码的大小来归一化损失的效果,以便平滑的Dice损失不会在图像中具有较少空间表示的类中学习。

6ff44496df930dbef39e97b45cd917ac.png

让我们定义smooth_dice_coeff()函数来计算损失并编译模型:

cd5adb14713fd0d1ccfc0d9e33e4fc81.png

训练

在接下来的部分,我们将看到作者在这个项目中的训练结果和测试效果。作者选择了批量大小为10和60个epochs。批量大小为10有助于在RTX3070(笔记本电脑)上运行训练。学习曲线表现不错,没有欠拟合或过拟合的迹象,这是一个好兆头。

5849da81ad4c88f0b5376b8263679e85.png

f644da775126c70cf3b1baa3bbe52810.png

5d2ad5d7536c0987a8ed17eb0ad640ac.png

测试

accbf0e323ebbdc0f471151c8a22fee6.png

接下来我们使用 predict_evaulation() 函数来检查测试集上的结果。

a92456f6dbe70b5746c56cad9a151aeb.png

结论

这个项目更多是一个概念的证明,训练图像是人工生成的。在现实世界中,从相机或数字显微镜获得的图像可能具有不同的对比度或亮度值,这可能会使缺陷检测变得困难。在训练过程中使用数据增强技术可能有助于为真实的工业应用程序准备训练模型。在这篇文章中,结果比作者预想的要好。

参考文献:

  1. U-Net: 用于生物医学图像分割的卷积网络 https://arxiv.org/abs/1505.04597

  2. Tabernik,D.,Šela,S.,Skvarč,J.等。基于分割的深度学习方法用于表面缺陷检测。J Intell Manuf 31,759-776(2020)。https://doi.org/10.1007/s10845-019-01476-x

  3. NVIDIA端到端深度学习平台

  4. The One Hundred Layers Tiramisu:全卷积密集网络用于语义分割 https://arxiv.org/abs/1611.09326

  5. 智能制造中的边缘人工智能:缺陷检测及其它应用

下面附上UNet网络的代码

def conv2d_block(input_tensor, n_filters, kernel_size = 3, batchnorm = True):
    """Function 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

好消息!

小白学视觉知识星球

开始面向外开放啦👇👇👇

 
 

d39c5450c8abbee5f97ea28ce4b07a4d.jpeg

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。


下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。


下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。


交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~
  • 0
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白学视觉

您的赞赏是我们坚持下去的动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值