LoginSignup
5

More than 5 years have passed since last update.

posted at

updated at

MNISTの手書き数字画像データをJavaでベクトル化、画像化する

はじめにMNISTの手書き数字画像データの仕様を説明した後、ベクトル化したり、画像として表示・保存するJavaのコードを説明します。

MNISTの手書き数字画像データセットとは

ネットに公開されている0から9までの手書き数字画像データセットです。
個々の画像の大きさは28ピクセル四方で、色は255階調のグレースケールです。
機械学習の学習および評価によく利用されています。

データ仕様

MNISTの手書き数字データの仕様について説明します。

訓練データセット

訓練(学習)に用いる0から9までの6万枚の画像データと正解ラベルデータがあります。

訓練用画像データ

train-images-idx3-ubyte.gz
訓練用画像データが独自のバイナリフォーマットで格納されています。

オフセット データ型 説明
0 32bit整数 2051 マジックナンバー(MSB First)
4 32bit整数 60000 画像数
8 32bit整数 28 画像の縦のピクセル数
12 32bit整数 28 画像の横のピクセル数
16 符号なしバイト 0から255 1枚目の画像の1行1列目のピクセルのグレースケール値
17 符号なしバイト 0から255 1枚目の画像の1行2列目のピクセルのグレースケール値
・・・ 符号なしバイト 0から255 6万枚目の画像の28行目28列目のピクセルのグレースケール値

訓練用正解ラベルデータ

train-labels-idx1-ubyte.gz
訓練データの正解ラベルが独自のバイナリフォーマットで格納されています。

オフセット データ型 説明
0 32bit整数 2049 マジックナンバー(MSB First)
4 32bit整数 60000 画像数
8 符号なしバイト 0から9 1枚目の画像の正解ラベル
9 符号なしバイト 0から9 2枚目の画像の正解ラベル
・・・ 符号なしバイト 0から9 6万枚目の画像の正解ラベル

テストデータセット

テスト(評価)に用いる0から9までの1万枚の画像データと正解ラベルデータがあります。

テスト用画像データ

t10k-images-idx3-ubyte.gz
テスト用画像データが独自のバイナリフォーマットで格納されています。

オフセット データ型 説明
0 32bit整数 2051 マジックナンバー(MSB First)
4 32bit整数 10000 画像数
8 32bit整数 28 画像の縦のピクセル数
12 32bit整数 28 画像の横のピクセル数
16 符号なしバイト 0から255 1枚目の画像の1行1列目のピクセルのグレースケール値
17 符号なしバイト 0から255 1枚目の画像の1行2列目のピクセルのグレースケール値
・・・ 符号なしバイト 0から255 1万枚目の画像の28行目28列目のピクセルのグレースケール値

テスト用正解ラベル

t10k-labels-idx1-ubyte.gz
テストデータの正解ラベルが独自のバイナリフォーマットで格納されています。

オフセット データ型 説明
0 32bit整数 2049 マジックナンバー(MSB First)
4 32bit整数 10000 画像数
8 符号なしバイト 0から9 1枚目の画像の正解ラベル
9 符号なしバイト 0から9 2枚目の画像の正解ラベル
・・・ 符号なしバイト 0から9 1万枚目の画像の正解ラベル

Javaによるベクトル化、画像化

GitHub上のソースコード
簡単な使い方はREADME.mdをご覧ください。

画像データの読み込み

DataInputStreamを使ってデータを読み込みます。
readIntでマジックナンバー、画像数、画像の縦のピクセル数、画像の横のピクセル数を読み込みます。
次元数は28*28=784になります。
double型の2次元配列featuresにreadUnsignedByteに画像データを読み込みます。
1次元目が画像のインデックスで、2次元目が次元のインデックスです。
機械学習での利用を考慮して、値を255.0で割って正規化(normalize)しています。

ImageDataSet.java
    private void loadFeatures() throws IOException {
        System.out.println("Loading feature data from " + fileName + " ...");
        DataInputStream is = new DataInputStream(new GZIPInputStream(new FileInputStream(Const.BASE_PATH + fileName)));
        is.readInt();
        numImages = is.readInt();
        numDimensions = is.readInt() * is.readInt();

        features = new double[numImages][numDimensions];
        for (int i = 0; i < numImages; i++) {
            for (int j = 0; j < numDimensions; j++) {
                features[i][j] = (double) is.readUnsignedByte() / 255.0;
            }
        }
    }

ラベルデータの読み込み

DataInputStreamを使ってデータを読み込みます。
readIntでマジックナンバー、画像数を読み込みます。
int型の配列labelsにreadUnsignedByteで正解ラベルを読み込みます。

LabelDataSet.java
    private void loadLabels() throws IOException {
        System.out.println("Loading label data from " + fileName + " ...");
        DataInputStream is = new DataInputStream(new GZIPInputStream(new FileInputStream(Const.BASE_PATH + fileName)));

        is.readInt();
        numLabels = is.readInt();

        labels = new int[numLabels];
        for (int i = 0; i < numLabels; i++) {
            labels[i] = is.readUnsignedByte();
        }
    }

画像の表示

テキストで表示

引数で画像のインデックスを指定して、コンソールに画像の概形をテキストで表示します。

ImageViewer.java
    public void showImageAsText(int index) {
        System.out.println("Label: " + labels[index]);
        for (int i = 0; i < 28; i++) {
            for (int j = 0; j < 28; j++) {
                double value = images[index][i * 28 + j];
                if (value > 0.0) {
                    System.out.print("**");
                } else {
                    System.out.print("  ");
                }
            }
            System.out.println();
        }
    }

BufferedImageを作成

ベクトル化された画像データを復元して、BufferedImageを作成します。
値は正規化されているので、255.0を掛けて元のグレースケール値に戻します。

ImageViewer.java
    private BufferedImage makeImage(int index) {
        BufferedImage image =
                new BufferedImage(28, 28, BufferedImage.TYPE_INT_RGB);

        for (int i = 0; i < 28; i++) {
            for (int j = 0; j < 28; j++) {
                int value = (int) (images[index][i * 28 + j] * 255.0);
                image.setRGB(j, i, 0xff000000 | value << 16 | value << 8 | value);
            }
        }

        return image;
    }

ダイアログに表示

makeImageで読み込んだBufferedImageをダイアログに表示します。

ImageViewer.java
    public void showImage(int index) {
        BufferedImage image = makeImage(index);
        Icon icon = new ImageIcon(image);
        JOptionPane.showMessageDialog(null, labels[index], "MnistImageViewer", JOptionPane.PLAIN_MESSAGE, icon);
    }

画像ファイルに保存

makeImageで読み込んだBufferedImageをgifファイルに保存します。

ImageViewer.java
    public void saveImage(String dir, String prefix, int index) throws IOException {
        BufferedImage image = makeImage(index);
        File file = new File(dir + "/" + prefix + "_" + String.format("%05d", index) + "_" + labels[index] + ".gif");
        if (file.exists()) file.delete();
        ImageIO.write(image, "gif", file);
    }

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
What you can do with signing up
5