"BOKU"のITな日常

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

Base64をデコードした画像で画像分類するクラスの作成/Tensorflow-hubの学習済モデル

画像イメージをファイルから読むのではなく、Base64エンコードテキストで受け取り、それをデコードしたものを学習済モデルに渡して分類結果を得る・・そんなクラスを作ってみます。

f:id:arakan_no_boku:20190903223432p:plain

 

はじめに

 

今回のテーマは、「画像イメージをファイルから読むのではなく、Base64エンコードテキストで受け取り、それをデコードしたものを学習済モデルに渡す」ことです。

これができれば。

WebAPI等の形で「画像の分類」など、ディープラーニングの学習済モデルを、アプリケーションの部品として使えるようになります。

ポイントは。

  • Base64形式とは
  • Tensorflow hubの学習済モデルを使った画像分類クラスの作成
  • Base64エンコードテキストをデコードして画像イメージを復元する

です。

 

Base64とは

 

すべてのデータをアルファベット(a~z, A~z)と数字(0~9)と記号(+,/等)の64文字で表すエンコード方式です。

もともと、電子メールを送るSMTPが英数字しか送れなかったので、そこに画像などのバイナリデータを添付するために、すべてのデータを英数字で表すMIME(Multipurpose Internet Mail Extensions)という規格ができて、その中にBase64も含まれているというわけです。

Base64エンコードすると半角英数字で構成されたテキストデータになります。

つまり、メールの添付ファイルみたいにネットワークに流せるということですね。

そしたら、単純に画像ファイルをどっかのパスからよんで処理するより、なんとなく可能性は広がる感じがするので、とりあえず、やり方だけは整理しときたいと思います。

 

Tensorflow hubの学習済モデルを使った画像分類クラスの作成

 

画像を分類するエンジンは学習済モデルを使います。

今なら、文句なく「tensorflow hub」です。

使い方については、こちらの記事で書いてます。

arakan-pgm-ai.hatenablog.com

今回はここで紹介したものをクラス化して、Web-APIのバックで使える形に整理してみます。

 

Mobilenetを使う

 

Tensorflow hubのMobilenetの学習済モデルを使います。

こちらの記事で紹介している実装をベースにクラス化して以下の2つのメソッドを実装してみます。

  • 画像ファイルのファイル名を引数で受け取る。
  • 画像イメージをBase64エンコードしたテキストデータを引数で受け取る。

ソースコードです。

tf_hub_mobilenet_v2.py

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


# ImageNetデータで学習済のMobilenet(画像分類モデル)
class MobileNetImageNet():
    # Mobilenetの学習済モデルをHubから取得し、kerasモデルに変換する
    def __init__(self):
        model_url = "https://tfhub.dev/google/tf2-preview/"
        model_name = "mobilenet_v2/classification/2"
        self.IMAGE_SHAPE = (224, 224)
        self.classifier = tf.keras.Sequential([hub.KerasLayer(
            model_url + model_name, input_shape=self.IMAGE_SHAPE + (3,))])
        data_url = "https://storage.googleapis.com/download.tensorflow.org/data/"
        labels_path = tf.keras.utils.get_file(
            'ImageNetLabels.txt', data_url + 'ImageNetLabels.txt')
        self.imagenet_labels = np.array(open(labels_path).read().splitlines())

    # 分類結果のクラス名を受取り文章に編集して返す内部関数
    def __make_result_str(self, predicted_class_name):
        translator = toj.Ilsvrc2012Japanese()
        return "この画像は" + predicted_class_name + ":日本語名(" + \
            translator.convert(predicted_class_name) + ")です"
    
    # 画像イメージを受け取り分類を行う内部処理
    def __predict(self, target_image):
        # 実数データに255.0で割る
        target_image_array = np.array(target_image) / 255.0
        # 分類を行い結果オブジェクトを返す
        result = self.classifier.predict(target_image_array[np.newaxis, ...])
        # 最も確率の高い分類クラスを示すインデックス(数値)を得る
        predicted_class = np.argmax(result[0], axis=-1)
        # インデックスを日本語名に変換する
        predicted_class_name = self.imagenet_labels[predicted_class]
        # 日本語名を結果文字列に変換する
        return self.__make_result_str(predicted_class_name)
    
    # 画像ファイルのパス+ファイル名を引数にとって分類結果を返す
    def predict(self, image_file_path):
        target_image = Image.open(image_file_path).resize(self.IMAGE_SHAPE)
        return self.__predict(target_image)

    # Base64エンコードされたテキストを受け取って分類結果を返す
    def predict_from_base64(self, encoded_base64_text):
        target_image = Image.open(BytesIO(base64.b64decode(encoded_base64_text))).resize(self.IMAGE_SHAPE)
        return self.__predict(target_image)

補足していきます。

 

Base64エンコードテキストをデコードして画像イメージを復元する

 

今回のポイントは、Base64エンコードされたテキストデータを、kerasモデルで利用できるデータにどうやってするか?ということにつきます。

ところが、これは超簡単です。

PIL.Imageを使えば、openで処理できてしまいます。

画像ファイル名を受け取る場合は。

target_image = Image.open(image_file_path).resize(self.IMAGE_SHAPE)

です。

Base64エンコード済のテキストデータを受け取る場合は。

target_image = Image.open(BytesIO(base64.b64decode(encoded_base64_text))).resize(self.IMAGE_SHAPE) 

です。

ここが違うだけで、後続の処理はまったく同じにできます。

なので、上記のソースの中では共通処理「__predict(self, target_image)」として切り出して使っています。

 

試しに使ってみます

 

上記のクラスを試しにつかってみます。

from td20 import tf_hub_mobilenet_v2 as mn
import base64


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


cl = mn.MobileNetImageNet()
ans = cl.predict_from_base64(base64encode('test.jpg')) print(ans)

画像ファイルをBase64エンコードして、さきほどのクラスで処理してます。

こんな画像です。

f:id:arakan_no_boku:20190727150607p:plain

Base64エンコードすると、こんな感じ(一部だけ抜粋)のテキストになります。

ABAAAAsgEoAAMAAAABAAIAAAExAAIAAAAwAAAAugEyAAIAAAAUAAAA6gITAAMAAAABAAEAAIdpAAQAAAABAAAA/oglAAQAAAABAAADCgAAA9YAAABIAAAAAQAAAEgAAAABOS4wMDEwLjMwLCBOT1JNQUwgMCwgVjAxMC4wMDgwLjAxLCBWMDAxLjAwMjAuMDcAMjAxOTowNzoyNyAxNDo1NDoyNAAAHoKaAAUAAAABAAACbIKdAAUAAAABAAACdIgiAAMAAAABAAAAAIgnAAMAAAABAX4AAJAAAAcAAAAEMDIyMJADAAIAAAAUAAACfJAEAAIAAAAUAAACkJEBAAcAAAAEAQIDAJIBAAoAAAABAAACpJICAAUAAAABAAACrJIDAAoAAAABAAACtJIHAAMAAAABAAIAAJIJAAMAAAABAAAAAJIKAAUAAAABAAACvJJ8AAcAAAAmAAACxJKQAAIAAAAEMDMzAJKRAAIAAAAEMDMzAJKSAAIAAAAEMDMzAKAAAAcAAAAEMDEwMKABAAMAAAABAAEAAKACAAQAAAABAAAMMKADAAQAAAABAAAQQKAFAAQAAAABAAAC6qIXAAMAAAABAAAAAKMBAAcAAAABAAAAAKQCAAMAAAABAAAAAKQDAAMAAAABAAAAAKQFAAMAAAABAAAAAKQGAAMAAAABAAAAAOodAAkAAAABAAAAZAAAAAAC+t5sO5rKAAAAAMgAAABkMjAxOTowNzoyNyAxNDo1NDoyNAAyMDE5OjA3OjI3IDE0OjU0OjI0AAAAEOIAAAPoAAAAyAAAAGQAAAAAAAAAZAAADfIAAAPo//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADoA0EfAABkANcHAAAAAgABAAIAAAAEUjk4AAACAAcAAAAEMDEwMAAAAAAAAAAIAAEAAgAAAAJOAAAAAAIACgAAAAMAAANwAAMAAgAAAAJFAAAAAAQACgAAAAMAAAOIAAUABQAAAAEAAAOgAAYABQAAAAEAAAOoAAcABQAAAAMAAAOwAB0AAgAAAAsAAAPIAAAAAAAAACIAAAABAAAALwAAAAEAABZDAAAAZAAAAIcAAAABAAAAJgAAAAEAABC8AAAAZAAAAAAAAABkAAAAXAAAAAEAAAAFAAAAAQAAADYAAAABAAAAGAAAAAEyMDE5OjA3OjI3AAAAAAAGAQMAAwAAAAEABgAAARoABQAAAAEAAAQkARsABQAAAAEAAAQsASgAAwAAAAEAAgAAAgEABAAAAAEAAAQ0AgIABAAAAAEAABECAAAAAAAAAEgAAAABAAAASAAAAAH/2P/bAEMACAYGBwYFCAcHBwkJCAoMFA0MCwsMGRITDxQdGh8eHRocHCAkLicgIiwjHBwoNyksMDE0NDQfJzk9ODI8LjM0Mv/bAEMBCQkJDAsMGA0NGDIhHCEyMj・・・・

 これを処理した結果は。

この画像はacoustic guitar:日本語名(アコースティックギター)です

 OKです。

とりあえず、できました。

ではでは。