728x90

2021.03.10 - [기록 note] - 2021-03-10(제조공정불량실습)

아래 내용은 머신러닝 강의 교육 중 실습한 내용의 일부분입니다

좋은 참고가 되었으면 합니다

 

개요

의류직물 생산 시 찍힘, 스크래치, 뜯어짐 같은 불량을 인공지능을 통해 구분하는 프로그램을 만드는 내용입니다. 

 

*사용된 모델: inception

*데이터 출처: AITEX FABRIC IMAGE DATABASE

 

불량(왼쪽) 이미지와  정상(오른쪽) 이미지

원본 데이터를 보면 몇몇 의류직물의 패턴이 다른 걸로 보아서 여러 모델의 의류직물이 섞여 있는 것 같습니다

또한 불량 이미지 중에는 사람이 봐도 구분이 가지 않는 불량품이 있음을 확인했습니다.

이제 이 데이터를 가지고 딥러닝을 통해 불량 검출을 해보겠습니다

 

 Import

import glob
import os
from datetime import datetime
import time
import tensorflow as tf
import tensorflow_addons as tfa
import matplotlib.pyplot as plt
import cv2
from tensorflow.keras.layers import Conv2D, MaxPool2D, Concatenate, Flatten, Dense

 

작업 경로 및 파라미터 설정

DATASET_OK_PATTERN = 'dataset/3/OK/*.png'
DATASET_FAIL_PATTERN = 'dataset/3/FAIL/*.png'

TFRECORD_PATH = 'tfrecords/'
IMAGE_PER_TFRECORD = 100 #100개이미지를 하나의 TFRECORD로 묶음

 

  • 이미지를 TFRecord로 변환 시 저장할 위치를 지정합니다
  • 100개 이미지를 하나의 TFRecord로 생성합니다

 

데이터 전처리

ok_list = glob.glob(DATASET_OK_PATTERN)
fail_list = glob.glob(DATASET_FAIL_PATTERN)

#오버샘플링을 위해 데이터 수를 저장 해준다.
num_ok = len(ok_list)
num_fail = len(fail_list)

#fail데이터를 여러번 반복해서 ok데이터 사이즈와 맞춘다
fail_list_new= list()
for _ in range(num_ok//num_fail):
    fail_list_new += fail_list
    
#나머지 부분을 채워주기 위해서 따로 작업해준다
fail_list_new += fail_list[: num_ok % num_fail]
fail_list =fail_list_new

#레이블 생성
ok_label = [0]* len(ok_list)
fail_label = [1]* len(fail_list)

#파일과 레이블을 각각 묶어주기
file_list = ok_list +fail_list
label_list = ok_label +fail_label
  • 불량 데이터가 적으므로 오버샘플링을 사용해서 정상 데이터만큼 늘려줍니다
  • num_ok//num_fail를 통해 차이가 나는 것만큼 채워주고, 나머지는 num_ok%num_fail로 해결해 줍니다
  • 정상은 0번 클래스, 불량은 1번 클래스로 레이블 생성합니다
  • 이미지 파일은 파일대로, 레이블은 레이블대로 묶습니다(TFRecord 만들기 위해서 따로 구분)

 

TFRecord 생성

1. TFRecord 함수

# 함수출처: https://www.tensorflow.org/tutorials/load_data/tfrecord

def _bytes_feature(value):
    """Returns a bytes_list from a string / byte."""
    if isinstance(value, type(tf.constant(0))):
        value = value.numpy() # BytesList won't unpack a string from an EagerTensor.
    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))

def _int64_feature(value):
    """Returns an int64_list from a bool / enum / int / uint."""
    return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))

def image_example(image_string, label): 
    image_shape = tf.image.decode_image(image_string).shape

    feature = {
        'height': _int64_feature(image_shape[0]),
        'width': _int64_feature(image_shape[1]),
        'depth': _int64_feature(image_shape[2]),
        'label': _int64_feature(label),
        'image_raw': _bytes_feature(image_string),
    }

    return tf.train.Example(features=tf.train.Features(feature=feature))

 

 

2. TFRecord 저장

#데이터를 원하는 크기 만큼슬라이싱 해서 저장
#tfrecord 저장 폴더
if os.path.exists(TFRECORD_PATH) is False:
    os.mkdir(TFRECORD_PATH)
    
# +1: 딱 나누 떨어지지 않고 나머지가 존재하기 때문
num_tfrecords = len(file_list) // IMAGE_PER_TFRECORD #TFRecord 갯수
if len(file_list) // IMAGE_PER_TFRECORD !=0:
    num_tfrecords+=1
    
for idx in range(num_tfrecords):
    idx0= idx*IMAGE_PER_TFRECORD
    idx1=  idx0+IMAGE_PER_TFRECORD #끝나는점
    record_file =TFRECORD_PATH+'%05d.tfrecords'% idx
    with tf.io.TFRecordWriter(record_file) as writer:
        for filename, label in zip(file_list[idx0:idx1], #0~99,100~99 100개씩
                                  label_list[idx0:idx1]):
            image_string = open(filename, 'rb').read() #rb 바이너리로 읽기
            tf_example = image_example(image_string, label) #파일과 레이블 , 바이너리로 저징
            writer.write(tf_example.SerializeToString()) #저장
  • num_tfrecord: tfrecord파일의 개수를 계산합니다
  • 나머지 부분은 if문으로 따로 처리합니다
  • idx와 IMAGE_PER_TFRRCORD를 사용해서 100개씩 이미지와 레이블을 저장합니다
  • 위에서 정의한 image_example 함수를 통해 이미지와 레이블의 저장 타입을 설정할 수 있습니다

 

  • 각 파일에는 100개 이미지와 100개 레이블 정보가 이진 형태로 함께 저장되어 있습니다
  • 이 tfrecord 데이터로 모델 학습을 진행합니다

하이퍼라마미터 설정

EPOCHS = 1000
RESULT_SAVE_PATH = 'results/'

 

모델 정의

def Model():
    #padding=same을 해줘야 다른 필터들을 적용한 것과 Concatenate가 가능하다
    def inception(filters):
        def subnetwork(x):
            h1 = Conv2D(filters, (1,1), padding='same',activation='relu')(x)
            h1 = MaxPool2D()(h1)
            
            h2 = Conv2D(filters//2, (1,1), padding='same',activation='relu')(x)
            h2 = Conv2D(filters, (3,3), padding='same',activation='relu')(h2)
            h2 = MaxPool2D()(h2)
            
            h3 = Conv2D(filters//2, (1,1), padding='same',activation='relu')(x)
            h3 = Conv2D(filters, (5,5), padding='same',activation='relu')(h3)
            h3 = MaxPool2D()(h3)
            
            return Concatenate()([h1,h2,h3])
        return subnetwork
    #입력 x 설정
    x= tf.keras.Input(shape=(256,256,3)) #입력층을 아는 경우에는 지정하는게 연산과정에서 이득
    h= inception(16)(x)
    h= inception(32)(h)
    h= inception(32)(h)
    h= inception(32)(h)
    h= inception(32)(h)
    h=Flatten()(h)
    h=Dense(1024, activation='relu')(h)
    
    #출력
    y=Dense(1, activation='sigmoid')(h)
    return tf.keras.Model(inputs=x, outputs=y)    

 

이미지 데이터 타입 변환을 위한 함수 정의

def preprocess(img):
    return tf.image.convert_image_dtype(img, tf.float32)

 

데이터 Augmentation 함수 정의

def augmentation(img, label):
    def flip(x):
        x= tf.image.random_flip_left_right(x)
        x= tf.image.random_flip_up_down(x)
        return x
    def rotate(x):
        x = tf.cond(tf.random.uniform(shape=[], minval=0.0, maxval=1.0,dtype=tf.float32)>0.5,
                   lambda: tfa.image.rotate(x, tf.random.uniform(shape=[], minval=0.0, maxval=360.0, dtype=tf.float32),
                                                                interpolation='BILINEAR'),
                   lambda: x)
        return x
                    
    def translation(x):
        dx= tf.random.uniform(shape=[], minval=-10.0, maxval=10.0,dtype=tf.float32)
        dy= tf.random.uniform(shape=[], minval=-10.0, maxval=10.0,dtype=tf.float32)
        x = tf.cond(tf.random.uniform(shape=[], minval=0.0, maxval=1.0, dtype=tf.float32) >0.5,
                   lambda: tfa.image.transform(x, [0,0,dx, 0,0,dy, 0,0], #끝에 두 0은 스케일팩터
                                              interpolation='BILINEAR'),
                   lambda: x)
        return x
    img = flip(img)
    img = rotate(img)
    img = translation(img)    
            
    return img, label
  • [0,0, dx, 0,0, dy]에서 0,0은 각각 x, y좌표인 것 같습니다 x, y의 변화량은 0이고 위치만 dx, dy만큼 변경한다는 내용(추측입니다)

1번 참고 이미지
2번 참고 이미지
3번 참고 이미지
affine 참고 이미지

TFRecords 불러오기

tffiles = glob.glob('tfrecords/*')
raw_image_dataset = tf.data.TFRecordDataset(tffiles)

image_feature_description = {
    'height': tf.io.FixedLenFeature([], tf.int64),
    'width': tf.io.FixedLenFeature([], tf.int64),
    'depth': tf.io.FixedLenFeature([], tf.int64),
    'label': tf.io.FixedLenFeature([], tf.int64),
    'image_raw': tf.io.FixedLenFeature([], tf.string),
}

def _parse_image_function(example_proto):
    return tf.io.parse_single_example(example_proto, image_feature_description)

def _parse_image_label(parsed_dataset):
    return preprocess(tf.image.decode_png(parsed_dataset['image_raw'])), parsed_dataset['label']

parsed_image_dataset = raw_image_dataset.map(_parse_image_function) #각 파일을 image_feature_description를 적용
dataset = parsed_image_dataset.map(_parse_image_label) #레이블은 그대로 출력하고, 이미지는 float32로 타입 변환 후 출력
  • 이미지와 레이블 데이터를 불러올 때 이미지 데이터만 preprocess를 통해 float32로 변환해준다

데이터셋 나누기

ds_size=0
for _ in dataset:
    ds_size+=1
train_size = int(ds_size*0.7)

ds = dataset.shuffle(ds_size)

#배치다음에 map함수가 들어가야 addons함수가 작동이 됨(데이터 증식과 관련됨)
ds_train =ds.take(train_size).shuffle(1024, reshuffle_each_iteration=True).prefetch(1024).batch(32) #.map(augmentation)
ds_valid =ds.skip(train_size).prefetch(1024).batch(32)

 

 

모델 생성

model = Model()
model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

 

모델 학습

earlystopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=30, verbose=1)
history = model.fit(ds_train,
                    validation_data=ds_valid,
                    epochs=EPOCHS,
                    callbacks=[earlystopping])

 

학습 결과 Plot

loss = history.history['loss']
val_loss = history.history['val_loss']

plt.figure()
plt.plot(loss, 'ro-')
plt.plot(val_loss, 'bo-')
plt.ylabel('Cross Entropy')
plt.xlabel('Epoch')
plt.title('Training and Validation Loss')
plt.show()

  • 180 epoch에서 큰 폭의 감소가 이뤄졌으며 과적합 현상 없이 잘 학습이 되었습니다

모델 저장

model.save('model/inception_model.h5')
  • 저장된 모델을 사용해서 배치형 동작 프로그램을 생성합니다

*배치형 동작 프로그램 생성하기

배치형 프로그램은 대량의 데이터를 일괄적으로 특정 시간에 처리하는 것을 의미합니다

이번 실습 내용에서는 특정 시간이 되면 자동으로 동작하도록 하는 내용은 없음을 미리 알려드립니다

 

하이퍼 파라미터/ Path 설정

THRES_LEVEL = 0.5

INPUT_PATH = 'data/input_data/'
CSV_PATH = 'data/output_csv/'
  • 0과 1 클래스를 판별하는 기준은 보통 0.5로 사용합니다. 
  • 0.5 이하면 0번 클래스(양품), 크다면 1번 클래스(부적합품)로 구분합니다
  • 이 실습의 고객 요구 사항중 하나는 불량품을 정상품으로 오분류를 절대 허용하지 않아야 한다는 점입니다
  • 즉, 조금이라도 의심이 되면 불량으로 처리해달라는 의미 이므로 THRES_LEVEL를 내리는 방법도 생각해 볼 수 있습니다

 

모델 불러오기

model = tf.keras.models.load_models('model/inception_model.h5')
  • 학습한 모델을 불러와서 새로운 이미지 데이터를 예측할

입력 데이터 전처리를 위한 함수 정의

def preprocess(file_name):
    img = tf.io.read_file(file_name)
    img = tf.image.decode_image(img)
    return tf.image.convert_image_dtype(img, dtype.float32)
  • 불러들인 이미지의 타입을 float32로 변경하는 함수를 정의합니다
  • 이때 이미지는 png가 아니므로 decode_image를 사용합니다

입력 데이터 불러오기

file_list =glob.glob(INPUT_PATH+'*.png') # 파일이름
dataset = tf.data.Dataset.list_files(file_list).map(preprocess) #이미지

 

알고리즘 구동 및 CSV 결과 저장

now = datetime.now().strftime('%Y%m%d%_%H%M%S')
with open(CSV_PATH+now+'.csv','w') as f: #파일을 열고 작업
    for image, filename in zip(dataset, file_list):
        image= image[tf.newaxis,...] # HWC -> batch,HWC
        
        a= time.time()
        predict = model.predict(image)[0][0]
        print('Inference Time:', time.time()-a)
        
        if predict > THRES_LEVEL:
            label = 'FAIL'
        else:
            label ='OK'
        f.write(','.join([filename, label, str(predict)]))
  • 해당 모델을 학습 시 배치가 있었으므로 축을 하나 더 생성하여 형식을 맞춰줍니다 [tf.newaxis,...]
  • 만일 축을 뒤쪽에 생성한다면 [..., tf.newaxis]

  • C 셀을 보면 예측값이 1과 가까운 값이므로 1번 클래스인 FAIL로 되어 있지만
  • A셀에 정답 레이블을 보면 OK인 경우가 다수 존재했음을 알 수가 있습니다

2021.03.09 - [기록 note] - 2021-03-09(제조공정불량검출)

2021.03.10 - [기록 note] - 2021-03-10(제조공정불량실습)

728x90

'실습 note' 카테고리의 다른 글

자전거 수요예측 실습  (0) 2021.04.16
bitcoin 예측 실습  (0) 2021.04.14
OpenCV_12(딥러닝2)  (0) 2021.03.05
OpenCV_11(딥러닝)  (0) 2021.03.04
OpenCV_10(머신러닝)  (0) 2021.03.03

+ Recent posts