7-2 MNIST画像データを開いて中身を得る

THE MNIST DATABASE of handwritten digits」ページからダウンロードし、Windowsなら7-Zipアプリなどで展開して現れたt10k-images.idx3-ubyte、t10k-labels.idx1-ubyte、train-images.idx3-ubyte、train-labels.idx1-ubyteファイルは、以降で説明するサンプルのHTMLファイルと同じフォルダなどに置いておきます。

以下では、MNISTの画像データをJavaScriptから扱えるようにするために、次のようなアプリを作成します。

  1. HTMLフォームの[ファイルを選択]ボタンをクリックする。
  2. [開く]ダイアログボックスで、t10k-images.idx3-ubyteかtrain-images.idx3-ubyteを選ぶ。
  3. ファイルに含まれるデータを読み取り、すべての画像データを含む配列を作成する。
  4. その配列を使って、画像を画面に表示する。

HTMLファイルでは、type=”file”を指定したinput要素を作成します。これは、画像データのファイルの読み取りに使用します。また画像表示に利用するdiv要素も作成します。

<input type="file" id="input-file">
<div id="image-container"></div>

以下はJavaScriptコードです。

const inputFile = document.getElementById('input-file');

inputFile.addEventListener('change', (e) => {
    // 取得したファイル
    const files = e.target.files;
    console.log(files[0].name); // ファイル名
    // ファイルの読み取りに成功したら、readPromise.then()を実行する
    new Promise((resolve, reject) => {
        const reader = new FileReader();
        // ファイルの読み取りに成功したら解決する
        reader.onload = (e) => {
                resolve(e.target.result);
            }
            //  MNIST画像ファイルをArrayBufferとして読み取る
        reader.readAsArrayBuffer(files[0]);
    }).then((buffer) => {
        // ArrayBufferオブジェクトを受け取る
        console.log(buffer); // ArrayBuffer(47040016)

        // 扱いやすいようにDataViewオブジェクトにする
        const dataView = new DataView(buffer);
        // マジックナンバー
        const magicNum = dataView.getInt32(0);
        console.log(magicNum); // 2051
        // 含まれる画像の数
        const itemNum = dataView.getInt32(4);
        console.log(itemNum); // 60000
        //画像の高さ
        const rows = dataView.getInt32(8);
        console.log(rows); // 28
        // 画像の幅
        const columns = dataView.getInt32(12);
        console.log(columns); // 28
        // データを入れる配列
        const dataArray = [];
        let offset = 0;
        // 含まれる画像数分だけ繰り返す
        for (let i = 0; i < itemNum; i++) {
            dataArray.push([]);
            for (let j = 0; j < rows; j++) {
                for (let k = 0; k < columns; k++) {
                    // dataArray配列に。dataViewからの画像データを追加
                    // 画像データは0016から始まるので、その分を足しておく
                    dataArray[i].push(dataView.getUint8(16 + offset));
                    offset++;
                }
            }
        }
        // dataArray配列には、60,000個の画像(28x28)データが含まれる
        console.log(dataArray.length); // 60000
        console.log(dataArray[0]); // 1つめのデータ

        // dataArrayに集めた画像データの中から1つを実際に表示してみる
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        canvas.width = columns;
        canvas.height = rows;
        // 空のイメージデータ
        imageData = context.createImageData(canvas.width, canvas.height);
        // 表示したい画像の、dataArray配列でのインデックス番号
        const dataIndex = 0;
        // 空のimageDataに、dataArray配列内の適切なデータを割り当てる。
        for (let x = 0; x < imageData.height; x++) {
            for (let y = 0; y < imageData.width; y++) {
                var index = x + (y * imageData.width);
                let step4 = index * 4;
                // RGBには同じ値を設定し、モノクロにする
                // [R]
                imageData.data[step4 + 0] = dataArray[dataIndex][index];
                // [G]
                imageData.data[step4 + 1] = dataArray[dataIndex][index];
                // [B]
                imageData.data[step4 + 2] = dataArray[dataIndex][index];
                // [alpha]
                // 透明度はなし
                imageData.data[step4 + 3] = 255;;
            }
        }
        // キャンバスにイメージデータを描画して、ページに表示する
        context.putImageData(imageData, 0, 0);
        document.getElementById('image-container').appendChild(canvas);
    });
}, false);

たとえば訓練用画像データを含んだtrain-images.idx3-ubyteを開くと、下図のような結果が得られます。

1つめの出力はファイル名です。type属性を”file”にしたinput要素で、’change’イベントの発生を監視すると、ユーザーが能動的に開こうとしているファイルを特定することができます。ここではそのファイルをconst files = e.target.filesで取得し、ファイル名をfiles[0].nameで出力しています。

2つめのArrayBuffer(47040016)という出力は、ファイルをFileReaderのreadAsArrayBuffer()で読み取った結果の生データを指しています。47040016という値は、28 x 28 x 60,000 + 16(ヘッダ領域分)に一致します。ArrayBufferはそのままでは操作できないので、ここではDataViewオブジェクトを使って扱いやすくしています。

3つめの2051は、dataView.getInt32(0)によるマジックナンバーの出力です。同様に、60000はdataView.getInt32(4)の、2つの28はdataView.getInt32(8)とdataView.getInt32(12)で得た結果の出力です。いずれもtrain-images.idx3-ubyteを説明する次の表と一致しています。

コードではその後、配列dataArrayに画像データを集め、確認としてdataIndexで指定されたインデックスにあるdataArray内の画像データを、実際に描画しています。dataIndexを0にすると、下図の5に読める画像が表示されるので、dataArray内の最初の画像データは5であることが分かります。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA