본문 바로가기
  • 우당탕탕속의 잔잔함
Programming/Front-End & Back-End

[WEB + Classifier] Django환경에서 TensorFlow 구동

by zpstls 2023. 1. 27.
반응형

 

 

이번 포스트에서는 Web에서 구동되는 Image Classifier를 개발해 볼 것입니다.

Django와 TensorFlow를 통해 구현해 볼 예정입니다!

 

 

Django를 통해 Web환경을 구성하고 해당 환경에서 간단한 Image Classifier를 구동시킬 것입니다. 이때 Image Classifier는 TensorFlow를 통해 구현할 것입니다.

 

우선, Django에 대한 간단한 내용은 다음 글을 참고하시면 도움이 될 것이라 생각됩니다.

 

[WEB] Django를 통한 간단한 웹 구축하기

요번 포스트에서는 Django를 아주 간단하게 다뤄보도록 하겠습니다. 저는 Back-End 개발자가 아니기에... 아주 심플하게 수행해볼 예정입니다. Deep Learning과 관련된 프로젝트를 수행하다보면 Server에

mj-thump-thump-story.tistory.com

 

TensorFlow에 관한 기초적인 내용은 지금은 다루지 않겠습니다. 향후, 관련 포스트를 작성하게 되면 첨부하도록 하겠습니다.

 

 

그럼, 본격적으로 시작해보겠습니다.

TensorFlow를 통해 다섯 종류의 동물을 분류하는 분류기를 만들고 해당 분류기를 Web 환경에서 구동할 수 있도록 할 것입니다. 개발 순서는 TensorFlow → Django로 진행할 것 입니다.

 

 

TensorFlow

다섯 종류의 동물(고양이, 개, 원숭이, 코끼리, 앵무새)을 분류하는 Image Classifier를 개발할 것입니다. 분류기는 CNN 기반으로 구현할 것 입니다.

Dataset을 구성하고 Model을 설계한 다음 구성한 Dataset을 이용해 Train을 진행합니다.

 

 

Dataset

Google App인 “Download All Images”를 이용해 검색된 Image들을 모두 Download 하는 Tool을 사용해 Dataset을 구성할 것입니다.

다음 링크에서 확장 프로그램을 다운로드하여 사용하도록 합니다.

 

Download All Images

Save all images in active tab as .zip file. Easily save photos from Instagram, Google Images, etc.

chrome.google.com

Google Image에서 수집하고자 하는 Keyword를 입력하여 이미지 검색을 수행합니다. 그리고 확장 프로그램을 통해 검색된 모든 이미지를 ZIP 파일로 저장합니다.

 

수집이 완료되었으면, 다음과 같은 구조로 수집한 각각의 Image Data들을 저장합니다.

Structure Of Dataset

Dataset 구성이 완료되었다면 다음과 같은 Script를 통해 numpy array로 .npy file을 생성합니다.

import os
import cv2
from PIL import Image
import numpy as np

data=[]
labels=[]
# ----------------
# LABELS
# Cat 0
# Dog 1
# Monkey 2
# Parrot 3
# Elephant 4
# Bear 5
# ----------------

# Cat 0
cats = os.listdir(os.getcwd() + "/data/cat")
for x in cats:
    imag=cv2.imread(os.getcwd() + "/data/cat/" + x)
    img_from_ar = Image.fromarray(imag, 'RGB')
    resized_image = img_from_ar.resize((50, 50))
    data.append(np.array(resized_image))
    labels.append(0)

# Dog 1
dogs = os.listdir(os.getcwd() + "/data/dog/")
for x in dogs:
    imag=cv2.imread(os.getcwd() + "/data/dog/" + x)
    img_from_ar = Image.fromarray(imag, 'RGB')
    resized_image = img_from_ar.resize((50, 50))
    data.append(np.array(resized_image))
    labels.append(1)

# Monkey 2
monkeys = os.listdir(os.getcwd() + "/data/monkey/")
for x in monkeys:
    imag=cv2.imread(os.getcwd() + "/data/monkey/" + x)
    img_from_ar = Image.fromarray(imag, 'RGB')
    resized_image = img_from_ar.resize((50, 50))
    data.append(np.array(resized_image))
    labels.append(2)

# Parrot 3
parrots = os.listdir(os.getcwd() + "/data/parrot/")
for x in parrots:
    imag=cv2.imread(os.getcwd() + "/data/parrot/" + x)
    img_from_ar = Image.fromarray(imag, 'RGB')
    resized_image = img_from_ar.resize((50, 50))
    data.append(np.array(resized_image))
    labels.append(3)

# Elephant 4
elephants = os.listdir(os.getcwd() + "/data/elephant/")
for x in elephants:
    imag=cv2.imread(os.getcwd() + "/data/elephant/" + x)
    img_from_ar = Image.fromarray(imag, 'RGB')
    resized_image = img_from_ar.resize((50, 50))
    data.append(np.array(resized_image))
    labels.append(4)

# Bear 5
bears = os.listdir(os.getcwd() + "/data/bear/")
for x in bears:
    imag=cv2.imread(os.getcwd() + "/data/bear/" + x)
    img_from_ar = Image.fromarray(imag, 'RGB')
    resized_image = img_from_ar.resize((50, 50))
    data.append(np.array(resized_image))
    labels.append(5)


animals=np.array(data)
labels=np.array(labels)

np.save("animals",animals)
np.save("labels",labels)

위 코드는 각 Image들을 Load 한 후, 50x50으로 resize 한 후, numpy array 형태로 저장하는 기능을 수행합니다.

 

여기까지가 Dataset을 구성하는 과정입니다. 이제 이 Dataset을 이용해 Train을 수행할 수 있습니다.

 

 

Model & Train

Train을 진행하기 전에 Model을 구성합니다.

Model은 Conv2D→MaxPool→Conv2D→MaxPool→Conv2D→Flatten→Dense로 구성되는 아주 간단한 형태를 갖습니다. 이후, Adam을 Optimizer로 사용하고 Loss는 Sparse Categorical Crossentropy를 사용해 Train을 시킵니다.

 

전반적인 Code는 다음과 같습니다.

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

animals=np.load("animals.npy")
labels=np.load("labels.npy")

s=np.arange(animals.shape[0])
np.random.shuffle(s)
animals=animals[s]
labels=labels[s]

num_classes=len(np.unique(labels))
data_length=len(animals)

(x_train,x_test)=animals[(int)(0.1*data_length):],animals[:(int)(0.1*data_length)]
x_train = x_train.astype('float32')/255
x_test = x_test.astype('float32')/255
train_length=len(x_train)
test_length=len(x_test)

(y_train,y_test)=labels[(int)(0.1*data_length):],labels[:(int)(0.1*data_length)]

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(50, 50, 3)))
model.add(tf.keras.layers.MaxPooling2D((2, 2)))
model.add(tf.keras.layers.Conv2D(64, (3, 3), activation='relu'))
model.add(tf.keras.layers.MaxPooling2D((2, 2)))
model.add(tf.keras.layers.Conv2D(64, (3, 3), activation='relu'))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(64, activation='relu'))
model.add(tf.keras.layers.Dense(10))

model.summary()

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

history = model.fit(x_train, y_train, epochs=100, validation_data=(x_test, y_test))

plt.plot(history.history['accuracy'], label='accuracy')
plt.plot(history.history['val_accuracy'], label = 'val_accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim([0.5, 1])
plt.legend(loc='lower right')

test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)

print(test_acc)
model.save("model.h5")

앞서 구성한 Dataset File을 이용하며, Train이 완료된 후 .h5 Model File을 저장합니다. 이는 Web에서 사용할 File입니다.

위 과정을 수행하면 결과, 약 75%의 Accuracy를 갖습니다.

Image Classifier로서의 역할은 제대로 못하지만 적은 Dataset과 간단한 Model 구조로 꽤 높은 정확도를 갖는 것 같다고 생각해 보죠...ㅎ 

 

Accuracy에 집중한 프로젝트는 아니므로, 여기까지 수행이 제대로 됐다면 이 분류기를 실행시킬 Web 부분을 개발해 보도록 하겠습니다.

 

 

 

 

 

Web

앞서 Image Classifier 개발이 완료되었다면, Web환경에서 해당 기능이 동작하도록 다음과 같이 Django를 통해 Web을 구성합니다.

 

 

Django 환경 조성

다음 명령어를 통해 Django Project를 생성하고 제대로 생성되었는지 runserver를 통해 확인합니다.

>> django-admin startproject core .
>> python manage.py runserver

정상적으로 구성이 되었으면, 이제 본격적인 Web 부분 개발을 진행하도록 하겠습니다.

 

 

Front-End

우선, Front 부분을 간단히 작업할 것입니다.

CSS 및 HTML, JavaScript 관련 Code는 assets, templates Directory에 위치시키고 디자인과 관련된 Image File들은 assets/images Directory에 위치시킵니다.

 

우선, templates Directory에 위치하는 index.html은 다음과 같습니다.

{% load static %}
<!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" />
    <title>CNN Image Prediction Tool</title>
    <link rel="stylesheet" href="{% static 'css/main.css' %}" />
  </head>

  <body>
    <div class="header">
      <h1><span>Image Prediction Tool</span> with Django</h1>
    </div>

    <form method="POST" enctype="multipart/form-data">
      {% csrf_token %}
      <input
        class="custom-file-upload"
        type="file"
        accept="image"
        name="image"
        id="file"
      />
      <button class="compress_image" type="submit" id="submitBtn">
        Predict Animal
      </button>
      <div class="status">
        <p id="message">{{ message }}</p>
      </div>
    </form>

    <div class="wrapper">
      <img
        src="{% if image_url %}{{ image_url }}{% else %}{% static 'images/default.png' %}{% endif %}"
        alt="Compressed Image"
        width="400px"
      />

      <div class="info_container">
        <ul>
          <li>Name: <span class="name">{{ image.name }}</span></li>
          <li>Prediction: <span class="type">{{ prediction }}</span></li>
        </ul>
      </div>
    </div>

    <div class="header">
      <h4><span>Tensored </span>Django</h4>
    </div>

    <script src="{% static 'js/main.js' %}"></script>
  </body>
</html>

메인 Page는 간단한 Page 제목과 판별을 위한 Image를 Upload 하는 부분, Prediction을 위한 Button, Upload 된 Image와 Prediction 된 결과 값이 출력되는 부분으로 구성됩니다.

main.html에서 참조할 assets들은 assets Directory에 위치하며 해당 Directory에는 css, images, js로 구성됩니다.

 

우선, Button Click시 Error를 관리하는 부분은 JavaScript를 통해 구현되며 해당 Script는 다음과 같습니다.

// get element by id message
const message = document.getElementById("message");
const button = document.getElementById("submitBtn");
const file = document.getElementById("file");

// if button is clicked, check the file input
button.addEventListener("click", function (event) {
  if (file.files.length === 0) {
    message.innerHTML = "No File selected, Please select a file!";
  } else {
    message.innerHTML = "Processing Image....";
  }
});

CSS 파일은 디자인과 관련된 부분이기에 크게 언급할 내용은 없으므로 Code만 첨부하도록 하겠습니다.

더보기
더보기

CSS

@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@200;300;400;500;600;800&display=swap");
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
  /* disable selecting of site content */
  /* user-select: none;
  -webkit-user-select: none; */
  font-family: "Montserrat", sans-serif;
}

html {
  scroll-behavior: smooth;
}

:root {
  --main-color: #6ccff6;
  --white-color: #fffffc;
  --black-color: #021418;
  /*------- Font and typography -------*/
  --biggest-font-size: 1.25rem;
  --h1-font-size: 3rem;
  --normal-font-size: 1.2rem;
  --small-font-size: 0.713rem;
  --tiny-font-size: 0.625rem;
}

@media screen and (min-width: 1900px) {
  :root {
    --h1-font-size: 3.2rem;
    --h4-font-size: 2rem;
    --normal-font-size: 1rem;
    --small-font-size: 1rem;
    --tiny-font-size: 0.688rem;
  }
}

body {
  background: var(--white-color);
}

h1 {
  text-align: center;
}

span {
  color: var(--main-color);
  font-weight: 800;
}

h1 {
  font-weight: 800;
  margin: 4vh 2vw;
  font-size: var(--h1-font-size);
}

p {
  font-weight: 400;
  margin: 1vh auto;
  font-size: var(--normal-font-size);
}

a {
  color: var(--main-color);
  text-decoration: none;
  font-weight: 600;
}

button {
  padding: 1rem;
  font-weight: 600;
  font-size: var(--h4-font-size);
  color: var(--white-color);
  background: var(--main-color);
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

ul {
  margin: 10px 0;
  font-size: var(--normal-font-size);
  list-style-type: none;
}

input {
  border: 1px solid var(--main-color);
  border-radius: 6px;
  padding: 10px;
  font-size: var(--normal-font-size);
  width: 100%;
  margin: 10px 0;
}

form {
  margin: 2rem auto;
  width: 100%;
  max-width: 500px;
  display: flex;
  flex-direction: column;
}

/* header with background image */
.header {
  background-size: cover;
  background-position: center;
  height: 30vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background-image: url("../images/dark-cover.png");
  color: var(--white-color);
}

.wrapper {
  margin: 4rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

.info_container li,
.info_container span {
  font-size: var(--normal-font-size);
}

.upload_image {
  background-color: var(--main-color);
  border: none;
  width: fit-content;
  cursor: pointer;
  padding: 1rem 0;
}

#message {
  color: var(--main-color);
  font-size: var(--normal-font-size);
  font-weight: 600;
  margin: 1rem 0;
}

 

위와 같은 과정을 통해 Front 쪽이 구성되었으면 Back 단 개발을 진행합니다.

 

 

 

 

 

Back-End

Django Project 구성 시 생성된 Core Directory의 settings.py Code 중 Template 부분, 및 Path Data 부분을 다음과 같이 수정합니다.

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, "templates")],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

STATIC_URL = '/static/'
MEDIA_URL = "/medias/"

STATICFILES_DIRS = [
    BASE_DIR / "assets",
]

MEDIA_ROOT = BASE_DIR / "media"
STATIC_ROOT = BASE_DIR / "static"

모든 html File들이 저장될 templates Directory를 지정합니다. 또한 static file들을 보관한 asset Directory를 생성하고 해당 경로를 입력해 줍니다. 그리고 Upload 될 이미지 데이터가 저장될 media Path와 관련된 코드를 입력합니다.

 

이후, 사용자가 Image를 Upload 하면 해당 Image를 분석하여 어떠한 동물인지 판별하고 해당 결과 값을 보여주는 과정을 수행하는 Script를 생성합니다. 해당 Script는 core Directory의 views.py File입니다.

import os
import cv2
from PIL import Image
import numpy as np
import tensorflow as tf
from django.conf import settings
from django.template.response import TemplateResponse
from django.utils.datastructures import MultiValueDictKeyError
from django.core.files.storage import FileSystemStorage

class CustomFileSystemStorage(FileSystemStorage):
    def get_available_name(self, name, max_length=None):
        self.delete(name)
        return name

def index(request):
    message = ""
    prediction = ""
    fss = CustomFileSystemStorage()
    try:
        image = request.FILES["image"]
        print("Name", image.file)
        _image = fss.save(image.name, image)
        path = str(settings.MEDIA_ROOT) + "/" + image.name

        # image details
        image_url = fss.url(_image)

        # Read the image
        img=cv2.imread(path)
        img_from_ar = Image.fromarray(img, 'RGB')
        resized_image = img_from_ar.resize((50, 50))
        test_image =np.expand_dims(resized_image, axis=0) 

        # load model
        model = tf.keras.models.load_model(os.getcwd() + '/CNN/model.h5')
        result = model.predict(test_image) 
        # ----------------
        # LABELS
        # Cat 0, Dog 1, Monkey 2, Parrot 3, Elephant 4, Bear 5
        # ----------------
        print("Prediction: " + str(np.argmax(result)))

        if (np.argmax(result) == 0):
            prediction = "Cat"
        elif (np.argmax(result) == 1):
            prediction = "Dog"
        elif (np.argmax(result) == 2):
            prediction = "Monkey"
        elif (np.argmax(result) == 3):
            prediction = "Parrot"
        elif (np.argmax(result) == 4):
            prediction = "Elephant"
        elif (np.argmax(result) == 5):
            prediction = "Bear"
        else:
            prediction = "Unknown"
        
        return TemplateResponse(
            request,
            "index.html",
            {
                "message": message,
                "image": image,
                "image_url": image_url,
                "prediction": prediction,
            },
        )
    except MultiValueDictKeyError:

        return TemplateResponse(
            request,
            "index.html",
            {"message": "No Image Selected"},
        )

main으로 동작하는 Function은 index function입니다. 해당 Script는 urls.py에 Link 되어야 합니다.

작성한 Code들을 Web에서 띄우기 위해 urls.py File을 다음과 같이 수정합니다.

from django.conf.urls.static import static
from django.conf import settings
from django.contrib import admin
from django.urls import path
from .views import index

urlpatterns = [
    path("", index, name="index"),
    path('admin/', admin.site.urls),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

views.py에 작성한 index Function이 Load 될 수 있도록 하는 Code와 Image Upload시 Data가 관리될 Path관련 Code를 추가한 것입니다.

 

 

Result

여기까지 완료되었다면 localhost:8000에 접속하여 개발한 결과물을 확인해 봅니다.

Result

분류하고 싶은 동물 Image를 Upload 하고 Predict Animal Button을 Click 하면 몇 초 뒤, 결과 값이 출력됩니다.

고양이 사진을 Upload 하였더니 분류 결과가 Cat이 나왔습니다. 그리고 Upload 한 Image File은 media Directory에 저장되었습니다.

 

비교적 Django로 간단하게 WEB을 만들 수 있었습니다. 게다가 나름의 AI를 탑재한 WEB이었지요~

이번 포스트는 여기서 마무리하도록 하겠습니다.

반응형

댓글