AI 웹 개발 과정/개인 프로젝트

개인 프로젝트 05 : 타임어택 미션 - 강아지와 고양이 이미지 분류

만 기 2022. 5. 25. 16:35
 강아지와 고양이 이미지 분류

 

단계별 구현 내용

 

[1단계]

  1. 코랩에서 데이터를 다운로드 받는다.
  2. flow_from_directory 를 통해 데이터를 전처리하며 불러온다.
  3. cnn 혹은 전이학습을 통해 강아지와 고양이를 분류하는 모델을 학습시킨다.

[2단계]

  1. flask 프로젝트를 만든다.
  2. flask를 활용해 웹 상에서 제목과 함께 이미지를 업로드할 수 있는 파일 업로드 기능을 구현한다.
  3. flask를 활용해 웹 상에서 이미지를 업로드하면 해당 이미지가 강아지인지, 고양이인지에 대한 결과를 출력해준다.

[3단계]

  1. 파일이 업로드 한다고 해서 바로 결과 페이지로 넘어가지 않고, 제목을 토대로 특정한 이미지를 검색해서 결과를 볼 수 있도록 구현한다.

 

 

1단계

 

- 강아지와 고양이 데이터셋

(출처: https://www.kaggle.com/datasets/tongpython/cat-and-dog)

 

- CNN을 통한 모델 학습 코드

환경변수 설정

import os
os.environ['KAGGLE_USERNAME'] = '' # username
os.environ['KAGGLE_KEY'] = '' # key

데이터셋 다운 & 압축해제

!kaggle datasets download -d tongpython/cat-and-dog

!unzip -q cat-and-dog.zip

기본 CNN

from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, Flatten
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.image import ImageDataGenerator

이미지 증강

train_datagen = ImageDataGenerator(
  rescale=1./255, # 일반화
  rotation_range=10, # 랜덤하게 이미지를 회전 (단위: 도, 0-180)
  zoom_range=0.1, # 랜덤하게 이미지 확대 (%)
  width_shift_range=0.1,  # 랜덤하게 이미지를 수평으로 이동 (%)
  height_shift_range=0.1,  # 랜덤하게 이미지를 수직으로 이동 (%)
  horizontal_flip=True # 랜덤하게 이미지를 수평으로 뒤집기
)

test_datagen = ImageDataGenerator(
  rescale=1./255 # 일반화
)

train_gen = train_datagen.flow_from_directory(
  'training_set/training_set',
  target_size=(256, 256), # (height, width)
  batch_size=32,
  seed=2021,
  class_mode='binary',    # 결과 0(고양이), 1(강아지) 뿐
  shuffle=True
)

test_gen = test_datagen.flow_from_directory(
  'test_set/test_set',
  target_size=(256, 256), # (height, width)
  batch_size=32,
  seed=2021,
  class_mode='binary',
  shuffle=False
)

from pprint import pprint
pprint(train_gen.class_indices)

미리보기

preview_batch = train_gen.__getitem__(0)

preview_imgs, preview_labels = preview_batch

plt.title(str(preview_labels[0]))
plt.imshow(preview_imgs[0])

CNN 학습

model = Sequential([
    Conv2D(filters=32, kernel_size=(3, 3), padding='same', activation="relu", input_shape=(256, 256, 3)),
    MaxPooling2D(pool_size=(2, 2), strides=2),
    Conv2D(filters=64, kernel_size=(3, 3), padding='same', activation="relu"),
    MaxPooling2D(pool_size=(2, 2), strides=2),
    Conv2D(filters=128, kernel_size=(3, 3), padding='same', activation="relu"),
    MaxPooling2D(pool_size=(2, 2), strides=2),
    Flatten(),
    Dense(128, activation='relu'),
    Dense(1, activation="sigmoid")  # 이진
])

model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.001), metrics=['acc'])  # 이진

from tensorflow.keras.callbacks import ModelCheckpoint

history = model.fit(
    train_gen,
    validation_data=test_gen, # 검증 데이터를 넣어주면 한 epoch이 끝날때마다 자동으로 검증
    epochs=20, # epochs 복수형으로 쓰기!
    callbacks=[
      ModelCheckpoint('model.h5', monitor='val_acc', verbose=1, save_best_only=True)
    ]
)

 

 

 

2단계

 

- flask를 활용해 웹상에서 모델 적용하기

- index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script src="../static/js/upload.js"></script>
  <script src="../static/js/log_in.js"></script>
  <title>Document</title>
</head>
<body>
  <div class="upload">
    <img style="display: none;" id="frame" src="" width="300px"/>
    <input type="file" id="upload-file" onchange="preview()">
    <input type="text" id="upload-title">

    <!--upload() 함수 실행-->
    <button onclick="upload()">파일을 업로드해봅시다!</button>
  </div>
  <div class="search">
    <input type="text" id="search-title">

    <!--# search() 함수 실행-->
    <button onclick="search()">검색해보아요</button>
  </div>
</body>

</html>

 

 

- javascript

function upload() {
    let file = $('#upload-file')[0].files[0]    // id:uplad-file, 해당 파일 지정해주는 형식
    let title = $('#upload-title').val()
    let form_data = new FormData()  // 파일 보낼때는 new FormData()를 사용해서 담아보내준다.

    form_data.append("file_give", file)     // .append를 사용해 " "안의 이름으로 file 변수를 넣는다.
    form_data.append("title_give", title)

    $.ajax({
        type: "POST",
        url: "/upload",
        data: form_data,    // FormData
        cache: false,
        contentType: false,
        processData: false,
        success: function (response) {
            alert(response["result"])
        }
    });
  }

  function search() {
    let title = $('#search-title').val()
    let form_data = new FormData()

    form_data.append("title_give", title)

    $.ajax({
        type: "POST",
        url: "/search",
        data: form_data,
        cache: false,
        contentType: false,
        processData: false,
        success: function (response) {
            let predictions = response["predictions"]
            $('.result').remove()
            for (let i = 0; i < predictions.length; i++) {
                let path = predictions[i]['path']
                let result = predictions[i]['result']

                // 응답받은값 이미지와 결과 가져오기
                let temp_html = `<div class="result"><img src="${path}" width="100px"/>
                                <p>${result}</p></div>`
                $('.search').append(temp_html)
            }
        }
    });
  }

  function preview() {
    let frame = document.getElementById('frame');
    frame.src=URL.createObjectURL(event.target.files[0]);
    frame.style.display = 'block';
  }

 

 

- app.py

from flask import Flask, render_template, request, jsonify
from datetime import datetime
import tensorflow as tf
import numpy as np
import os
# 이미지 저장 위해서 pillow 라이브러리 설치 필요!!!

# 학습시킨 모델 불러오기 (0이면 고양이, 1이면 강아지)
# 함수 밖에서 불러야 메모리 손실 방지
model = tf.keras.models.load_model('static/model/model.h5')
app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/upload', methods=['POST'])
def upload():
    # 받은데이터에서 .files로 ' '안의 이름가진 파일 꺼내담기
    file = request.files['file_give']
    title = request.form['title_give']
    extension = file.filename.split('.')[-1]    # 파일 확장자 추출

    #현재시간 담기
    today = datetime.now()
    mytime = today.strftime('%Y-%m-%d-%H-%M-%S')
    filename = f'{mytime}'

    # 저장 경로에다가 현재시간과 확장자로 파일 이름만들어 저장
    save_to = f'static/img/{title}_{filename}.{extension}'
    file.save(save_to)  # 파일 저장

    return jsonify({'result':'success'})

@app.route('/search', methods=['POST'])
def search():
    title = request.form['title_give']
    filenames = os.listdir('static/img') # os.listdir : 경로에 있는 파일 리스트 가져오기
    # 유저가 작성한 이름과 일치하는 파일 경로를 저장한다.
    matched_files = ['static/img/'+filename for filename in filenames if title in filename]
    result_dict = []
    for index, matched_file in enumerate(matched_files):    # enumerate : index값이 있는 for문
        # .load_img로 경로에있는 파일 가져온다.
        image = tf.keras.preprocessing.image.load_img(matched_file, target_size=(224, 224))
        # 이미지형태의 데이터타입을 array로 만듬
        input_arr = tf.keras.preprocessing.image.img_to_array(image)
        # [] 로 배치형태 데이터로 만듬 => .predict 할수 있다.
        input_arr = np.array([input_arr])
        predictions = model.predict(input_arr)
        if predictions[0][0] > 0.5:
            result = '강아지'
        else:
            result = '고양이'
        # 파일인덱스번호, 파일경로, 예측결과를 담아서 반환
        result_dict.append({'index': index, 'path':matched_file, 'result':result})
    return jsonify({'predictions': result_dict})

if __name__ == '__main__':
    app.run('0.0.0.0', port=8000, debug=True)

 

 

- 결과