naritoブログ

このブログはDjangoとBootstrap4で作成されました
ソースコード

画像認識で、訓練画像を読み込む

プログラミング関連 画像認識 約287日前
2017年2月28日7:11
画像認識の類を行う場合、訓練画像等を用意しそれを読み込ませる必要があります。
「ゼロから作るDeep Learning」という書籍では、MNISTデータセットのダウンロードから画像データのnumpy配列への変換までをサポートする便利なPythonスクリプトが提供されていました。
https://github.com/oreilly-japan/deep-learning-from-scratch/blob/master/dataset/mnist.py

上記のコードは、gzのMNISTデータセットからデータを読み込んでいました。
今回は、gzではなくフォルダに画像があるケースで、それを読み込ませれるようにします。

「mnist_png」というディレクトリがあり...


「testing」と「training」の2つのディレクトリ。名前のとおり、訓練とテストですね。


中には更に、0~9までのディレクトリです。


ディレクトリ名の数字は、中に入っている画像が表している数字そのものです。つまり、0ディレクトリならば中にある画像は全て0です。


元のmnist.pyを参考に作成しています。
loaddata.py
import os
import pickle
import numpy as np
from PIL import Image


class LoadMnistImage:

    def __init__(self):
        """初期化処理"""

        self.train_img = None
        self.train_label = None
        self.test_img = None
        self.test_label = None

    def get_data(self, save_file,
                 normalize=False, flatten=False, one_hot_label=True):
        """既に作成したpickleファイルからデータを読み込む(なければ作成する)"""

        if not os.path.exists(save_file):
            self.create_data(save_file)

        with open(save_file, 'rb') as f:
            dataset = pickle.load(f)

        self.train_img = dataset['train_img']
        self.train_label = dataset['train_label']
        self.test_img = dataset['test_img']
        self.test_label = dataset['test_label']

        if flatten:
            self.to_flatten()

        if normalize:
            self.to_normalize()

        if not one_hot_label:
            self.no_hot_label()

    def to_flatten(self):
        """画像を1次元にする (28, 28) →(784,)"""

        self.train_img = self.train_img.reshape(len(self.train_img), 784)
        self.test_img = self.test_img.reshape(len(self.test_img), 784)

    def to_normalize(self):
        """画像のピクセル値を0.0~1.0に正規化する"""

        self.train_img = self.train_img.astype(np.float32) / 255.0
        self.test_img = self.test_img.astype(np.float32) / 255.0

    def no_hot_label(self):
        """one_hot表現をなくす"""

        self.train_label = self.train_label.argmax(axis=1)
        self.test_label = self.test_label.argmax(axis=1)

    def create_data(self, save_file):
        """画像・ラベルを読み込み、pickleとして作成・保存する"""

        train_img, train_label, test_img, test_label = [], [], [], []

        # traningディレクトリはの画像はtrain_img, train_labelへ
        # testingディレクトリはtest_img, test_labelへ
        kinds = [
            ['training', train_img, train_label],
            ['testing', test_img, test_label],
        ]
        for kind in kinds:

            # このiはディレクトリ名であると同時に、正解ラベル名
            for i in range(10):

                # mnist_png/training/0 等のパス
                dir_path = os.path.join('mnist_png', kind[0], str(i))

                # [mnist_png/training/0/1.png, mnist_png/training/0/2.png]等のリスト
                file_paths = [os.path.join(dir_path, file)
                              for file in os.listdir(dir_path)]

                # [[画像のnumpy配列], [画像のnumpy配列], ...])のような2次元配列
                images = [np.asarray(Image.open(path)).reshape(1, 28, 28) for path in file_paths]
                kind[1].extend(images)

                # ディレクトリ名のiが、そのまま正解ラベル名なので、1を入れる
                label = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                label[i] = 1
                labels = [label for x in file_paths]
                kind[2].extend(labels)

        dataset = {
            'train_img': np.array(train_img),  # (60000, 28, 28)
            'train_label': np.array(train_label),  # (60000, 10)
            'test_img': np.array(test_img),  # (10000, 28, 28)
            'test_label': np.array(test_label),  # (10000, 10)
        }

        with open(save_file, 'wb') as f:
            pickle.dump(dataset, f, -1)


このような感じで使います。
mnist = LoadMnistImage()
mnist.get_data('save.pkl')
x_train = mnist.train_img
t_train = mnist.train_label
x_test = mnist.test_img
t_test = mnist.test_label



インスタンス化した後に呼び出すメソッドです。
save_fileはセーブした、又はセーブしたいpickleファイルの名前を
normalizeは正規化、flattenは1次元配列にするか、one_hot_labelはone_hot表現にするかのフラグです。
デフォルトではone_hot表現だけするようにしています。
    def get_data(self, save_file,
                 normalize=False, flatten=False, one_hot_label=True):
        """既に作成したpickleファイルからデータを読み込む(なければ作成する)"""



まだpickleが作られていなければ、create_dataで作成します。
pickleがあるか、もしくは作った後にdataset変数としてそれを読み込みます。
インスタンスのtrain_img等の属性にそれらを格納しておきます。
normalize、flattenがTrueならば、そうなるように処理をします。
        if not os.path.exists(save_file):
            self.create_data(save_file)

        with open(save_file, 'rb') as f:
            dataset = pickle.load(f)

        self.train_img = dataset['train_img']
        self.train_label = dataset['train_label']
        self.test_img = dataset['test_img']
        self.test_label = dataset['test_label']

        if flatten:
            self.to_flatten()

        if normalize:
            self.to_normalize()

        if not one_hot_label:
            self.no_hot_label()


reshapeで1次元に変更しています。今回はMNIST画像なので、28*28の784と入れています。
    def to_flatten(self):
        """画像を1次元にする (28, 28) →(784,)"""

        self.train_img = self.train_img.reshape(len(self.train_img), 784)
        self.test_img = self.test_img.reshape(len(self.test_img), 784)


これは正規化処理です。255で割るだけです。
    def to_normalize(self):
        """画像のピクセル値を0.0~1.0に正規化する"""

        self.train_img = self.train_img.astype(np.float32) / 255.0
        self.test_img = self.test_img.astype(np.float32) / 255.0


今回はデータ作成時にone_hot表現になるようにしていますが、このメソッドでそれを変更できます。
    def no_hot_label(self):
        """one_hot表現をなくす"""

        self.train_label = self.train_label.argmax(axis=1)
        self.test_label = self.test_label.argmax(axis=1)


上記の3つは、それぞれ逆のことをするメソッドも欲しいですね。

pickleの作成メソッドです。
    def create_data(self, save_file):
        """画像・ラベルを読み込み、pickleとして作成・保存する"""

        train_img, train_label, test_img, test_label = [], [], [], []

        # traningディレクトリはの画像はtrain_img, train_labelへ
        # testingディレクトリはtest_img, test_labelへ
        kinds = [
            ['training', train_img, train_label],
            ['testing', test_img, test_label],
        ]
        for kind in kinds:

            # このiはディレクトリ名であると同時に、正解ラベル名
            for i in range(10):

                # mnist_png/training/0 等のパス
                dir_path = os.path.join('mnist_png', kind[0], str(i))

                # [mnist_png/training/0/1.png, mnist_png/training/0/2.png]等のリスト
                file_paths = [os.path.join(dir_path, file)
                              for file in os.listdir(dir_path)]

                # [[画像のnumpy配列], [画像のnumpy配列], ...])のような2次元配列
                images = [np.asarray(Image.open(path)).reshape(1, 28, 28) for path in file_paths]
                kind[1].extend(images)

                # ディレクトリ名のiが、そのまま正解ラベル名なので、1を入れる
                label = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                label[i] = 1
                labels = [label for x in file_paths]
                kind[2].extend(labels)

        dataset = {
            'train_img': np.array(train_img),  # (60000, 28, 28)
            'train_label': np.array(train_label),  # (60000, 10)
            'test_img': np.array(test_img),  # (10000, 28, 28)
            'test_label': np.array(test_label),  # (10000, 10)
        }

        with open(save_file, 'wb') as f:
            pickle.dump(dataset, f, -1)