アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

Djangoでアップロードした画像をCNNで予測し、結果を返すアプリを作ってみた(画像認識、機械学習)

kerasで作った画像分類器に画像を読み込ませ、予測したラベルのidを返すアプリ作った。以前、rubyで作ったことがあるけど、今回はpython専用のフレームワークDjangoを使って作成。

画像分類器にはCNNを使ったので、GPUとか学習のところは割愛して、アプリ作成過程についてだけ書いてみる。

目次
1.アプリについて
2.メインのファイル



1.アプリについて


一通りの動作
デフォルトのホーム画面はこんな感じでシンプル

写真をアップロードして、クリックすると予測した確率が高いトップ3のラベルのidが表示される仕組み


アプリ名と構成ファイル
project名はimage_pred

アプリ名はmyapp

メインの構成ファイルは
・setting.py
・forms.py
・views.py
・index.html
・main.py
・urls.py

いろんなファイルを保存するディレクトリは、必要に応じて作成した。




MVCフレームワークについて

DjangoでのMVCフレームワーク

・Mはmodels.pyでデータベースの操作

・Vはviews.pyで画面の表示を操作

・Cはurls.pyでアクセス関係の操作

今回はデータベースにデータを保存しなくていいので、models.pyは使わない。使うときはマイグレーションが必要で、使えるデータベースは任意に指定可能

便利なパッケージでdjango-cleanupがあった。データベースを使うならpipでインストールして、setting.pyのINSTALLED_APPSに入れておきたい





2.メインのファイル


まずパッケージは
・Pillow
・keras
・tensorflow
opencv

とかをpipでインストール





setting.py
myapp作成後に、INSTALLED_APPSにmyappを追加。

画像をアップロードするので以下のディレクトリを設定
・MEDIA_ROOT
=>サーバから見たメディアルートの絶対パス

・MEDIA_URL
=>メディアファイル公開時のURLのプレフィクス。 メディアファイルのURLは「http://アプリのドメイン+MEDIA_URL+メディアファイル名」

# 一部掲載
import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

SECRET_KEY = '8bdf)xh2b4h!8#g$i0j_9pymjld*ov19!rpfgj2qsi6j)-$t9d'

DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp' # 追加
]

STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'






forms.py
ここは画像をアップロードする専用のフォームを作成するファイル
画像専用フォーム「forms.ImageField()」を使用

from django import forms

class PhotoForm(forms.Form):
    
    image = forms.ImageField()






index.html

ここはメインのホーム画面を表示するhtml。主にしてる処理は
・main.pyで予測したラベルのidをhtmlの変数に埋め込み、レンダリング
・フォームの設定
・静的ファイル(CSS)の読み込み
など。htmlにpythonの変数を直接、埋め込めるのがDjangoの強み

{% load static %}
<!doctype html>
<html lang="ja">
<head>
 <meta charset="utf-8">
 <title>img_prediction</title>
 <link rel="stylesheet" type="text/css"
      href="{% static 'myapp/css/style.css' %}" />
</head>
<body>
<p>{{pred}}</p>
<table>
<form action="{% url 'index' %}" method="POST" enctype="multipart/form-data">
   {% csrf_token %}
   {{ form }}
   <tr><td></td><td><input type="submit" value="click" /></td></tr>
</form>
</table>
</body>
</html>


静的ファイルは{% load フォルダ名 %}を記述してCSSを読み込ませた。 staticファルダ内にstyle.cssが保存してある。javascriptとかも同じように読み込める

変数は{{pred}}の部分。

フォームはformタグ内に記述。画像の場合はenctype="multipart/form-data"を指定しなきゃだめ







main.py
コードはkerasで作成した学習済みの画像分類器(CNN)と予測結果を返す処理。学習済みの重み、id用のcsvファイルは専用フォルダを別途作成して、保存してる。

# pred関数のみ
def pred(img_path):
    sess = tf.Session()
    K.set_session(sess)
    cnt=pd.read_csv('/Users/d/image_pred/myapp/weight_dir/cnt.csv')
    cnt=cnt.drop('Unnamed: 0', axis=1)
    cnt=cnt.drop('cnt', axis=1)
    
    
    dic = {}
    for i, v in cnt.iterrows():
        dic.setdefault(v['index'], []).append([v['brand_id'], v['model_id'], v['cate_id']])
    
    x=np.asarray(Image.open(img_path))
    x = cv2.resize(x, (100, 100))
    x = np.expand_dims(x, axis=0)
    image = preprocess_input(x)

    test_model = InceptionResNetV2(include_top=True)
    test_model.load_weights('/Users/d/image_pred/myapp/weight_dir/incep_model.h5')
    test_model.compile(optimizer=SGD(lr=0.01, momentum=0.9, decay=0.001, nesterov=True),
                    loss='categorical_crossentropy', 
                    metrics=['accuracy'])

    y_pred = test_model.predict(image)
    top_k=sess.run(tf.nn.top_k(y_pred,k=3,sorted=True))
    idxs=list(np.reshape(top_k[1],(3,)))
    id=[]
    for idx, v in dic.items():
        if idx in idxs:
            id.append(['brand_id:{} model_id:{}, cate_id:{}'.format(v[0][0], v[0][1], v[0][2])])
    return id





views.py
ここは、index.htmlへのレンダリングなど、表示関連の処理を書くとこ。

主に、get時の処理とpost時の処理を関数に分けて書いてある。get時の処理はアクセス時のデフォルトの画面表示。

post時の処理はアップロードして画像を受け取って読み込み、予測したラベルのidを返す処理を書いてる。
フォームからアップロードされた画像を読み込み予測ラベルを返す処理はmain.pyのpred関数をimportで呼び出す。そして、変数を代入して、その結果をhtmlにレンダリング。画像じゃなきゃエラーを返す仕組み

from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.views.generic import TemplateView
from .forms import PhotoForm
from myapp.main import pred

class MyappView(TemplateView):
   def __init__(self):
       self.params={'pred': 'idx',
                    'form': PhotoForm()}
   def get(self, req):
       return render(req, 'myapp/index.html', self.params)

   def post(self, req):
       form = PhotoForm(req.POST, req.FILES)
       if not form.is_valid():
           raise ValueError('invalid form')

       image = form.cleaned_data['image']
       self.params['pred'] = pred(image)
       return render(req, 'myapp/index.html', self.params)





urls.py

Djangoではプロジェクトの下にいくつもアプリを作れる。Djangoでは「1アプリ=1 url」が基本。つまりurls.pyはアプリを作るたびに、プロジェクトの階層以外にも。アプリ内に必ず一つurls.pyを配置する仕組み




・プロジェクトの階層のurls.py
プロジェクトの階層では全てのアプリのurls.pyを管理するpathを指定する。「アプリのことは各アプリ内のurls.pyに聞け」って意味のinclude()を使う

urlpatterns = [
   path('admin/', admin.site.urls),
   path('myapp/', include('myapp.urls')),]


・各アプリ内のurls.py
各アプリ内ではurlspatternにurlを指定する。引数は3つあって、1つめはhttp://~/myapp/の後に続くアドレス、2つめはアクセスするファイル、3つめは指定したurlの名前


from django.conf.urls import url
from .views import MyappView
urlpatterns = [
              url(r'', MyappView.as_view(), name='index'),
              ]


画像をアップロードするたびにファルダにurlと画像が保存されるようにするには以下を追加。

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




動作確認

画像をアップロードしてクリックを押す

すると、予測ラベルのidが表示される。

idを返す超単純なアプリを作成した。Djangoの勉強とアウトプットには、やっぱり基礎を抑えて自分でアプリとか作るのが一番っぽい


参考記事

Django - 画像ファイルのアップロード処理

Django-ファイルアップロード機能の使い方 (基本設定編)

Djangoで、けものフレンズキャラの顔を認識させる(Deep Learning)