「画像でゴミ分類！」アプリ作成日誌day3～Djangoでwebアプリ化～

# はじめに
「画像でゴミ分類！」アプリ作成日誌3日目の今日はDjangoを使ってwebアプリにしていきたいと思います。

＜記事一覧＞

- [「画像でゴミ分類！」アプリ作成日誌day1～データセットの作成～](https://qiita.com/eycjur/items/7d8223b28758c7dfaaa0)
- [「画像でゴミ分類！」アプリ作成日誌day2～VGG16でFine-tuning～](https://qiita.com/eycjur/items/3e954cb70dc15f996c2d)
- [「画像でゴミ分類！」アプリ作成日誌day3～Djangoでwebアプリ化～](https://qiita.com/eycjur/items/9c618538177c82f7fdc3) ←イマココ
- [「画像でゴミ分類！」アプリ作成日誌day4～Bootstrapでフロントエンドを整える～](https://qiita.com/eycjur/items/7b58c28eb8b16e722b5d)
- [「画像でゴミ分類！」アプリ作成日誌day5～Bootstrapでフロントエンドを整える2～](https://qiita.com/eycjur/items/08808097acd625e00652)
- [「画像でゴミ分類！」アプリ作成日誌day6～ディレクトリ構成の修正～](https://qiita.com/eycjur/items/f436787c2d517af6cad6)
- [「画像でゴミ分類！」アプリ作成日誌day7～サイドバーのスライドメニュー化～](https://qiita.com/eycjur/items/009dca5866c3e9ad0dd6)
- [「画像でゴミ分類！」アプリ作成日誌day8～herokuデプロイ～](https://qiita.com/eycjur/items/ebfae5db5dd1cd8349ea)

# 前回までのあらすじ
前回の記事ではVGG16でFine-tuningすることでモデルを作成しました。今回はこのモデルをローカルのブラウザ上で実行できるようにすることを目標とします。

# 大まかな流れ
流れとしては、まずアプリの大元の設定をします。次に画像をアップロードできるようにフォームを作成します。その後前回作ったモデルとがっちゃんこしてきます。今回はとりあえず最低限の形だけ作ってhtmlやcssの整備は次回以降に回す形にしようと思っています。

# Djangoアプリの作成
まず、Djangoアプリを作るコードをコンソール上で実行します。VSCodeでフォルダごと開くので、そうじゃない人はまずカレントディレクトリに移動させてください。

```console:console
django-admin startproject garbage_proj
cd garbage_proj
python manage.py startapp garbage
```
これで基本的なものはできているのでここからいじっていきます。

# settingの設定
まず、`garbage_proj/setting.py`を書き換えていきます。やり方含めてコードブロック上に書いていきます。

```py:garbage_proj/setting.py

INSTALLED_APPSに"garbage"追加

# 以下は書き換える
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
```

モデルについては今回は画像の保存をするような機能を付けないので必要ありません。
フォームは画像をアップロードする用のものを作っておきます。`forms.py`を新たに作成して以下を書いていきます。

```py:garbage/forms.py

from django import forms
class UploadPictureForm(forms.Form):
	img = forms.ImageField(required=True, label="")
```
labelはhtmlで表示するときの問題ですが、`{{ form }}`とするとlabel含めて表示されてしまうので空白にしています。（自分で要素を列挙するなら特に設定する必要はないと思います）

# URL周りの設定
まず、大元のURLのルーティングを設定します。`garbage_proj`内の`urls.py`に追加していきます。

```py:garbage_proj/urls.py

from django.urls import include

urlpatterns = [
    # path('admin/', admin.site.urls),
    path("garbage/", include("garbage.urls")),
    path("", include("garbage.urls"))
]
```

`include("garbage.urls")`と指定すると`garbage`フォルダ内の`urls.py`を参照するように処理を飛ばすことができます。adminは使わないのでコメントアウトしておきます。
また、`""`の際のルーティングを設定しておくとサーバーを起動した際に表示されるリンク踏んだ時に直で開けるので地味に便利です。

次に`garbage/urls.py`を新たに作成して`garbage`に処理を飛ばした際の処理を書いていきます。

```py:garbage/urls.py

from django.urls import path
from . import views
urlpatterns = [
    path("", views.index, name="index"),
    path("result", views.result, name="result"),
]
```

# Viewの設定
Viewsにはサーバー側で行う処理を記述していきます。

```py:garbage/views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.views.generic import TemplateView
from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required
from .forms import UploadPictureForm
from PIL import Image


def index(request):
    params = {
        "form":UploadPictureForm()
    }
    return render(request, "garbage/index.html", params)


def result(request):
    form = UploadPictureForm(request.POST, request.FILES)
    if form.is_valid():
        img = form.cleaned_data["img"]
        pred = predict(img)
    else:
        params = {
            "form":UploadPictureForm()
        }
        return render(request, "garbage/index.html", params)

    params = {
        "img":img,
        "pred":pred
    }
    return render(request, "garbage/result.html", params)
    # いったんここまで
```

`result`の関数内では`request.FILES`で画像が認識されているのでそれを取り出す処理をしています。また、`form.cleaned_data`とすることで分かりやすい形にして返してくれます（`form.is_valid`を確かめた後でしか使えないらしいです）。


```py:garbage/views.py
# 上のコードブロックからの続き
def predict(img):
    # 読み込み
    import numpy as np
    import matplotlib.pyplot as plt
    from keras.preprocessing import image
    from keras.models import model_from_json
    from PIL import Image

    model = model_from_json(open("../model.json").read())
    model.load_weights('../param.hdf5')

    img_width, img_height = 150, 150
    img = Image.open(img)
    img.save("image.png")
    img = np.array(img.resize((img_width, img_height)))
    classes = ['不燃ごみ', '包装容器プラスチック類', '可燃ごみ', '有害ごみ', '資源品']

    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = x / 255.0

    # 画像の人物を予測
    pred = model.predict(x)[0]
    # 結果を表示する
    pred_dict = {c:s for (c, s) in zip(classes, pred*100)}
    pred_dict = sorted(pred_dict.items(), key=lambda x:x[1], reverse=True)
    return pred_dict
```
基本的には前回のモデルをそのままですが、パスなどはいい感じになるように直しました。
また、画像ファイルについてはそのままでは`'InMemoryUploadedFile'`のような形式になっているのでPillowで読み直しています。

# Templateファイルの作成
Template(HTMLの元ファイルみたいなやつ)を作成していきます。ファイルを置く場所はtemplatesフォルダを作ってその中に置くようにしてください。

```django:garbage/templates/garbage/index.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>画像でゴミ分類！</title>
	<link rel="stylesheet" type="text/css" href="{% static 'garbage/css/style.css' %}" />
</head>
<body>
	<h1>画像でゴミ分類！</h1>
	<p>分類を調べたい画像を入力してください</p>
	<form action="/garbage/result" method="post" enctype="multipart/form-data">
		{% csrf_token %}
		{{ form }}
		<br>
		<button type="submit">送信</button>
	</form>
</body>
</html>
```
今回はホントに簡単に書いただけで失礼しますが、以下のような画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678160/2684b8ff-12e5-6e4c-530e-2d15285e56a6.png)

次に結果の画面を作成していきます。

```django:garbage/templates/garbage/result.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>画像でゴミ分類！</title>
	<link rel="stylesheet" type="text/css" href="{% static 'garbage/css/style.css' %}" />
</head>
<body>
	<h1>画像でゴミ分類！</h1>
	<p>結果は</p>
	<img src="../image.png" alt="画像">
	<table>
		<tr><th>分類</th><td>確率</td></tr>
		{% for key, value in pred %}
			<tr><th>{{ key }}</th><td>{{ value }}</td></tr>
		{% endfor %}
	</table>
	<a href="{% url "index" %}">Top</a>

</body>
</html>
```
templateファイル内で`{% %}`のように書くことでpythonのコードを書くことができます。for文の書き方とかはちょっと特殊になりますがこのように書きます。そして画像をアップロードして実行すると以下のような画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678160/3415cdf6-866c-fb15-8dd3-e8fa2654be9e.png)
画像に関してpathの設定がちょっとわからないとこがあって表示されていないですが、プログラム自体はいい感じに動いていそうです！

そういえばCSSにかんしては何も記述していないですが、ファイルだけ置いていてHTMLにもかかるようになっていると思います。

```css:garbage/static/garbage/css/style.css
/*  */
```

次回はフロントエンドをもうちょっと作りこんでいこうと思います。

＜記事一覧＞

- [「画像でゴミ分類！」アプリ作成日誌day1～データセットの作成～](https://qiita.com/eycjur/items/7d8223b28758c7dfaaa0)
- [「画像でゴミ分類！」アプリ作成日誌day2～VGG16でFine-tuning～](https://qiita.com/eycjur/items/3e954cb70dc15f996c2d)
- [「画像でゴミ分類！」アプリ作成日誌day3～Djangoでwebアプリ化～](https://qiita.com/eycjur/items/9c618538177c82f7fdc3) ←イマココ
- [「画像でゴミ分類！」アプリ作成日誌day4～Bootstrapでフロントエンドを整える～](https://qiita.com/eycjur/items/7b58c28eb8b16e722b5d)
- [「画像でゴミ分類！」アプリ作成日誌day5～Bootstrapでフロントエンドを整える2～](https://qiita.com/eycjur/items/08808097acd625e00652)
- [「画像でゴミ分類！」アプリ作成日誌day6～ディレクトリ構成の修正～](https://qiita.com/eycjur/items/f436787c2d517af6cad6)
- [「画像でゴミ分類！」アプリ作成日誌day7～サイドバーのスライドメニュー化～](https://qiita.com/eycjur/items/009dca5866c3e9ad0dd6)
- [「画像でゴミ分類！」アプリ作成日誌day8～herokuデプロイ～](https://qiita.com/eycjur/items/ebfae5db5dd1cd8349ea)