見出し画像

OpenCV4Python08:OpenCV(numpy.ndarray)とTensorFlowで画像分類

【0】はじめに

今回はOpenCVで読み込んだ画像データをTensorFlowに渡して簡単な画像分類をしてみる。

【1】numpy.ndarrayとtf.Tensorオブジェクト互換性

OpenCVで読み込んだ画像データの実態は「numpy.ndarrayオブジェクト」である。

これに対しTensorFlowは主に「tf.Tensorオブジェクト」を使ってディープラーニングの演算を行う。

■「tf.Tensorオブジェクト」は「numpy.ndarray」と互換性がある
具体的には、
・numpy.ndarrayをTensorflowの関数に渡す
  → 内部で自動的に「numpy.ndarray→tf.Tensorオブジェクト」に変換。

・tf.TensorオブジェクトをNumPyの関数に渡す
  → 内部で自動的に「tfTensorオブジェクト→numpy.ndarray」に変換。

【2】TFHubにある学習済みMNISTモデルを使う(Kerasを使用しない場合)

「ニューラルネットワークを定義して、学習させて、モデルをつくって…」というのは面倒くさいので「TensorFlow HubにあるMNISTの学習済みモデル」を使用する。

■モデルをロードする(Kerasを使わない場合)
Kerasを使わなず、TensorFlow単独でやる場合は「tensorflow_hub.load()」で読み込めばいい。

【例1】

import tensorflow_hub as hub

# モデルのロード
model = hub.load('https://tfhub.dev/tensorflow/tfgan/eval/mnist/logits/1')

あとはOpenCVで読み込んだ画像データを、このモデルが受け付ける入力データ構造にして渡せばいい。

※モデルが受け付ける入力データ構造について
TensorFlowHubの各モデルのページに説明があればいいのだが、何も書いていない場合もある(今回のMNISTのモデルも特に記述がない)。そういった時には、以下のような感じでsaved_model_cliコマンドをつかって情報を取得してみる。

import tensorflow_hub as hub

# モデルのロード
model = hub.load('https://tfhub.dev/tensorflow/tfgan/eval/mnist/logits/1')

# モデルのダウンロードされている場所(一時的に保存されている場所)を確認
print(hub.resolve('https://tfhub.dev/tensorflow/tfgan/eval/mnist/logits/1'))

出力例:

/tmp/tfhub_modules/977b25dcd7e9d44e13a42af999d5a3f512493960

出力されるパスをコピーして、以下のようにコマンドプロンプトを実行する

$ saved_model_cli show --all --dir /tmp/tfhub_modules/977b25dcd7e9d44e13a42af999d5a3f512493960

実行例:

画像1

ということでこのモデルが受け付ける入力データ構造は「(None,28,28,1):n枚x28x28x1」で、データの型は「tf.float32」ということが分かる。

【3】OpenCVで画像を読み込んでデータ形状を合わせてモデルに画像分類させる

今回は以下手書きで書いた数字の2(画像サイズは28x28)を使う。

画像2

【例2】:画像の読み込みと前処理(白黒反転)

import cv2 as cv
import numpy as np

... ...
img = cv.imread('2.png',cv.IMREAD_GRAYSCALE)
img = cv.bitwise_not(img) #ピクセル値の反転

MNISTの画像はグレースケール画像。さらに背景黒、数字部分が白なので読み込んだら白黒反転させる。白黒反転させるには「cv.bitwise_not()」でビット反転させればよい。

画像3

次に「numpy.ndarray(画像データ)」を受け付ける入力データ構造に変換する。
形状は(None,28,28,1)なのでreshapeで変換する。

データは型がtf.float32なので「ピクセルの値は0~1.0の範囲をとる32ビット浮動小数点データ」にする必要がある。

【例3】:「numpy.ndarray(画像データ)」の変換

# スケール変換 ピクセル値を0~1.0の範囲にする
x = img/255.0 
predict_x = x.reshape([1,28,28,1]) #予測用にndarrayの形状変換

print(predict_x.shape)
# (1, 28, 28, 1)

# 32ビット浮動小数点にキャスト
predict_x = predict_x.astype(np.float32)

「numpy.ndarrayのキャスト」は「numpy.ndarray.astype」でできる。

これでMNISTのモデルに渡す画像データもできたので、あとはモデルに投げ込んで画像分類させればよい。

【例4】:モデルに画像データを渡して分類させる

result = model(predict_x)
print(result)
print(result.numpy().argmax())

実行結果例:numpy.ndarray[2]の値が一番高い→数字の「2」と判定

<tf.Tensor: shape=(1, 10), dtype=float32, numpy=
array([[-0.5670906 ,  1.8182844 ,  7.146054  , -0.41182184, -2.2591584 ,
       -3.935476  , -3.7045004 , -1.1462402 ,  4.241019  , -0.6560709 ]],
     dtype=float32)>
2

【全体コード】:Kerasなし版

import tensorflow_hub as hub

import cv2 as cv
import numpy as np

# TFHubからモデルをロード
model = hub.load('https://tfhub.dev/tensorflow/tfgan/eval/mnist/logits/1')

# 入力画像をロード(グレイスケール画像で読み込み)
img = cv.imread('2.png',cv.IMREAD_GRAYSCALE)

# 入力データ用に画像データに前処理実施
img = cv.bitwise_not(img) # ピクセル値の反転
x = img/255.0 # スケール変換

predict_x = x.reshape([1,28,28,1]) # 形状変換
predict_x = predict_x.astype(np.float32) # キャスト
#print(predict_x.shape)

# モデルに渡して画像分類させる
result = model(predict_x)

print(result)
print(result.numpy().argmax())

※Kerasなしの場合、画像分類の結果は「tf.Tensorオブジェクト」として返ってくる。「argmax()」を使いたいので「tf.Tensor.numpy()」で「numpy.ndarray」を取得している。

【4】Kerasを使う場合のTFHubモデルのロードの仕方

Kerasを使う場合は、「hub.KerasLayer()」でモデルをロードする。

【例5】:Kerasを使う場合のTFHubモデルのロード

import tensorflow_hub as hub

mylayer = hub.KerasLayer('https://tfhub.dev/tensorflow/tfgan/eval/mnist/logits/1')

後はtf.keras.Sequetial()でレイヤとして積んでビルドすればよい。

import tensorflow as tf

mymodel = tf.keras.Sequential([mylayer,])

# モデルのビルド、入力形状はここで指定
mymodel.build((None,)+(28,28)+(1,))

print(mymodel.get_config())
print('-----')
print(mymodel.summary())

出力結果:入力データの構造とデータ型、Sequentialモデルの構造確認

{'layers': [{'class_name': 'InputLayer',
  'config': {'batch_input_shape': (None, 28, 28, 1),
   'dtype': 'float32',
   'name': 'keras_layer_input',
   'ragged': False,
   'sparse': False}},
 {'class_name': 'KerasLayer',
  'config': {'dtype': 'float32',
   'handle': 'https://tfhub.dev/tensorflow/tfgan/eval/mnist/logits/1',
   'name': 'keras_layer',
   'trainable': False}}],
'name': 'sequential'}
-----
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
keras_layer (KerasLayer)     (None, 10)                0         
=================================================================
Total params: 0
Trainable params: 0
Non-trainable params: 0
_________________________________________________________________

あとはこのKerasで作った「mymodel」に対し、OpenCVで読み込んだ画像を入力受付できるようにすればよい。

【全体コード】:Kerasあり版

import tensorflow_hub as hub
import tensorflow as tf

import cv2 as cv
import numpy as np

# Kerasを使う場合のTFHubモデルのロード
mylayer = hub.KerasLayer('https://tfhub.dev/tensorflow/tfgan/eval/mnist/logits/1')

# Sequentialモデルの構築
mymodel = tf.keras.Sequential([mylayer,])

mymodel.build((None,)+(28,28)+(1,))
#(None, 28, 28, 1)のバッチインプットサイズを設定している n枚の28x28x1チャネル

#print(mymodel.get_config())
#print(mymodel.summary())

# 入力画像をロード(グレイスケール画像で読み込み)
img = cv.imread('2.png',cv.IMREAD_GRAYSCALE)

# 入力データ用に画像データに前処理実施
img = cv.bitwise_not(img) # ピクセル値の反転
x = img/255.0 # スケール変換

predict_x = x.reshape([1,28,28,1]) # 形状変換
predict_x = predict_x.astype(np.float32) # キャスト
#print(predict_x.shape)

result = mymodel.predict(predict_x)
print(result)
print(result.argmax())

実行結果:array[2]の値が一番高い→数字の「2」と判定

array([[-0.5670906 ,  1.8182844 ,  7.146054  , -0.41182184, -2.2591584 ,
       -3.935476  , -3.7045004 , -1.1462402 ,  4.241019  , -0.6560709 ]],
     dtype=float32)
2

▲Kerasの場合は、分類結果は「arrayオブジェクト」として返ってくるのでsのまま「argmax()」を使える。

■補足:TensorFlowの画像読み込みライブラリ

TensorFlow自体にも画像を読み込むライブラリはある。(tf.image.decode_image、tf.image.decode_jpeg、tf.image.decode_png等々)。

今回はあくまでOpenCVーTensorFlow間の連携、となった場合の一例としてあげたもの。特に制約がないならTensorFlowが搭載している上記の画像読み込みライブラリを使えば十分。


もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。