一、简介
缺陷检测广泛应用于布料缺陷检测、工件表面质量检测、航空航天等领域。传统算法对于规则缺陷和场景相对简单的情况可以很好地工作,但不再适合特征不清晰、形状多样、场景混乱的情况。近年来,基于深度学习的识别算法越来越成熟,不少企业开始尝试将深度学习算法应用到工业场景中。
2. 缺陷数据
这里我们以布料数据为例。常见的缺陷有三种:磨损、白点、多纹。
如何创建训练数据?这里,截取原始图像,得到小图像。比如上面的图片是512x512,这里我将其裁剪成64x64的小图。以第一类缺陷为例,下面是产生数据的方法。
注意:制作缺陷数据时,缺陷区域必须至少占截取图像的2/3,否则将被丢弃,不能用作缺陷图像。
一般来说,缺陷数据比背景数据少得多。最后通过增强数据,defect:background=1:1,每个类别大约1000块~~~
3. 网络结构
具体使用的网络结构如下。输入尺寸为64x64x3,使用截取的小图像的尺寸。每个Conv卷积层后面跟着一个BN层。具体层参数如下。
转换1:64x3x3
Conv2:128x3x3 ResNetBlock 和DenseNetBlock 各两个。具体细节请参考Residual Network和DenseNet。 Add:将residual模块输出的结果和DenseNetBlock输出的结果在对应的feature map上相加。加法方法与残差模块相同。
请注意,这实际上是为了更好的特征提取。方法不一定是residual模块+DenseNetBlock,也可以是inception,或者其他方法。
Conv3: 128x3x3 Maxpool: stride=2, size=2x2 FC1: 4096 Dropout1: 0.5 FC2: 1024 Dropout1: 0.5 Softmax: 对应要划分的类别,这里我是第二类。
关于最终的损失函数,建议选择Focal Loss,这是何凯明的代表作。源码如下:
def focus_loss(y_true, y_pred): pt_1=tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred)) return -K.sum(K.pow(1. - pt_1, 2) * K.log(pt_1))
数据准备好之后就可以开始训练了~~~
4.整个场景图像的缺陷检测
上面训练的网络的输入是64x64x3,但是整个场景图像是512x512。此输入与模型的输入不匹配。我应该怎么办?事实上,你可以提取训练好的模型参数并将其分配给另一个新模型,然后将新模型的输入更改为512x512。然而,在conv3+maxpool层提取的特征图比较大。当将特征图映射到原始图像时,例如在原始模型的最后一个maxpool 层之后,输出特征图大小为8x8x128,其中128 是通道数。如果输入改为512x512,则输出特征图将变为64x64x128。这里的每个8x8 对应于原始图像上的64x64。这样就可以使用8x8的滑动窗口在64x64x128的特征图上进行滑动裁剪特征。然后对裁剪后的特征进行增肥,并将其发送到全连接层。详细信息如下图所示。
全连接层还需要重新建立模型,输入是flatten后的输入,输出是softmax层的输出。这是一个简单的小模型。
这是将训练后的模型参数读取到另一个模型中的代码。
#用于特征提取的大模型def read_big_model(inputs): #第一个卷积层和最大池化层X=Conv2D(16, (3, 3), name='conv2d_1')(inputs) X=BatchNormalization(name='batch_normalization_1' )(X) X=激活('relu', name='activation_1')(X) (X) # google_inception 模块conv_1=Conv2D(32, (1, 1), padding='same', name='conv2d_2' )(X) conv_1=BatchNormalization(name='batch_normalization_2')(conv_1) conv_1=激活( 'relu', name='activation_2')(conv_1) conv_2=Conv2D(32, (3, 3), padding='same ', name='conv2d_3')(X) conv_2=BatchNormalization(name='batch_normalization_3') (conv_2) conv_2=激活('relu', name='activation_3')(conv_2) conv_3=Conv2D(32, (5, 5), padding='相同', name='conv2d_4')(X) conv_3=BatchNormalization (name='batch_normalization_4')(conv_3) conv_3=激活('relu', name='activation_4')(conv_3) pooling_1=MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='相同', name='max_pooling2d_2')(X) X=合并([conv_1, conv_2, conv_3, pooling_1], mode=' concat', name='merge_1') strides=(2, 2), name='max_pooling2d_3')(X) # 这里的大小变成16x16x112 X=Conv2D(64, (3, 3), kernel_regularizer=regularizers.l2( 0.01), 填充='相同', 名称='conv2d_5')(X) X=BatchNormalization(name='batch_normalization_5')(X) X=激活('relu', 名称='activation_5')(X) ), strides=(2, 2), name='max_pooling2d_4')(X) # 这里的大小变成8x8x64 X=Conv2D(128, (3, 3), padding='same', name='conv2d_6')( X) X=BatchNormalization(name='batch_normalization_6')(X) X=Activation('relu', name='activation_6')(X) ), padding='same', name='max_pooling2d_5')(X) # 大小这里变成4x4x128 return Xdef read_big_model_classify(inputs_sec): X_=Flatten(name='flatten_1')(inputs_sec) X_=Dense(256,activation='relu', name='dense_1')(X_) X_=Dropout(0.5, name='dropout_1')(X_)predictions=Dense(2,activation='softmax',name='dense_2')(X_)returnpredictions#建立的小模型inputs=Input(shape=(512,512,3))X=read_big_model(inputs)#读取训练好的模型的网络参数#构建第一个模型model=Model(inputs=inputs,outputs=X )model.load_weights('model_halcon.h5', by_name=True)
5. 识别定位结果
上述滑动窗口方法可以对原始图像进行定位。 8x8 滑动窗口定位到64x64 的原始图像。同样,在原始图像中,滑动窗口方法也不同(这里左右和上下步长选择为16个像素)。定位的缺陷位置也不止一处,这涉及到定位精度。这里投票的方法其实就是统计原图像上每个标记的像素位置。当数量大于指定阈值时,判断为缺陷像素。
识别结果如下图所示:
6. 一些技巧
对于上面的情况,其实64x64大小的定位框是不够准确的。可以考虑训练一个32x32尺寸的模型,然后以与64x64模型相同的方式应用。最后根据32x32定位位置和64x64定位位置进行投票,但是这样会涉及到一个问题,就是时间会增加很多,所以要谨慎使用。
当背景和前景相差不大的时候,尽量不要让网络太深,因为太深的网络后面基本会学到同样的东西,不会有很好的区分能力。这就是为什么我在这里不使用对象检测。原因是这些检测模型网络的深度往往是50+,但效果并不好,虽然有残差模块作为主干。
但当背景和前景差异很大时,可以选择更深的网络。这时,物体检测方法就派上用场了。
审稿人:刘庆