"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

ブラウザにドロップした画像にうつる物体名を1000種類から識別するデモを作る/python・django・tensorflow2.0

WEBブラウザに画像ファイルをドロップしてBase64エンコードする処理と、Base64エンコードテキストを受け取って、学習済モデルを使って何の画像かを識別する処理を組み合わせて、簡単なデモンストレーションを作ってみます。

f:id:arakan_no_boku:20191218000746p:plain

 

はじめに

 

ブログで別々の記事に書いている3つを組み合わせて、簡単なデモを作ってみます。

その3うとは。

画像識別するクラス

arakan-pgm-ai.hatenablog.com

識別結果を英語→日本語化するクラス

arakan-pgm-ai.hatenablog.com

ドラッグ&ドロップで画像ファイルを受け取り、Base64形式にエンコードしてHTTPRequest経由でPythonプログラムに渡すJavaScript

arakan-pgm-ai.hatenablog.com

です。

これを組み合わせて。

  1. 画像ファイルをドラッグ&ドロップで受け取り、Base64形式にエンコードする。
  2. エンコードしたBase64テキストをSubmitでPythonプログラムに渡す。
  3. 受け取ったBase64テキストを画像にデコードする。
  4. 画像を受け取り物体名を推論する。
  5. 結果として得た物体名を日本語に変換する。
  6. 画像と推論した物体名を画面に表示する。

という動きをするデモ画面を作ります。

 

画像識別クラスに日本語化とBase64形式いたフェースを追加する

 

上記のうち、不足しているのが「3.受け取ったBase64テキストを画像にデコードする」機能なので、それを追加します。

Base64形式から画像にデコードする処理のみ抜粋します。

PIL.Image と BytesIOのライブラリを使えば1行で書けるのですが・・。

import PIL.Image as Image
from io import BytesIO

img = Image.open(BytesIO(base64.b64decode(base64text))).resize(self.IMAGE_SHAPE)

上記のIMAGE_SHAPEは「 (224, 224) 」のように、画像の縦横サイズをセットしていて、適当なサイズの画像でもリサイズできるようにしてます。

 あと。

評価結果の日本語化ですね。 

arakan-pgm-ai.hatenablog.com

これは上記の記事で作ってみたクラスを使ってこんな感じにします。

from . import to_japanese_ilsvrc2012 as toj

self.translator = toj.Ilsvrc2012Japanese()

ans['jp'] = self.translator.convert(results[0][1])

 このresults[0][1]は英語名の文字列をあらわします。

これらを組み込んで、拡張したクラス全体のソースです。

vgg16_imagenet.py

import tensorflow as tf
import numpy as np
import PIL.Image as Image
from io import BytesIO
import base64
from . import to_japanese_ilsvrc2012 as toj


class Vgg16k:

    def __init__(self):
        self.model = tf.keras.applications.vgg16.VGG16(
            weights='imagenet', include_top=True)
        self.translator = toj.Ilsvrc2012Japanese()
        self.IMAGE_SHAPE = (224, 224)

    def __predict(self, img):
        x = tf.keras.preprocessing.image.img_to_array(img)
        x = np.expand_dims(x, axis=0)
        x = tf.keras.applications.vgg16.preprocess_input(x)
        pred = self.model.predict(x)
        results = tf.keras.applications.vgg16.decode_predictions(pred, top=1)[
            0]
        ans = {}
        ans['pp'] = '{:.2f}'.format(results[0][2] * 100.0) + '%'
        ans['jp'] = self.translator.convert(results[0][1])
        ans['en'] = results[0][1]
        return ans

    def predict_from_path(self, imgpath):
        img = tf.keras.preprocessing.image.load_img(
            imgpath, target_size=self.IMAGE_SHAPE)
        return self.__predict(img)

    def predict_from_base64(self, base64text):
        img = Image.open(
            BytesIO(
                base64.b64decode(base64text))).resize(
            self.IMAGE_SHAPE)
        return self.__predict(img)


if __name__ == '__main__':
    def base64encode(file_name):
        target_file = file_name
        with open(target_file, 'rb') as f:
            data = f.read()
        encoded_base64_text = base64.b64encode(data)
        return encoded_base64_text

    m = Vgg16k()
    a = m.predict_from_base64(base64encode('.\\tf20\\test.jpg'))
    print(a['jp'])

ほぼ、以下の記事のクラスに上記の追加処理を書き足しただけです。 

arakan-pgm-ai.hatenablog.com

 

Djangoプログラムにロジックを追加する

 

ドラッグ&ドロップを受けてBase64変換してサブミットするまでの処理は、以下の記事で書いていものから変更はありません。 

arakan-pgm-ai.hatenablog.com

唯一変更するのは「views.py」です。

ここに、画像識別ロジックを組み込む必要があります。

そこだけ抜粋してみます。

img = request.POST["areaone"]
        if('data:image/jpeg' in img):
            decode_img = img.replace('data:image/jpeg;base64,''')
        else:
            decode_img = img.replace('data:image/png;base64,''')
        model = cl.Vgg16k()
        ans = model.predict_from_base64(decode_img)

この「img」には、JavaScriptエンコードしたBase64形式のテキストデータがはいってます。

JavaScriptエンコードしたBase64テキストには、例えばJPEGなら画像データの前に識別情報として「data:image/jpeg;base64,」がついてます。

これをそのまま渡すとデコードする時にエラーになるので、画像ファイルの種類を判定して余分な箇所を消してます。

いちおう。

インプットになるJavaScript側がそうなっているので、PNG形式も書いてますが・・、こちらは画像識別のテストをしてません。

なので、PNG画像だとうまくいかない可能性があります(苦笑)。

自分が、デモとかで使うときはJPEGファイル固定でやってます。

ちゃんと修正しろよ!・・とお叱りが聞こえそうですが(笑)、そのうち、暇ができたら・・ということで、ご容赦を。

上記を組み込んだ「views.py」です。

views.py

from django.shortcuts import render
from . import forms
from django.template.context_processors import csrf
from .tf20 import vgg16_imagenet as cl


def dropdemo(request):
    if request.method == 'POST':
        img = request.POST["areaone"]
        if('data:image/jpeg' in img):
            decode_img = img.replace('data:image/jpeg;base64,''')
        else:
            decode_img = img.replace('data:image/png;base64,''')
        model = cl.Vgg16k()
        ans = model.predict_from_base64(decode_img)
        c = {
            'base64text': img,
            'decodeimg': decode_img,
            'enlabel': ans['en'],
            'jplabel': ans['jp'],
            'pplabel': ans['pp'],
        }
    else:
        form = forms.UserForm(label_suffix=':')
        c = {'form': form}
        c.update(csrf(request))
    return render(request, 'dragdrop.html', c)
 

 

HTMLを少しだけ修正して実行です

 

上記以外は、ほぼ、こちらの内容を踏襲していますが、 HTMLを少しだけ修正します。

arakan-pgm-ai.hatenablog.com

画像の下に評価結果の文字列を表示するエリアをつけくわえるだけですが。

dragdrop.html

{% extends 'base.html' %}
{% load static %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}
{% load widget_tweaks %}

{% block header %}
<link rel="stylesheet" href="{% static "css/dragdrop.css" %}"></link>
{% endblock %}

{% block title %}
  サンプルその1
{% endblock %}

{% block content %}
<div class="container">
    <form action="" method="post">{% csrf_token %}
        {% if base64text %}
        <h4>ドロップされた画像</h4>
        <div class="form-group row my-4">
            <img  src="{{ base64text }}" />
        </div>
        <div class="form-group row my-4">
            <h4>英語名:{{ enlabel }}</h4>
        </div>
        <div class="form-group row my-4">
            <h4>日本語:{{ jplabel }}</h4>
        </div>
        <div class="form-group row my-4">
            <h4>確率は:{{ pplabel }}</h4>
        </div>
        <a href="{% url 'dragdrop' %}"><h2>もう一回</h2></a>
        {% else %}
        <h4>ドラッグエリア</h4>
        <div class="form-group row my-4">  
        	<div id="dragandrophandler" class="border col-lg-9">ここにJPEGまたはPNGファイルをドロップ</div>
        </div>
        <div class="form-group row my-4">         
    		<div class="col-lg-2">       
	            <button type="submit" class="btn btn-primary">サブミット</button>
    		</div>
    		<div class="col-lg-8"></div>       
        </div>
        <div id="imgdiv" class="form-group row my-4"> 
            <img  src="" />
        </div>
        <div class="form-group row my-4" hidden>         
            {{form.areaone|add_class:"form-control"}}
    	</div>    
        {% endif %} 
    </form>
</div>  
<script type="text/javascript" src="{% static "js/dragdrop.js" %}"></script>  
{% endblock %}

さて、変更はこれくらいで、実行してみます。 

実行の仕方はこちらの記事と同じです。 

arakan-pgm-ai.hatenablog.com

あとは。

実行した画面イメージだけ。 

画像をドロップして。

f:id:arakan_no_boku:20191221173112p:plain

 

確認して。

f:id:arakan_no_boku:20191221172837p:plain

 

サブミットすると。

f:id:arakan_no_boku:20191221172948p:plain

まあ、いい感じじゃないですかね。

今回はこんなところで。

ではでは。 

※これは2019/1/30にアップした記事のリライトです。