画像ファイルを分析・学習に使えるように読み込む方法を紹介します

# はじめに
自分のローカルの画像ファイルを使って、識別・分類をやってみたいとなった時に、まず画像データを学習に使えるように読み込む必要があります。

チュートリアルとかだとTensorFlowのdatasetでmnistをダウンロードする等が多く、自分のローカルの画像ファイルを使って識別させたい！ってなった時に**「ん？読み込みってどないすりゃええねん...。」**となることを防ぐことを目的としてざっと紹介できればと思います。


色々やり方はあるのですが、自分が一番使っている方法を紹介できたらと思います。
主な流れとしては

1. 画像データとラベルデータの配列を作成
2. それをもとに`npz`ファイルを作成

となっています。
`npz`ファイルにしておけばそこから読み取るだけですぐにデータセット化できるし楽なので愛用しています。

その後で`Tensorflow`の`from_tensor_slices`などでミニバッチ化したりといった応用が効くのも好きなポイントですね。

少しでも画像読み込みのハードルを下げられればと思ってますので参考になれば幸いです。

# 実行環境・使う画像データ

実行環境はAnacondaのJupyterlabを使います。
Google Colaboratoryを使っている人はGドライブから読み込む形で読み替えていただければ。


使うデータですが、Kaggleの[Dogs vs. Cats](https://www.kaggle.com/c/dogs-vs-cats)コンペの犬と猫の画像を使います。

>train/
test1/
sampleSubmission.csv


以上のように2つフォルダと1つの提出用csvファイルがあるので
この中のtrainフォルダの中に犬と猫の画像が合わせて25000枚あるのでこれらを読み込んでいきたいと思います。

# 1. 画像データとラベルデータの配列を作る方法

前置きが長くなりましたが、早速1番目の画像データとラベルデータの配列を作るやり方からやっていきます。

```python
# 必要なものをimport
import numpy as np
import glob
import cv2
import matplotlib.pyplot as plt

# ディレクトリの指定
train_dir = './train'
test_dir = './test'

# カテゴリネームと番号を対応させた辞書を作成（ラベルは数値で保存します）
label_dict = {
    'dog' : 0,
    'cat' : 1 }

# 画像とラベルを格納する空のリストを作る
Images = []
Labels = []

# 指定したディレクトリの画像パスを全て取得して処理していく
for image_path in glob.glob(train_dir + '/*'):
    label_name = image_path.split('/')[-1].split('.')[0] # 下で補足します

    # ラベル番号の取得
    label = label_dict[label_name]

    # 画像の読み込み
    img = cv2.imread(image_path)

    # RGB形式に変換
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    # サイズがバラバラなので今回は224×224に統一
    img_resize = cv2.resize(img_rgb, (224,224))

    # ndarray型に変換
    numpy_img = np.array(img_resize)

    # 指定した形にリシェイプ
    numpy_img = numpy_img.reshape(224, 224, 3)

    # float32型にして255で割ることで[0,1]スケールの正規化
    numpy_img = numpy_img.astype('float32')
    numpy_img = numpy_img / 255

    画像とラベルをリストに格納
    Images.append(numpy_img)
    Labels.append(label)

```
`glob`やsplitの挙動を補足しておくと

```python
glob.glob(train_dir + '/*')
# ->
#['./train/dog.8011.jpg',
# './train/cat.5077.jpg',
# './train/dog.7322.jpg',
# './train/cat.2718.jpg',
# './train/cat.10151.jpg',
# './train/cat.3406.jpg',
# './train/dog.1753.jpg',
# './train/cat.4369.jpg',
# './train/cat.7660.jpg',
# './train/dog.5535.jpg',
# './train/cat.8553.jpg',
# './train/cat.9895.jpg',
# './train/cat.1211.jpg',
# './train/dog.3144.jpg',
# './train/dog.775.jpg',
# './train/dog.11102.jpg',
# ...]
# 指定した形式に合うファイルパスのリストを取得する

----------------------------------
image_path = './train/dog.5535.jpg'
image_path.split('/')
# -> ['.', 'train', 'dog.5535.jpg']
# / でファイルパスを区切った要素のリストを作成

image_path.split('/')[-1]
# -> 'dog.5535.jpg'
# dogが含まれる一番最後の部分を取得

image_path.split('/')[-1].split('.')
# -> ['dog', '5535', 'jpg']
# 'dog.5535.jpg'を '.' で区切る

image_path.split('/')[-1].split('.')[0]
# -> 'dog'
# 一個上のリストの最初にdogがあるのでこれを取得

```

こんな感じになっています。
`label_name = image_path.split('/')[-1].split('.')[0]`で 'dog'か'cat'が取得できるのでそれを元に辞書を使って0か1かのラベル番号を取得しています。

また今回はカラー画像なのでcv2.cvtColorでRGB形式にしています。

なぜかは見た方が早いので

![cat.9.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/3230abfb-38d1-4dfa-144d-6a05290cdde9.jpeg)


上記画像を例にします。


```python
image_path = './train/cat.9.jpg' 

img = cv2.imread(image_path)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
```
`img`の方を`plt.imshow`で表示するとこんな感じ
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/cefa861d-aa3f-b044-13a4-2115be3e18e3.png)


色味が変わってますよね。

今度は`img_rgb`の方を表示しますと
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/adf8c67c-28d0-fea3-d94b-c2a453e6b54d.png)


元の画像と一緒ですね。

`cv2.imread`は画像をB→G→Rの順番で読み込むので注意です。
この辺り忘れがちなので気をつけたいポイントです。

※ちなみに`cv2.cvtColor`を使わないやり方もあるので紹介します。

```python
image_path = './train/cat.9.jpg'
img = cv2.imread(image_path)
b,g,r = cv2.split(img)
img_rgb = cv2.merge([r,g,b])
```

`cv2.split`でチャネルごとにアンパックで代入して、RGBの順番でマージするやり方です。

お好きな方でいいと思います。


さてちゃんと画像とラベルが対応しているかも見ていきましょう。

```python
plt.imshow(Images[1])
plt.title(Labels[1])
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/a421c1f7-fa81-879c-aa8c-a6052fc30c7d.png)

1はcatなので対応できていますね。

# 2. npzファイルを作る

さて、できた画像の配列とラベルの配列をもとに`npz`ファイルを作っていきます。

`npz`ファイルは`numpy`独自のバイナリファイルです。

```python
# 第1引数はファイル名、第2引数以降は [キー名 = 配列]の形で書く
np.savez('train_img.npz', img = Images, label = Labels)
```

これで`train_img.npz`ファイルが作成されます。

```python
# npzファイルを読み込む
data = np.load('train_img_npz')

# 画像とラベルをそれぞれXとyに代入
X = data[img]
y = data[label]
```

これでnpzファイルから画像データとラベルデータを読み込めました。
ちゃんと読み込めているか確認してみましょう。

```python
X.shape, y.shape
# -> ((25000, 224, 224, 3), (25000,))
```
しっかりと画像データは4次元テンソルになっていて件数も問題ないですね。
これを`fit(X, y)`のようにすれば学習可能です。

また

```python
# X:画像データ y:ラベルデータをセットでdataset化
dataset = tf.data.Dataset.from_tensor_slices((X, y))

# シャッフル
dataset = dataset.shuffle(25000)

# ミニバッチ化
#（drop_remainder = Trueで端数切り捨て）
# バッチサイズを128で固定したいため。
dataset = dataset.batch(128, drop_remainder=True)
```

とかってすればシャッフル済みでミニバッチ化されたデータセットを作成可能です。

# まとめ
自分がよく使っている画像の読み込み方法をお伝えしました。

ただこれが一番いいってわけじゃなくてディレクトリ構造の指定はありますが
`keras`の`ImageDataGenerater`を利用すれば、水増しなどの処理が楽です。

ニーズに応じて使い分けが必要ですね。


少しでも自分のローカル画像ファイル使って加工、分析やってみたいって方のハードルが下がれば幸いです。