LoginSignup
4

More than 5 years have passed since last update.

DjangoとJava for Androidで手書き数字を判定するだけのサーバーを立てて判定するアプリを作った。①サーバー側編 【DjangoのマルチーパートPOST】

Last updated at Posted at 2019-01-02

1 はじめに

keyword: Django Apache wsgi Ubuntu Android keras tensorflow MNIST VGG16

1.1 経緯

僕は常頃からAndroidメインでやっている人なんですが、サーバーとかに関するもの(いわゆるオープン系)は触ったことないのでやってみようと思ったのが最初です。そこからhttpとかポートとかPOSTとGETとかいろいろ覚えて、何かいいものないかなと言うことで「アプリ側はAndroid、サーバー側はPython」とかいうのを思いついてしまった次第です。結構頑張って書いた記事なので既読感覚でいいねをしてもらっていいですか!!よろしくお願いしまーす。

1.2 初のオープン系

POSTっちゅうのがあるみたいですね。GETは前の記事のQueryか。なるほど、、、。あっそう。
って感じで流しました。実装ではあまり考えませんでしたから支障はないでしょう。結果としてオープン系は異言語をつなげることができるんだなーと思ったわけですし。

1.3 スペック、環境(注意:pythonは3.6以下のものを使ってください!そうしないと死にます。私が経験済みです!!!!!)

2019/1/1 現在
OS : Windows10(64bit) #使いませんでした
   : Ubuntu 18.04.1 LTS(64bit) #こっちのみ
プロセッサ(参考): Intel® Core™ i7-2700K CPU @ 3.50GHz × 8
Python version : 3.6.7 #ココ重要
Apache2 version: 2.4.29
Django version : 2.1.4
Pip version    : pip 18.1 from /usr/local/lib/python3.6/dist-packages/pip (python 3.6)

Pythonライブラリのバージョン↓

Package                 Version  
----------------------- ---------
absl-py                 0.6.1    
apturl                  0.5.2    
asn1crypto              0.24.0   
astor                   0.7.1    
Brlapi                  0.6.6    
certifi                 2018.1.18
chardet                 3.0.4    
command-not-found       0.3      
cryptography            2.1.4    
cupshelpers             1.0      
defer                   1.0.6    
distro-info             0.18     
Django                  2.1.4    
gast                    0.2.0    
grpcio                  1.17.1   
h5py                    2.9.0    
httplib2                0.9.2    
idna                    2.6      
Keras                   2.2.4    
Keras-Applications      1.0.6    
Keras-Preprocessing     1.0.5    
keyring                 10.6.0   
keyrings.alt            3.0      
language-selector       0.1      
launchpadlib            1.10.6   
lazr.restfulclient      0.13.5   
lazr.uri                1.0.3    
louis                   3.5.0    
macaroonbakery          1.1.3    
Mako                    1.0.7    
Markdown                3.0.1    
MarkupSafe              1.0      
mod-wsgi                4.6.5    
netifaces               0.10.4   
numpy                   1.13.3   
oauth                   1.0.1    
olefile                 0.45.1   
opencv-contrib-python   3.4.5.20 
opencv-python           3.4.5.20 
pexpect                 4.2.1    
Pillow                  5.3.0    
pip                     18.1     
protobuf                3.6.1    
pycairo                 1.16.2   
pycrypto                2.6.1    
pycups                  1.9.73   
pygobject               3.26.1   
pymacaroons             0.13.0   
PyNaCl                  1.1.2    
pyRFC3339               1.0      
python-apt              1.6.3    
python-debian           0.1.32   
pytz                    2018.3   
pyxdg                   0.25     
PyYAML                  3.12     
reportlab               3.4.0    
requests                2.18.4   
requests-unixsocket     0.1.5    
scipy                   1.2.0    
screen-resolution-extra 0.0.0    
SecretStorage           2.3.1    
setuptools              39.0.1   
simplejson              3.13.2   
six                     1.11.0   
ssh-import-id           5.7      
system-service          0.3      
systemd-python          234      
tensorboard             1.12.1   
tensorflow              1.12.0   
termcolor               1.1.0    
ubuntu-drivers-common   0.0.0    
ufw                     0.35     
unattended-upgrades     0.1      
urllib3                 1.22     
usb-creator             0.3.3    
wadllib                 1.3.2    
Werkzeug                0.14.1   
wheel                   0.30.0   
xkit                    0.0.0    
zope.interface          4.3.2

もしなぜかエラーが出てくる場合にはバージョンを確認してみてください。

2 サーバー側の実装

2.1 Python・Django・Apache2・wsgiのインストールと新しいプロジェクトの立ち上げ方

これが手本です↓ありがとうございます。
https://qiita.com/itisyuu/items/dafa535adc8197208af1
とても参考になるのでいろいろ助けてもらいました。

Python

$ sudo apt install python3-pip python3-dev
$ sudo apt install python-pip python-dev
$ python -V
Python 3.6.7

必要ならばpython3と打てばpython3.6が呼び出されるようにしてください。

Django

$ pip3 install django
$ apt install python-django-common
$ django-admin --version

Apache

$ sudo apt-get update
$ sudo apt-get install apache2
$ apt-get install apache2-dev
$ systemctl restart apache2

ここにアクセスしてください↓
http://localhost/
ここはあなたが使用しているパソコンのサーバーです。ここでIt Works!と出力されるとインストールできているということです。
こちらでもかまいません。
http://127.0.0.1/
127.0.0.1はIPアドレスといい、ネットワーク上の住所です。127.0.0.1は自分を表します。
以下のコマンドで自分のIPアドレスを知ることができます。

$ ifconfig
enp13s0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether xxxxxxxxxx  txqueuelen 1000  (イーサネット)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 17  

enx3476c5229d10: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.xxx.x.x  netmask 255.255.255.0  broadcast 192.xxx.x.255
        inet6 xxxxxxxxxxxxxxxxxxxx  prefixlen 64  scopeid 0x20<link>
        ether xxxxxxxxxx  txqueuelen 1000  (イーサネット)
        RX packets 55892  bytes 24881116 (24.8 MB)
        RX errors 0  dropped 722  overruns 0  frame 0
        TX packets 33614  bytes 11814780 (11.8 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (ローカルループバック)
        RX packets 5036  bytes 1293963 (1.2 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5036  bytes 1293963 (1.2 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

この場合だと10行目の192.xxx.x.xがIPアドレスです。
http://192.xxx.x.x/
にアクセスすれば同じものが見れるはずです。

mod_wsgi

ApacheとDjangoを連携するモジュール(mod)です。

$ pip3 install mod_wsgi
$ sudo mod_wsgi-express install-module

ここでwsgiのインストールに使ったpip3がpython3.6配下のものであるか確認してください

DjangoとApacheをwsgiでくっつける

$ mod_wsgi-express install-module

これで出てきたものをコピーしましょう。ちなみに私はこれでした。↓(python3.6の人は下と同じものが出てくるかもです。)

LoadModule wsgi_module "/usr/lib/apache2/modules/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so"
WSGIPythonHome "/usr"

そして

$ vi /etc/apache2/apache2.conf 

これでvimが開きます。なれない人は

$ gedit /etc/apache2/apache2.conf &

その最終行にさっきコピーしたものをそのまま入れます。そして閉じましょう!保存を忘れずに。


ここまでがインストール等です。新しいプロジェクトを生成しましょう。

プロジェクトの立ち上げ方

以下のコマンドで新しいdjangoのプロジェクトを作りましょう。
今回はサイト名(プロジェクトの名前)をDjangoApp001とします。

~$ mkdir DjangoApp001
~$ cd DjangoApp001
~$ django-admin startproject DjangoApp001

以下のコマンドで新しいApacheのサイトを作りましょう。/etc/apache2/sites-available/にはサイトの設定ファイルがいくつもあります。ここで新しいサイトを作るのです。

$ vi /etc/apache2/sites-available/django.conf
or
$ gedit /etc/apache2/sites-available/django.conf &

開いた無地ファイルの中に以下のことを入力します。
(username)はしっかり変えてください。

django.conf
<VirtualHost *:80>
  WSGIDaemonProcess DjangoApp001 python-home=/usr python-path=/home/(username)/DjangoApp001
  WSGIScriptAlias / /home/(username)/DjangoApp001/DjangoApp001/wsgi.py process-group=DjangoApp001
  <Directory /home/(username)/DjangoApp001/DjangoApp001>
    <Files wsgi.py>
      Require all granted
    </Files>
  </Directory>
</VirtualHost>

ファイルの構成はこうなります。(django-adminをした時点でこのような構成にはなっています。)

ここまでのファイル構成
/home
  /(username)
       /DjangoApp001
              /DjangoApp001
                    wsgi.py

さて、これからサーバー側に変更を加えるごとにサーバーの再起動をしなければなりません。その時は以下のようにすればOKです。

ストップ
$ sudo systemctl stop apache2
スタート
$ sudo systemctl start apache2
再起動(ストップ&スタート)
$ sudo systemctl restart apache2

わかっていると思いますがここで構築したサーバーは同じルーター内でのみアクセスでき、パソコンが動いている状態でないと使用できません。同じルーター内でパソコンとサーバーが起動していればスマホでもノートパソコンでもPSVitaでもアクセスできます。

2.2 実装

まずはGUI操作で画像ファイルをアップロードして保存するものを作りましょう。
ほとんどパクリですっ...すみません...↓ありがとうございます。
https://qiita.com/narupo/items/e3dbdd5d030952d10661

ここから先何も書いてなければhome/(username)/DjangoApp001/DjangoApp001/内のもの

settings.py

このプロジェクトのDjango内の定義を行います。

settings.py

......
ALLOWED_HOSTS = ['192.xxx.x.x'] #もともと書いてあるので192.xxx.x.x(自分のIPアドレス)(127.0.0.1はNG)を追加してください

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

staticとは静止ファイルつまり実行できない画像や動画のことです。(動画は動くけどなぁ〜って思ったそこの君ぃ!動画は実行できないだろぅ〜?)
mediaと指定すれば
home/(username)/DjangoApp001/mediaが作られそこにstaticが保存されます。

urls.py

url関連などの設定を行います。

urls.py

urlpatterns = [
    url(r'^/?$', views.index, name='index'),# views.pyで最初に呼ぶ関数
]

......
from django.conf import settings
from django.conf.urls.static import static

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

デバッグを追加することでなんのエラーが出たかをきれいに表示してくれます。

models.py

ここからが正念場です。
models.pyではこのサーバーで扱うデータを定義します。今回は画像だけですので一つです。
データベースに保存するデータの箱を定義すると言っていいでしょう。

models.py
from django.db import models

class Photo(models.Model):

    image = models.ImageField(upload_to='DjangoApp001') 変更可: プロジェクトの名前をどうぞ

forms.py

forms.pyではmodels.pyの箱と対比して外部からデータを入れるためのパイプの種類を定義します。今回は画像のみです。文字列を取りたければCharFieldです。ここで設定したものはのちのindex.htmlに影響します。

forms.py
from django import forms                                                           

class PhotoForm(forms.Form):

    image = forms.ImageField()

home/(username)/DjangoApp001/DjangoApp001/templates/DjangoApp001/index.html

サイトのレイアウトです。PythonとHtmlを混ぜた構文です。

home/(username)/DjangoApp001/DjangoApp001/templates/DjangoApp001/index.html
<html>
<title>マルチパートのテストページ for django and Java</title>
<body>
<hr>
<form action="{% url 'index' %}" method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="投稿"/>
</form>
<hr>
</body>
</html>

{{ form }}の部分でforms.pyの内容を反映してます。ImageFieldならファイルピッカーが自動で備え付けられCharFieldなら文字列を入力するところが生成されます。すごいですよね。

views.py(珍しくオリジナル)

ここでPythonします。(以上)
っとその前に..重みデータとかをしっかり保存しときましょうでもいいですよ。
ないのであれば僕が作った重みファイルとモデルデータをダウンロードしてください。(近日公開します、すみません)
生成したい人はぜひこれを参考に...https://qiita.com/Cyber_Hacnosuke/items/9b6f561632a56598bb56
重みデータ等はhome/(username)/DjangoApp001/DjangoApp001/data/にでも入れときましょう。
結構悩んだりしたのでこのソースコード気に入ってるんですよ(w)

views.py
import os

from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from django.shortcuts import render, redirect

from .forms import PhotoForm
from .models import Photo

from keras.models import load_model
from keras import backend as K

from PIL import Image
import numpy as np
import cv2

# 親ディレクトリを取得する関数です。 https://qiita.com/elu697/items/56d7f5e74ee25bb8d234
def parentPath(path1=__file__, f=0):
    return str('/'.join(os.path.abspath(path1).split('/')[0:-1-f]))

@csrf_exempt
def index(request):
    if request.method == 'GET': #GETでアクセス(一回目)
        return render(request, 'MnistScanner/index.html', {
                        'form': PhotoForm(),
                        'photos': Photo.objects.all()
        }) # index.htmlをもとにサイトを生成(レンダリング)して表示

    elif request.method == 'POST':# POSTでアクセス(画像とともにくる。2回目以降)
        form = PhotoForm(request.POST, request.FILES)# POSTデータとFILE(画像)の準備
        if not form.is_valid():# データが不足していた場合
            raise ValueError('invalid form')

        photo = Photo()
        photo.image = form.cleaned_data['image'] #'image'はindex.htmlでのデータの"name"、画像を取る
        photo.save() #mediaへ保存

        filename = photo.image.name #画像のファイル名
        returnAnswer = myChallenge(filename) #判定する


        return HttpResponse(str(returnAnswer))

def myChallenge(filename):

    #データの形を変数に
    image_rows = 28
    image_cols = 28
    image_color = 1 #グレースケールのこと
    input_shape = (image_rows, image_cols, image_color)

        #親の親のパス
    parent = parentPath(__file__, 1)
    img = Image.open(parent + '/media/' + str(filename)).convert('L') #画像のロード

    #画像を28x28に変換
    img.thumbnail((image_rows, image_cols))

    #フロート型の行列に変換
    img = np.array(img, dtype=np.float32)
    img = img.reshape(-1, image_rows, image_cols, image_color)
    #黒0~255白の画像データをMNISTのデータと同じ白0~1黒に変える
    img = 1 - np.array(img / 255)

    parentDir = parentPath(__file__, 0)
    model = load_model(parentDir + '/data/MNIST-model.h5')
    model.load_weights(parentDir + '/data/MNIST-weights.h5')

    answer = model.predict_classes(np.array(img))

    #★ポイント
    K.clear_session()

    return(str("The computer guesses that this figure is \"" + str(answer[0]) + "\"!"))

ポイントの部分で実に3日かかりました。その頃にはAndroid側もできて手書きのデータを送れるようになっていたのに、一回目はしっかり判定できてよっしゃ!ってなりますが、二回目やると500エラー。つまりサーバー側のエラーが起こって、LOGみてもなんじゃこれ状態でもう...(泣)自分は英語に自信がないので日本語にこだわって調べましたがむだでした。日本語にこだわり続けて3日。間違えて押してしまった英語のGithubの質問サイトで目にしたのは解決されたものでした。

どうやらDjangoでtensorflowやkerasをやっている人は皆こうなるそうです。セッションとやらをクリーンしなきゃいけないってそのままやん!こうすると何回も判定できます。

たった一行でこんな悩むのか!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
(ちなみにPython3.7を使ってたのでさらに3日ほど3.7でどうにかtensorflowを使おうと躍起になっておりました。)

重みデータとかのパスはご自由に。

まとめと注意

今回は第一弾です!DjangoはとてもいいFrameWorkなので機械学習サイトにはもってこいです!

注意ですがPOSTはPOSTでも普通のではなくファイルも遅れるマルチパート送信ですのであしからず。

次回はAndroid側の実装です!なお、Androidいらねぇって方でも今のままで利用できます。Androidでマルチパート送信できちゃいます!

ではまた今度!!!

Twitter: https://twitter.com/Cyber_Hacnosuke (フォローしてくださいお願いします。)
Github: https://github.com/CyberHacnoshuke

【追記:投稿直後】

サーバーによってはハッキングや不正な連続アクセスのアタック(DOS攻撃)から守るためにCSRF対策が有効になってる場合があります。一般的にはHTMLがしっかり認証してくれるそうですが、Androidなどからアクセスするともちろん跳ね返されます。
セキュリティ面では危ないですがCSRF対策をオフにしましょう。フレームワークごとに違いますが、Djangoではメソッド前に@csrf_exemptを付けることで解除できそうです。(上にしれっと書いてある)なお、メソッドごとにしか効かないのでプロジェクトごと無効にしたいのであれば別の方法でできます。(すみません、調べてません)
CSRFについて知りたい方はどうぞ↓
https://qiita.com/maruloop/items/e14d02299bd136f4b1fc

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4