인공지능

Image Data - 콘크리트 이미지 분류 모델(MobileNetV2)

별은_StarIs_Dev 2025. 2. 9. 22:30
반응형

목적

 - 콘크리트 이미지가 정상인지 비정상인지 분류하는 모델을 만들자

 

1. 필요한 라이브러리 불러오기

import os
import shutil
import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tensorflow.keras.applications.mobilenet_v2 import MobileNetV2, preprocess_input
from tensorflow.keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau

 

2. 데이터 폴더 생성 및 정리

train = './concrete_image_cjk/src_cjk'
print(len(os.listdir(train)))
test = './concrete_image_cjk/test_cjk'
print(len(os.listdir(test)))

#(1) IMAGE/Negative 와 IMAGE/Positive에 파일 분류해서 넣기
import shutil

# 이미지 폴더 있는지 확인
if not os.path.exists('IMAGE'):
    os.mkdir('IMAGE')
if not os.path.exists('IMAGE/Negative'):
    os.mkdir('IMAGE/Negative')
if not os.path.exists('IMAGE/Positive'):
    os.mkdir('IMAGE/Positive')

count=0
for filename in os.listdir(train):
    if filename.endswith('.jpg'):
        label=filename.split("_")[-1].split(".")[0]
        if(label=="negative"):
          shutil.copy(os.path.join("./concrete_image_cjk/src_cjk", filename), os.path.join("IMAGE/Negative", filename))
        elif(label=="positive"):
          shutil.copy(os.path.join("./concrete_image_cjk/src_cjk", filename), os.path.join("IMAGE/Positive", filename))

 

3. 하이퍼파라미터 설정

num_epochs = 10
batch_size = 32

learning_rate = 0.001
dropout_rate = 0.5

input_shape = (224, 224, 3)  # 사이즈 확인
num_classes = 2    # Postive , Negative

 

4. 데이터 전처리 및 증강

image_datagen = ImageDataGenerator(
    validation_split=0.2,   # 80% 학습, 20% 검증
    preprocessing_function=preprocess_input,
    rotation_range=20,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest'
)

 

5. 학습 및 검증 데이터 로드

training_generator = image_datagen.flow_from_directory(
    './IMAGE',
    batch_size=batch_size,
    target_size=(224, 224),       # 원하는 출력 사이즈 입력. MobileNetV2 위해 사이즈 변경 : 227,227 --> 224,224. 최종 출력 : (224,224,3)
    class_mode = 'categorical',   # binary , categorical
    shuffle = True,
    subset = 'training'           # training, validation. ImageDataGenerator의 validation_split 사용하므로 subset 지정
    )

test_generator = image_datagen.flow_from_directory(
    './IMAGE',
    batch_size=batch_size,
    target_size=(224, 224),       # 원하는 출력 사이즈 입력. MobileNetV2 위해 사이즈 변경 : 227,227 --> 224,224. 최종 출력 : (224,224,3)
    class_mode = 'categorical',   # binary , categorical
    shuffle = True,
    subset = 'validation'         # training, validation. ImageDataGenerator의 validation_split 사용하므로 subset 지정
    )

 

6. MobileNetV2 모델 불러오기

base_model = MobileNetV2(input_shape=(224, 224, 3), include_top=False, weights='imagenet')
base_model.trainable = False  # 사전 학습된 가중치 고정

 

7. 모델 설계

x = base_model.output
x = tf.keras.layers.GlobalAveragePooling2D()(x)
output = tf.keras.layers.Dense(2, activation='softmax')(x)  # Positive / Negative 이진 분류

# 최종 모델 생성
model = tf.keras.Model(inputs=base_model.input, outputs=output)

 

8. 모델 컴파일

model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate),
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

 

9. 콜백 설정

es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=30)
checkpoint = ModelCheckpoint(filepath="checkpoint.weights.h5", save_weights_only=True, save_best_only=True, monitor='val_loss', verbose=1)
lrReducer = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2, min_lr=0.0001)
callbacks = [es, checkpoint, lrReducer]

 

10. 모델 학습

history = model.fit(
    training_generator,
    validation_data=test_generator,
    epochs=num_epochs,
    callbacks=callbacks
)

 

11. 모델 저장

model.save('20241220_con.h5')

 

12. 테스트 데이터 평가

# 정답 데이터 생성
target=[]
label=[]
result=[]
for item in os.listdir("./concrete_image_cjk/test_cjk"):
    target.append(item.split(".")[0])
    if(item.split("_")[-1].split(".")[0]=="positive"):
        label.append('positive')
    elif(item.split("_")[-1].split(".")[0]=="negative"):
        label.append('negative')
    result.append("")

df = pd.DataFrame({"target":target, "label":label, "result":result})
df.to_csv("test.csv")
final=pd.read_csv("test.csv")

 

# 모델 불러오기
loaded_model = tf.keras.models.load_model('20241220_con.h5')

# 테스트 데이터 불러오기
final = pd.read_csv("test.csv")
answer_class = {0: 'negative', 1: 'positive'}

right = 0
total = 0

# 테스트 이미지 개별 예측
for image, label in zip(final['target'], final['label']):
    total += 1
    test_image_path = f"./concrete_image_cjk/test_cjk/{image}.jpg"

    img = load_img(test_image_path, target_size=(224, 224))
    img_array = img_to_array(img)
    img_array = np.expand_dims(img_array, axis=0)
    img_array = preprocess_input(img_array)

    predictions = loaded_model(img_array)
    predicted_class = np.argmax(predictions, axis=1)[0]
    pred = answer_class[predicted_class]

    if label == pred:
        right += 1

accuracy = (right / total) * 100
print(f"점수는 : {accuracy:.2f}점")

 

 

13. 최종파일 생성

real_final=pd.DataFrame({"target":final['target'].tolist(), "label":final['label'].tolist(), "result":result})
real_final.to_csv("real_final.csv")

 

 

최종코드

import os
import shutil
import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tensorflow.keras.applications.mobilenet_v2 import MobileNetV2, preprocess_input
from tensorflow.keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau

train = './concrete_image_cjk/src_cjk'
print(len(os.listdir(train)))
test = './concrete_image_cjk/test_cjk'
print(len(os.listdir(test)))

#(1) IMAGE/Negative 와 IMAGE/Positive에 파일 분류해서 넣기
import shutil

# 이미지 폴더 있는지 확인
if not os.path.exists('IMAGE'):
    os.mkdir('IMAGE')
if not os.path.exists('IMAGE/Negative'):
    os.mkdir('IMAGE/Negative')
if not os.path.exists('IMAGE/Positive'):
    os.mkdir('IMAGE/Positive')

count=0
for filename in os.listdir(train):
    if filename.endswith('.jpg'):
        label=filename.split("_")[-1].split(".")[0]
        if(label=="negative"):
          shutil.copy(os.path.join("./concrete_image_cjk/src_cjk", filename), os.path.join("IMAGE/Negative", filename))
        elif(label=="positive"):
          shutil.copy(os.path.join("./concrete_image_cjk/src_cjk", filename), os.path.join("IMAGE/Positive", filename))

num_epochs = 10
batch_size = 32

learning_rate = 0.001
dropout_rate = 0.5

input_shape = (224, 224, 3)  # 사이즈 확인
num_classes = 2    # Postive , Negative

image_datagen = ImageDataGenerator(
    validation_split=0.2,   # 80% 학습, 20% 검증
    preprocessing_function=preprocess_input,
    rotation_range=20,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest'
)

training_generator = image_datagen.flow_from_directory(
    './IMAGE',
    batch_size=batch_size,
    target_size=(224, 224),       # 원하는 출력 사이즈 입력. MobileNetV2 위해 사이즈 변경 : 227,227 --> 224,224. 최종 출력 : (224,224,3)
    class_mode = 'categorical',   # binary , categorical
    shuffle = True,
    subset = 'training'           # training, validation. ImageDataGenerator의 validation_split 사용하므로 subset 지정
    )

test_generator = image_datagen.flow_from_directory(
    './IMAGE',
    batch_size=batch_size,
    target_size=(224, 224),       # 원하는 출력 사이즈 입력. MobileNetV2 위해 사이즈 변경 : 227,227 --> 224,224. 최종 출력 : (224,224,3)
    class_mode = 'categorical',   # binary , categorical
    shuffle = True,
    subset = 'validation'         # training, validation. ImageDataGenerator의 validation_split 사용하므로 subset 지정
    )

base_model = MobileNetV2(input_shape=(224, 224, 3), include_top=False, weights='imagenet')
base_model.trainable = False  # 사전 학습된 가중치 고정

x = base_model.output
x = tf.keras.layers.GlobalAveragePooling2D()(x)
output = tf.keras.layers.Dense(2, activation='softmax')(x)  # Positive / Negative 이진 분류

# 최종 모델 생성
model = tf.keras.Model(inputs=base_model.input, outputs=output)

model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate),
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=30)
checkpoint = ModelCheckpoint(filepath="checkpoint.weights.h5", save_weights_only=True, save_best_only=True, monitor='val_loss', verbose=1)
lrReducer = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2, min_lr=0.0001)
callbacks = [es, checkpoint, lrReducer]

history = model.fit(
    training_generator,
    validation_data=test_generator,
    epochs=num_epochs,
    callbacks=callbacks
)

model.save('20241220_con.h5')

# 정답 데이터 생성
target=[]
label=[]
result=[]
for item in os.listdir("./concrete_image_cjk/test_cjk"):
    target.append(item.split(".")[0])
    if(item.split("_")[-1].split(".")[0]=="positive"):
        label.append('positive')
    elif(item.split("_")[-1].split(".")[0]=="negative"):
        label.append('negative')
    result.append("")

df = pd.DataFrame({"target":target, "label":label, "result":result})
df.to_csv("test.csv")
final=pd.read_csv("test.csv")

# 모델 불러오기
loaded_model = tf.keras.models.load_model('20241220_con.h5')

# 테스트 데이터 불러오기
final = pd.read_csv("test.csv")
answer_class = {0: 'negative', 1: 'positive'}

right = 0
total = 0

# 테스트 이미지 개별 예측
for image, label in zip(final['target'], final['label']):
    total += 1
    test_image_path = f"./concrete_image_cjk/test_cjk/{image}.jpg"

    img = load_img(test_image_path, target_size=(224, 224))
    img_array = img_to_array(img)
    img_array = np.expand_dims(img_array, axis=0)
    img_array = preprocess_input(img_array)

    predictions = loaded_model(img_array)
    predicted_class = np.argmax(predictions, axis=1)[0]
    pred = answer_class[predicted_class]

    if label == pred:
        right += 1

accuracy = (right / total) * 100
print(f"점수는 : {accuracy:.2f}점")

real_final=pd.DataFrame({"target":final['target'].tolist(), "label":final['label'].tolist(), "result":result})
real_final.to_csv("real_final.csv")
반응형