この記事では、作ったAIをWebアプリにする方法をお教えします。できるだけ簡単にコピペで作ることを目的としますので、詳しいプログラムの解説は省きます。
仕様
ここで作るWebアプリはこんな内容です。
- 作るもの : 任意の画像をアップロードすると画像に何が写っているか識別するWebアプリ。
- 使用するAIモデル : 画像分類型CNN
- 使用する環境 : パソコン(WindowsまたはMacintosh)、Python、Django※
※ DjangoはPythonライブラリの一つで、WebとPythonの橋渡しやWebサーバを提供するものです。以降でインストール方法も書きます。
※ 今回はローカルPCでWebサーバを起動します。
作成手順
PC環境
1.下記の記事でパソコンの環境を準備してください。Pythonが使えるようにしましょう。
バックエンド(AI)を作る
Webアクセス者からは見えない裏側の仕組みをバックエンドといいます。今回はAIの部分にあたります。
2.下記の記事をそのまま実施してください。画像分類AIを1回実行し、学習済みファイルを作ってください。「model_cnn_cifer.h5」を後ほど利用します。
フロントエンドと連携部分を作る
バックエンドに対し、Webアクセス者から見えるページや機能をフロントエンドといいます。
3.Djangoとその他必要なライブラリをインストールします。
1 2 3 |
pip install django pip install django-bootstrap4 pip install tensorflow==1.14 |
4.Djangoの機能を使って、プロジェクトを作成します。
1 |
django-admin startproject aipj |
5.作成したプロジェクトの中でアプリを作成します。
1 2 |
cd aipj python manage.py startapp cnnapp |
6.アプリフォルダに作られたビュー「views.py」に追記します。ビューはユーザからのhttpリクエストに対する動作ルールを書いたものです。aipj / cnnapp / views.py を開き、次のように変更してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
from django.http import HttpResponse from django.shortcuts import render, redirect from django.template import loader from .forms import PictForm from .models import Pict def index(request): template = loader.get_template('cnnapp/index.html') context = {'form': PictForm()} return HttpResponse(template.render(context, request)) def predict(request): if not request.method == 'POST': return redirect('cnnapp:index') form = PictForm(request.POST, request.FILES) if not form.is_valid(): raise ValueError('Form is illegal.') pict = Pict(image=form.cleaned_data['image']) predicted, rate = pict.predict() template = loader.get_template('cnnapp/result.html') context = { 'pict_name': pict.image.name, 'pict_data': pict.image_src(), 'predicted': predicted, 'rate': rate, } return HttpResponse(template.render(context, request)) |
7.アプリフォルダに作られたモデル定義「models.py」に追記します。aipj / cnnapp / models.py を開き、次のように変更してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D from tensorflow.keras.layers import MaxPool2D from tensorflow.keras.optimizers import Adam from tensorflow.keras.layers import InputLayer, Dense, Activation, Dropout, Flatten from django.db import models import numpy as np from PIL import Image import io, base64 class Pict(models.Model): image = models.ImageField(upload_to='picture') IMAGE_SIZE = 32 MODEL_FILE_PATH = './carbike/trainedmodel/model_cnn_cifer.h5' CLASSES = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] NUM_CLASSES = len(CLASSES) def predict(self): # create model model = Sequential() model.add(InputLayer(input_shape=(32,32,3))) model.add(Conv2D(32,3)) model.add(Activation('relu')) model.add(Conv2D(32,3)) model.add(Activation('relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Conv2D(64,3)) model.add(Activation('relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Flatten()) model.add(Dense(1024)) model.add(Activation('relu')) model.add(Dropout(0.5)) model.add(Dense(self.NUM_CLASSES, activation='softmax')) model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=["accuracy"]) model.summary() # predict img_data = self.image.read() img_bin = io.BytesIO(img_data) image = Image.open(img_bin) image = image.convert("RGB") image = image.resize((self.IMAGE_SIZE, self.IMAGE_SIZE)) data = np.asarray(image) / 255.0 X = [] X.append(data) X = np.array(X) result = model.predict([X])[0] predicted = result.argmax() rate = int(result[predicted] * 100) print(self.CLASSES[predicted], rate) return self.CLASSES[predicted], rate def image_src(self): with self.image.open() as img: base64_img = base64.b64encode(img.read()).decode() return 'data:' + img.file.content_type + ';base64,' + base64_img |
8.プロジェクトフォルダに作られたリダイレクトファイル「url.py」に追記します。Webサーバにアクセスされた時、そのURLによって見せるページを定義するものです。aipj / aipj / url.py を次のように変更してください。
1 2 3 4 5 6 7 8 9 10 |
""" コメント部分 """ from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('cnnapp/', include('cnnapp.urls')), ] |
9.アプリフォルダに新たに「url.py」を作成します。aipj / cnnapp / url.py を作り次のように書いてください。
1 2 3 4 5 6 7 8 9 |
from django.urls import path from . import views app_name ='cnnapp' urlpatterns = [ path('', views.index, name='index'), path('predict/', views.predict, name='predict'), ] |
10.動的にWebページを表示するためのhtmlを3つ作成します。aipj / cnnapp / templates / cnnapp / というフォルダを新たに作成してください。その中に、下のファイル名と内容で3つ保存してください。
共通テンプレート base.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> {% load static %} {% load bootstrap4 %} {% bootstrap_css %} <link rel="stylesheet" type="text/css" href="{% static 'cnnapp/css/style.css' %}"> {% bootstrap_javascript jquery='full' %} <title>画像分類AI | {% block title %}{% endblock %}</title> </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="#">画像分類AI</a> </nav> <div class="container"> {% block content %}{% endblock %} </div> </body> </html> |
トップ画面 index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
{% extends 'cnnapp/base.html' %} {% block title %}画像分類AI{% endblock %} {% block content %} <div> <br> <p>画像ファイルを指定し、分類実行ボタンをクリックしてください。</p> <form action="{% url 'cnnapp:predict' %}" method="post" class="form" enctype="multipart/form-data"> {% csrf_token %} <div class="form-group"> <div class="custom-file"> {{ form.image }} <label class="custom-file-label" for="customFile"> ファイル指定 </label> </div> </div> <button type="submit" class="btn btn-primary">分類実行</button> </form> </div> {% endblock %} |
結果表示画面 result.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
{% extends 'cnnapp/base.html' %} {% block title %}画像分類結果{% endblock %} {% block content %} <div> <h4 class="mt-4 mb-5 border-bottom">画像分類結果</h4> <table class='table'> <tbody> <tr> <td>ファイル名</td> <td>{{ pict_name }}</td> </tr> <tr> <td>画像ファイル</td> <td><img class='preview-img' src={{ pict_data }} ></img></td> </tr> <tr> <td>分類名</td> <td>{{ predicted }}</td> </tr> <tr> <td>分類確率</td> <td>{{ rate }} %</td> </tr> </tbody> </table> <a href = "{% url 'cnnapp:index' %}" class="btn btn-primary">画像指定に戻る</a> </div> {% endblock %} |
11.Webページ上で画像を選択するためのフォームを定義します。aipj / cnnapp / forms.py を新たに作り、次のように書いてください。
1 2 3 4 |
from django import forms class PictForm(forms.Form): image = forms.ImageField(widget=forms.FileInput(attrs={'class':'custom-file-input'})) |
12.Djangoがデータベースへアクセスするための設定をします。aipj / aipj / settings.py を開いて次のように追記してください。
1 2 3 4 5 6 |
INSTALLED_APPS = [ 'django.... 'django.... 'cnnapp.apps.CnnappConfig', #追記 'bootstrap4', #追記 ] |
13.aipj / cnnapp / static / cnnapp / css / style.py という中身の無いファイルを作ってください。ファイル構成として必要です。
1 |
中身無し |
14.aipj / media / pict / というフォルダを作ってください。フォルダ構成として必要です。ファイルは必要ありません。
15.aipj / cnnapp / trainedmodel / というフォルダを作り、学習済みモデル「model_cnn_cifer.h5」を置いてください。
以上で全てのプログラム作成が完了です。
Webサーバの起動
16.次のように実行します。しばらく待つとWebサーバが起動します。今回はDjangoに内蔵されている簡易Webサーバを用います。
1 2 3 4 5 6 |
python manage.py startapp を実行した同じフォルダで次を実行。 python manage.py runserver しばらく待ち、最後に次が表示されれば成功です。 Starting development server at http://127.0.0.1:8000/ Quit the server with CTRL-BREAK. |
Webサーバへアクセス
17.ブラウザを開き、http://127.0.0.1:8000/cnnapp/ へアクセスします。下のようなページが表示されれば、これがAIのWebアプリです。
18.「ファイル指定」を押し、任意の画像ファイル選択し、「開く」を押します。
19.「分類実行」を押すと、AIで画像が分類され、結果が出ます。
以上でWebアプリの作成は完了です。
コマンドで動かしていたAIをこうしてWeb化すると、より身近に感じますね。
今回はDjangoに内蔵されている簡易Webサーバと簡易DBを使いました。
Webサーバを公開し多くのアクセスを受ける場合は、負荷に耐えられるようグレードアップしたほうがよいです。WebサーバはNGINXかApacheへ、DBはPostgresがいいでしょう。