C++
OpenCV
機械学習
MachineLearning

MNISTデーターをOpenCvで画像化する[C/C++]

MNISTデーターをOpenCvで画像化する

以前、MNISTデーターを.NETを使って画像化しましたが、今回はC/C++のみでpngファイルとして作成します。

保存フォルダーはプロジェクトフォルダーに自動で作成されます。作成した画像を削除するときは、フォルダーごと削除してください。

画像化するファイルはトレーニング用のイメージです。応用すれば、テスト用イメージも画像化できますが、今回は記述してません。

あらかじめ MNISTのサイト からトレーニング用のファイル(下)をダウンロードしてプロジェクトフォルダーに入れておきます(7-zipなどで解凍をお忘れなく)。もちろんOpenCv3.xもダウンロードして、有効にしておきます。

・train-images-idx3-ubyte.gz: training set images (9912422 bytes)
・train-labels-idx1-ubyte.gz: training set labels (28881 bytes)

分かりやすくするためソースファイルとヘッダーファイルに分けました。

プログラムのポイント

  • ヘッダーの構造体

ファイルのヘッダーは分りやすいように構造体で作成しました。

ヘッダーの構造体
struct trainingLabels {
    int magicNumLabels;
    int numOfItems;
}labelsInfo;

struct trainingImage {
    int magicNumber;
    int numOfImages;
    int Raws;
    int Columns;
}imagesInfo;
  • エンディアンの変更

ヘッダー部(int型)はビッグエンディアン(非インテル系)なのでリトルエンディアンに変更します。変更方法はいろいろありますが、個人的にわかりやすいコードにしました。
BitWise演算を使います。変換にはビットシフトを使い、8ビットづつ左辺と右辺で比較しいくと、最終的に1バイト分の順番が入れ替わります。

エンディアンの変更
labelsInfo.magicNumLabels = (labelMagicBit << 24  | (labelMagicBit << 8) & 0x00ff0000) | ((labelMagicBit >> 8) & 0x0000ff00 | labelMagicBit >> 24);

  • ピクセル値への配列の作成

ピクセル値(0-255)は一文字づつ符号なし1バイトの配列にいれます。unsigned charを使うのが一般的のようですが、uint8_tを型にします。__int8でも大丈夫です。

ピクセル値配列作成
uint8_t* pixArr = new uint8_t[numPixels];

  • 配列からMatの作成

OpenCvのMatで画像を作ります。第三引数はピクセル値の配列へのポインターです。ピクセル値を二次配列にして作成しても構いません。その際は、forループを追加することになるので、より時間がかかると思います。

Matの作成
cv::Mat matrix = cv::Mat(cv::Size(imagesInfo.Columns, imagesInfo.Raws), CV_8UC1, pixArr);
  • ファイルインデックスの求め方

各ファイルのインデックスは既存のファイル数を求めて、次の数字を割り当てています。このコードはMSDNの例を参照しました。大変簡潔で分かりやすいです。

        struct _finddata_t fdata;
        intptr_t  hFile;
        fcount = 0;

        if ((hFile = _findfirst(checkAddress, &fdata)) == -1L) {

        }else {

            while (_findnext(hFile, &fdata) == 0) {
                fcount++;
             }

            _findclose(hFile);

        }

        sprintf_s(saveAddress, ".\\image\\%d\\index_%d.png", labeTmp, fcount);

実行結果

プログラムを実行すると各数値のフォルダーにそれぞれ画像が保存されます。6万文字を変換するには数十分はかかると思います。

ここから本当のOpnenCvの出番です。機械学習として作成した画像からカスケード分類器やニューラルネットワークのトレーニングデーターを作成できます。文字の場合は、カスケード分類器よりニューラルネットワークの方が適しています。OpenCVならこうした機械学習を使った画像の加工が自由にできるクラスが豊富に用意されています。時間があればそのプログラムを新たに書いてみたいと思います。

プログラムのコード

Header.h
#pragma once

#include <stdio.h>
#include <Windows.h>
#include <direct.h>
#include <io.h>
#include "opencv2\opencv.hpp"

/*//////////////////////////////////////////////

train-images.idx3-ubyte: training set images
train-labels.idx1-ubyte: training set labels
t10k-images.idx3-ubyte:  test set images
t10k-labels.idx1-ubyte:  test set labels

/////////////////////////////////////////////*/


struct trainingLabels {
    int magicNumLabels;
    int numOfItems;
}labelsInfo;

struct trainingImage {
    int magicNumber;
    int numOfImages;
    int Raws;
    int Columns;
}imagesInfo;

int labelMagicBit;
int labelNumBit;
int imageMagicBit;
int imageNumBit;
int imageRowsBit;
int imageColomnsBit;
int maxRead;
int numPixels;
int fromWhere = 0;
uint8_t pixsTmp;
int fcount;

main.cpp
#include "Header.h"

int pixelChange();

int main(void) {

    printf_s("何枚目まで変換しますか? (0 - 60000) 0はファイルの情報を参照します。\n");
    scanf_s("%d", &maxRead);
    if (maxRead > 60000 || maxRead < 0) {

        printf_s("\n有効な値を入力してください。\n\n");
        system("pause");
        return 0;

    }

    if (maxRead < fromWhere) {
        printf_s("\n変換開始点が終了枚数を超過しています。fromWhere変数(現在の設定:%d)か終了枚数(%d)を変更してください。\n\n",fromWhere,maxRead);
        system("pause");
        return 0;
    }

    if ((_access(".\\image", 00)) == -1) {
        _mkdir(".\\image");
    }

    char addressDir[_MAX_PATH];
    for (int d = 0; d < 10; d++) {

        sprintf_s(addressDir,".\\image\\%d", d);

        if ((_access(addressDir, 00)) == -1) {
            _mkdir(addressDir);
        }

    }

    printf_s("\n");

    if (pixelChange() != 1) {
        printf_s("\n変換に失敗しました。\n\n");

    }
    else {
        printf_s("\n変換に成功しました。\n\n");
    }

    system("pause");
    //Sleep(4000);
    return 0;
}

int pixelChange() {

    FILE *fpLabel;
    FILE *fpImage;

    errno_t retLab = fopen_s(&fpLabel,".\\train-labels.idx1-ubyte", "r");
    if (retLab != 0) {
        printf_s("トレーニング用ラベルファイルが見つかりません。終了します。.\n");
        system("pause");
        return 0;
    }

    errno_t retImg = fopen_s(&fpImage, ".\\train-images.idx3-ubyte", "r");
    if (retImg != 0) {
        printf_s("トレーニング用イメージファイルが見つかりません。終了します。.\n");
        system("pause");
        return 0;
    }

    fread_s(&labelMagicBit, sizeof(int), sizeof(int), 1, fpLabel);
    fread_s(&labelNumBit, sizeof(int), sizeof(int), 1, fpLabel);
    fread_s(&imageMagicBit, sizeof(int), sizeof(int), 1, fpImage);
    fread_s(&imageNumBit, sizeof(int), sizeof(int), 1, fpImage);
    fread_s(&imageRowsBit, sizeof(int), sizeof(int), 1, fpImage);
    fread_s(&imageColomnsBit, sizeof(int), sizeof(int), 1, fpImage);

    labelsInfo.magicNumLabels = (labelMagicBit << 24  | (labelMagicBit << 8) & 0x00ff0000) | ((labelMagicBit >> 8) & 0x0000ff00 | labelMagicBit >> 24);
    labelsInfo.numOfItems = (labelNumBit << 24 | (labelNumBit << 8) & 0x00ff0000) | ((labelNumBit >> 8) & 0x0000ff00 | labelNumBit >> 24);
    imagesInfo.magicNumber = (imageMagicBit << 24 | (imageMagicBit << 8) & 0x00ff0000) | ((imageMagicBit >> 8) & 0x0000ff00 | imageMagicBit >> 24);
    imagesInfo.numOfImages = (imageNumBit << 24 | (imageNumBit << 8) & 0x00ff0000) | ((imageNumBit >> 8) & 0x0000ff00 | imageNumBit >> 24);
    imagesInfo.Raws = (imageRowsBit << 24 | (imageRowsBit << 8) & 0x00ff0000) | ((imageRowsBit >> 8) & 0x0000ff00 | imageRowsBit >> 24);
    imagesInfo.Columns = (imageColomnsBit << 24 | (imageColomnsBit << 8) & 0x00ff0000) | ((imageColomnsBit >> 8) & 0x0000ff00 | imageColomnsBit >> 24);

    printf_s("Label Magic Number : %d\n", labelsInfo.magicNumLabels);
    printf_s("Label Number of items : %d\n\n", labelsInfo.numOfItems);
    printf_s("Image Magic Number : %d\n", imagesInfo.magicNumber);
    printf_s("Image Number Of Items : %d\n\n", imagesInfo.numOfImages);
    printf_s("Image Raws : %d\n", imagesInfo.Raws);
    printf_s("Image Columns : %d\n\n", imagesInfo.Columns);

    numPixels = imagesInfo.Raws * imagesInfo.Columns;

    uint8_t* pixArr = new uint8_t[numPixels];

    for (int n = fromWhere; n < maxRead; n++) {

        fseek(fpImage, (numPixels * n) + sizeof(trainingImage), SEEK_SET);

        uint8_t labeTmp = 0;
        fread_s(&labeTmp, 1, 1, 1, fpLabel);
        //printf_s("num is %d. Now, %d done.\n", labeTmp, n + 1);

        for (int i = 0; i < numPixels; i++) {

            pixsTmp = 0;
            fseek(fpImage, i + (numPixels * n) + sizeof(trainingImage), SEEK_SET);
            fread_s(&pixsTmp, 1, 1, 1, fpImage);
            pixArr[i] = (pixsTmp - 255) * -1;

            /*///////////// Check Pixel Value ///////////////////////

            if (i == numPixels - 1) {
                printf_s("%d\n\n", pixsTmp);
            }
            else {
                printf_s("%d,", pixsTmp);
            }

            ////////////////////////////////////////////////////////*/
        }

        cv::Mat matrix = cv::Mat(cv::Size(imagesInfo.Columns, imagesInfo.Raws), CV_8UC1, pixArr);

        char saveAddress[_MAX_PATH];
        char checkAddress[_MAX_PATH];
        struct _finddata_t fdata;
        intptr_t  hFile;

        sprintf_s(checkAddress, ".\\image\\%d\\*.*", labeTmp);

        fcount = 0;

        if ((hFile = _findfirst(checkAddress, &fdata)) == -1L) {

        }else {

            while (_findnext(hFile, &fdata) == 0) {
                fcount++;
             }

            _findclose(hFile);

        }

        sprintf_s(saveAddress, ".\\image\\%d\\index_%d.png", labeTmp, fcount);
        cv::imwrite(saveAddress, matrix);


    }

    delete[] pixArr;
    fclose(fpLabel);
    fclose(fpImage);

    return 1;
}