論文題目: Segmentation-Based Deep-Learning Approach for Surface-Defect
文獻地址:https://arxiv.org/abs/1903.08536v3
源碼地址:https://github.com/Wslsdx/Deep-Learning-Approach-for-Surface-Defect-Detection
最近接到表面缺陷檢測的需求,在Github上查找Defect Detection開源代碼時,“最先進的缺陷檢測網絡”赫然屹立於Github搜索結果的首位,還附帶該代碼對應的論文的題目,便前來膜拜。

Segmentation-Based Deep-Learning Approach for Surface-Defect(2019.06)
這篇論文主要是利用分割模型和分類模型進行表面瑕疵的檢測,主要優勢為:只需要25-30個有缺陷的樣本就可完成分類,所用樣本極少。
從目標檢測、目標追蹤最先進的文獻來看,效果最好的模型都是建立在分割(segmentation)的基礎上,像目標檢測中的Mask R-CNN、單目標追蹤中的Siam Mask,均包含Mask的分支。正如Siam Mask一作王強所述:
更為本質的,我們會發現,這個旋轉的矩形框實際上就是 mask 的一種近似。我們所要預測的實際上就是目標物體的 mask。
利用 mask 才能得到精度本身的上界。
這篇關於表面缺陷檢測的論文也是建立在分割的基礎上,概括的來說,這篇論文提出的模型結構包含分割模型與分類模型,在兩個模型融合的基礎上進行物體表面的缺陷檢測。與傳統深度學習不一樣的地方體現在,其可以通過較少的訓練樣本實現模型的訓練,並獲得很高的精度。這一設計使得模型可以更好的應用於工業領域。畢竟缺陷樣本數據的采集在工業場景中是一大難題,就像故障檢測中故障樣本集很少一樣。
表面缺陷檢測的現狀
工業生產過程中, 檢查產品的表面是保證本身質量的關鍵。表面質量的監控往往通過人工的,通過訓練的工人辨別復雜的表面缺陷, 這種控制方式是耗時、低效的。(就像最早日本豐田的生產線,每一個技工負責某一塊產品的生產檢測...) 工業4.0的發展,使得工業加工生產更趨向於生產線模式。
深度學習應用於缺陷檢測還有一個公開問題就是需要大量的數據去訓練具有百萬級參數的模型,大數據量的要求在機器視覺領域帶來的是數據標注的人工成本。
深度學習算法實際應用需要考慮三個方面的問題:
- 訓練數據數量的需求
 - 標注的需求
 - 計算能力的需求
 
論文貢獻
這篇文章提出了一個two-stage的模型,具有精度高、所需標注樣本少、計算量小的特性。並且其提供了表面缺陷的開源數據集KolektorSDD。 399個圖像【50種電子換向器】,52個有缺陷的樣本,347個無缺陷樣本。 分辨率1408*512。
其標注具有不同的類別,作者嘗試了不同大小的標注方式,認為不同的標注方式對於模型的訓練會有影響。
http://www.vicos.si/Downloads/KolektorSDD

實現細節
模型結構包含兩個部分,其一為像素級的分割Segmentation network(下圖左側),用於生成分割掩碼,確定缺陷的具體位置;其二為Decision network(下圖右側),用於生成二值分類輸出,判斷當前物體表面是否有缺陷。作者認為,在表面質量控制方面,是否有缺陷的決策比缺陷定位更重要。

1. Segmentation network (左側)
第一階段:分割網絡,對表面的缺陷進行像素級的定位,在高分辨率的圖像中檢查表面小缺陷。
這部分包含11個卷積層,3個最大池化層:
- 9個5*5卷積層;
 - 1個15*15卷積層; --- 卷積層后面包含一個特征標准化層(BN)和ReLU層,增加收斂性
 - 1個1*1卷積層; --- 源碼中1*1卷積沒有使用ReLU激活函數,使用的是sigmoid激活函數,用於生成二值掩碼。
 - 3個最大池化層 --- 用於降采樣, 其步長為2。
 
卷積層使用的都是同卷積,即卷積層不進行降采樣的操作,僅通過池化層進行降采樣的操作。通道數量分別為32/64/64/1024/1, 最終生成的是一個單通道,8倍縮小於原圖的segmentation output。
沒有使用Dropout。
由於分割網絡專注於在高分辨率的圖像表面上查找小的缺陷,因此,網絡需要滿足兩個需求:
- 具有較大的感受野;
 - 能夠捕捉到較小的特征細節。
 
以上兩個需求就需要在網絡設計上,具有下采樣層,在比較深的層中使用尺寸大的卷積核。
☆☆☆使用最大池化層代替具有步長的卷積層,能夠保證小但重要的細節在降采樣的過程中幸存。
比較yolov3 和yolo v3-tiny版,在yolo v3-tiny的結構中,由於其層數較少,使用最大池化層,代替yolov3中具有步長的卷積層。
模型不需要在別的數據集上進行預訓練,權重只需要使用服從正態分布隨機初始化即可。
模型選擇了MSE和交叉熵兩種損失函數對模型進行評估,結果表明使用交叉熵損失作為該網絡的損失函數最好。如下圖所示。

segmentation network 實現
1 def build_model(self): # build_model()中包含兩部分: 第一部分,分割網絡; 第二部分:決策網絡, 使用了嵌套函數的方式 2 def SegmentNet(input, scope, is_training, reuse=None): 3 with tf.variable_scope(scope, reuse=reuse): # 一個scope下的 4 with slim.arg_scope([slim.conv2d], 5 padding='SAME', 6 activation_fn=tf.nn.relu, 7 normalizer_fn=slim.batch_norm): # 為目標函數conv2d設置默認參數 8 net = slim.conv2d(input, 32, [5, 5], scope='conv1') 9 net = slim.conv2d(net, 32, [5, 5], scope='conv2') 10 net=slim.max_pool2d(net,[2,2],[2,2], scope='pool1') # [2,2]表示上下都是2的步長 11 12 net = slim.conv2d(net, 64, [5, 5],scope='conv3') 13 net = slim.conv2d(net, 64, [5, 5], scope='conv4') 14 net = slim.conv2d(net, 64, [5, 5], scope='conv5') 15 net=slim.max_pool2d(net, [2, 2], [2, 2], scope='pool2') 16 17 net = slim.conv2d(net, 64, [5, 5],scope='conv6') 18 net = slim.conv2d(net, 64, [5, 5], scope='conv7') 19 net = slim.conv2d(net, 64, [5, 5],scope='conv8') 20 net = slim.conv2d(net, 64, [5, 5], scope='conv9') 21 net=slim.max_pool2d(net,[2,2],[2,2],scope='pool3') 22 23 net = slim.conv2d(net, 1024, [15, 15], scope='conv10') # 1024的特征需要輸出 24 features=net # 所以這里將其賦值給features的變量中,用於輸出 25 net = slim.conv2d(net, 1, [1, 1],activation_fn=None, scope='conv11') 26 logits_pixel=net 27 net=tf.sigmoid(net, name=None) # 生成mask 經過了一層sigmoid激活函數 28 mask=net 29 return features,logits_pixel,mask
2. Decision network (右側)

第二階段: 決策網絡,對二值圖像進行分類
1. 附加網絡,使用的是分割網絡的輸出和分割網絡的特征,即上圖中,通道數量包含1024+1 = 1025層。其結構包括:
3個5*5卷積層; 通道數量為 8 、 16、 32 。[1025-8-16-32]
3個最大池化層; --- 降采樣 【步長為2】
這樣,輸出將會16倍縮小於原圖。
2. 附加網絡之后,包含一個全局池化層,分別對segmentation output 和 附加網絡輸出 進行全局最大和全局平均池化。最終將會組合生成 32+32+1+1 = 66 通道數量的輸出。(由於是做的全局池化,所以每一個輸出都是一個值,即消除了segmentation output 和附加網絡輸出維度不匹配的問題)
最后,通過一個全連接層,生成最終分類的輸出。 [0, 1]表示缺陷出現的可能性。
分類問題,所以選擇交叉熵損失函數。
Decision Network 實現
1 def DecisionNet(feature,mask, scope, is_training,num_classes=2, reuse=None): 2 with tf.variable_scope(scope, reuse=reuse): 3 with slim.arg_scope([slim.conv2d], 4 padding='SAME', 5 activation_fn=tf.nn.relu, 6 normalizer_fn=slim.batch_norm): 7 # 附加網絡 8 net=tf.concat([feature,mask],axis=3) # 1024的特征是和mask進行合並 9 net = slim.max_pool2d(net, [2, 2], [2, 2], scope='pool1') 10 11 net = slim.conv2d(net, 8, [5, 5], scope='conv1') 12 net = slim.max_pool2d(net, [2, 2], [2, 2], scope='pool2') 13 14 net = slim.conv2d(net, 16, [5, 5], scope='conv2') 15 net = slim.max_pool2d(net, [2, 2], [2, 2], scope='pool3') 16 17 net = slim.conv2d(net, 32, [5, 5], scope='conv3') 18 19 vector1=math_ops.reduce_mean(net, [1,2], name='pool4', keepdims=True) # 全局平均 20 vector2=math_ops.reduce_max(net, [1,2], name='pool5', keepdims=True) # 全局最大 21 22 # segmentation network 輸出的處理 23 vector3=math_ops.reduce_mean(mask, [1, 2], name='pool6', keepdims=True) 24 25 vector4=math_ops.reduce_max(mask, [1, 2], name='pool7', keepdims=True) # 全局池化操作就是reduce_max_mean 26 # 拼接 27 vector=tf.concat([vector1, vector2, vector3, vector4], axis=3) # 將全局內容合並 28 29 vector=tf.squeeze(vector, axis=[1, 2]) # # 刪除張量shape中維度為1的, 可以指定維度 30 31 logits = slim.fully_connected(vector, num_classes,activation_fn=None) 32 output=tf.argmax(logits, axis=1) # 找logits中最大的那個變量 33 return logits,output
兩個網絡損失函數的處理:
1 loss_pixel = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=logits_pixel, labels=PixelLabel_reshape)) 2 loss_class = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits_class,labels=Label)) 3 loss_total=loss_pixel+loss_class # 會把兩個的損失疊加起來進行計算
訓練過程:
訓練兩步走:
1. 單獨訓練分割網絡
2. 分割網絡權重凍結,訓練決策網絡
再進行微調,避免分割網絡過擬合。
訓練兩步走,微調的實現:
1 optimizer = tf.train.GradientDescentOptimizer(self.__learn_rate)
2 train_var_list = [v for v in tf.trainable_variables()] 3 train_segment_var_list = [v for v in tf.trainable_variables() if 'segment' in v.name ] 4 train_decision_var_list = [v for v in tf.trainable_variables() if 'decision' in v.name]
5 optimize_segment = optimizer.minimize(loss_pixel,var_list=train_segment_var_list) # segment訓練時使用 6 optimize_decision = optimizer.minimize(loss_class, var_list=train_decision_var_list) # decision訓練時使用 7 optimize_total = optimizer.minimize(loss_total, var_list=train_var_list) # 最終一起訓練時使用
決策層進行學習時,僅使用一個或兩個樣本每個batch。【限於GPU的內存】
分割層學習時,可以讓batch size增加幾倍,由於每個像素都是一個訓練樣本。
輸入圖像為灰度圖像
作者並沒有將8倍小於原圖的segmentation利用插值的方式還原到原圖的大小。作者認為8倍小於原圖的尺寸已經夠后續網絡使用。 可能還是覺得模型的輸出最主要的是分類,而分割的輸出沒有那么重要。 ---- 有沒有缺陷 要比在哪更重要
實驗的嘗試:

作者對標注類型的影響、損失函數的選擇(分割網絡)、輸入分辨率的大小、是否對輸入圖像進行旋轉 這四個方面對模型精度的影響進行探討。從下圖的結果中可以看出,分割網絡使用交叉熵損失函數,模型的精度要明顯優於MSE損失函數。
最佳的結果:
- dilate=5的標注方式 大注釋要好一些
 - cross-entropy loss function
 - full image resolution (即使用1408*512的分辨率的圖像)
 - 沒有任何旋轉 No rotation
 


