LoginSignup
23
26

More than 3 years have passed since last update.

EfficientNetによるRaspberry Piを用いたリアルタイム画像分類

Last updated at Posted at 2020-02-05

Raspberry Piを使ったセルフレジを製作

機械学習実務未経験者が勉強目的でセルフレジを製作してみました。
学習済みの5種類のペットボトルを正しく分類し価格を表示し、学習していないものに対してはエラーを返すことを目標とする設計にしました。
また、撮影から値段の表示までの時間が五秒以内に収まることを条件にします。

セルフレジの外観

白いボックス内に撮影するペットボトルを設置する。
カメラで商品を撮影し、PC上に商品名、価格などを表示する。
スクリーンショット 2020-02-05 18.12.11.png

データの収集

trainデータ計300枚(各5種類のペットボトルをサンプル60枚ずつ、360度回転させて写真を撮影)
IMG_0388.jpeg

EfficientNetとは

2019年に発表されたモデルで、グラフに示すように少ないパラメータ数で高い精度を出すことができる。
モデルのパラメータ数に応じてEfficientNet-B0からEfficientNet-B7まで選択できる。
今回は撮影から値段の表示まで五秒以内という目標から、速度と精度を加味し、EfficientNet-B3を使用し学習を行った。
x1.png

↓論文
https://arxiv.org/abs/1905.11946
↓EfficientNetを詳しくまとめてくれた方がいらっしゃるので共有します。
https://qiita.com/omiita/items/83643f78baabfa210ab1

ArcFace

畳み込みニューラルネットワークは、識別機能を学習する能力が高いため、近年、顔認識のパフォーマンスを大幅に向上させました。
その中でもArcFaceは、動画や大量の画像など10種もの顔認識データセットを適用し、従来モデルと比較した結果、最高精度の結果を記録。
今回は形状の似たペットボトルを認識するということでArcFaceを応用出来ないかと思い、ArcFaceを使用しました。
スクリーンショット 2020-02-05 16.47.15.png

↓論文
https://arxiv.org/abs/1801.07698
↓詳しくはこちらのQiita参照
https://qiita.com/yu4u/items/078054dfb5592cbb80cc

実装

こちらの記事にはモデルの実装、学習の課程のコードのみ掲載します。
Raspberry Pi周りの実装は以下のgitを参照してください。
https://github.com/teamozawa/group_work

学習データの水増し

ArcFaceに対応させるためのクラスを作成しました。

from keras.preprocessing.image import ImageDataGenerator

class Generator_xandy(object):
    def __init__(self):
        train_dir="train_data"
        test_dir="test_data"
        batch_size=32
        datagen = ImageDataGenerator(zoom_range=[0.95, 1.0],width_shift_range =0.1,height_shift_range=0.1, brightness_range=[0.95, 1.05], rescale=1/255.)

        train_gen = datagen.flow_from_directory(
            train_dir,
            target_size=(224,224),
            batch_size=batch_size,
            class_mode='categorical',
            shuffle=True,
            subset = "training" 
        )

        self.gene = train_gen

    def __iter__(self):

        return self

    def __next__(self): 
        X, Y = self.gene.next()
        #元はreturn X, Y
        return [X,Y], Y

train_generator=Generator_xandy()
val_generator=Generator_xandy()

モデルの実装→学習

from keras import backend as K
from keras.engine.topology import Layer
import numpy as np
import tensorflow as tf

#arcface layerの実装
class Arcfacelayer(Layer):
    def __init__(self, output_dim, s=30, m=0.50, easy_magin=False):
        self.output_dim = output_dim
        self.s = s
        self.m = m
        self.easy_magin = easy_magin
        super(Arcfacelayer, self).__init__()

    def build(self, input_shape):
        # Create a trainable weight variable for this layer.
        self.kernel = self.add_weight(name='kernel',
                                      shape=(input_shape[0][1], self.output_dim),
                                      initializer='uniform',
                                      trainable=True)
        super(Arcfacelayer, self).build(input_shape)

    def call(self, x):
        y = x[1]
        x_normalize = tf.math.l2_normalize(x[0]) #|x = x'/ ||x'||2
        k_normalize = tf.math.l2_normalize(self.kernel) # Wj = Wj' / ||Wj'||2

        cos_m = K.cos(self.m)
        sin_m = K.sin(self.m)
        th = K.cos(np.pi - self.m)
        mm = K.sin(np.pi - self.m) * self.m

        cosine = K.dot(x_normalize, k_normalize) # W.Txの内積

        sine = K.sqrt(1.0 - K.square(cosine))
        phi = cosine * cos_m - sine * sin_m

        if self.easy_magin:
            phi = tf.where(cosine > 0, phi, cosine) 

        else:
            phi = tf.where(cosine > th, phi, cosine - mm) 

        output = (y * phi) + ((1.0 - y) * cosine) # true cos(θ+m), False cos(θ)
        output *= self.s

        return output

    def compute_output_shape(self, input_shape):

        return (input_shape[0][0], self.output_dim)

from keras.models import Model
from keras.models import Sequential
from keras.layers import Activation 
from keras.layers import Input, Dense, GlobalAveragePooling2D
import efficientnet.keras as efn

n_categories=5
#B3の部分をB0~B7と変えるだけでモデルを変更可能
base_model = efn.EfficientNetB3(input_shape=(224,224,3), weights="imagenet",include_top=False)

#ArcFaceLayerをくっつける
x = base_model.output
yinput = Input(shape=(n_categories,))
hidden = GlobalAveragePooling2D()(x)
x = Arcfacelayer(5, 40, 0.05)([hidden,yinput])
prediction = Activation('softmax')(x)
efn_model = Model(inputs=[base_model.input,yinput],outputs=prediction)

efn_model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
#学習
efn_model.fit_generator(train_generator, steps_per_epoch=5, epochs=300, verbose=1,callbacks=[modelCheckpoint], 
                         validation_data=val_generator, validation_steps=5, class_weight=None, max_queue_size=10, workers=1, 
                        use_multiprocessing=False, 
                        shuffle=True, initial_epoch=0)

学習した5種類のペットボトルの特徴ベクトルと、商品の特徴ベクトルを比較し、コサイン類似度を算出することによって未学習の商品を判別する。

def get_hold_vector(model, classes=[1,2,3,4,5]):
    """
    classes: クラス名のリスト イメージの名前はこのリスト名にしてください
    hold_dir: str イメージが入ったフォルダpath
    """

    hold_vector=np.zeros((5,1280))
    train_path="/content/train_data"

    for clas in classes:
        img_array = np.empty((0, 224,224,3))
        for i in range(60):
            imagepath=glob.glob("/content/train_data/{}/*".format(clas))[i]

            img = load_img(imagepath, target_size=(224,224))
            array = img_to_array(img).reshape(1, 224, 224, 3)
            img_array = np.vstack((img_array, array))


        img_array = img_array/255.0
        vector = model.predict(img_array)
        vector=vector.mean(axis=0)
        hold_vector[clas-1, :]=vector
    return hold_vector
#予測用のモデル
predict_model=Model(efn_model.get_layer(index=0).input, efn_model.get_layer(index=-4).output)
predict_model.save("models.h5")

#学習データ(5種類のペットボトル)のベクトル
hold_vector=get_hold_vector(model=predict_model)
np.save(
    "hold_vector.npy", # データを保存するファイル名
    hold_vector,  # 配列型オブジェクト(listやnp.array)
)

結果

学習した5種類のペットボトルを正確に分類し、学習していない未知データ5種類に対してはエラーを返すことに成功しました。
EfficientNet使用することで、学習した5種類のペットボトルに対してはコサイン類似度が0.999..と高い数値が算出される為、閾値を0.993と高い値に設定することができました。これにより、未知データはかなり似たデザインのペットボトルでも判別ができました。
また、撮影から値段の表示までが2秒前後だった為速度の目標も達成できました。
結論として、EfficientNetを使用することで高速かつ高い精度を得られ、ArcFaceはペットボトルの分類にも応用できることがわかりました。

(上が学習した商品。こちらはしっかり5値分類できた。下は未学習のサンプル。学習した商品と非常に似ているがエラーを返すことができた。)
IMG_0388.jpeg
IMG_0389.jpeg

まとめ、感想

・未学習データを判別するのにArcFaceは有効
・EfficientNetは軽くて精度が良い

23
26
7

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
23
26