【C#】画素ごとの輝度を得て、テキスト形式で保存する

機械学習や画像処理にて解析を行う際には、画像を数値化しなければなりません。今回は手書き数字の画像の輝度情報を読み取り、テキストファイルにして保存する方法をメモります。

実行環境

この環境でやってみました。特別なライブラリは使用していません。

  • Visual Studio 2019
  • .NET Core 3.0 コンソールアプリケーション
  • ペイント (Win10 付属のやつ)

1. 手書き数字を用意する

ペイントなどのソフトを用いて 32×32 ピクセルで数字を書きます。今回は試しに2を書きました。これを100個ほど用意します(鬼畜)。 保存形式は Jpeg をおススメします。

f:id:takunology:20200307220653p:plain

用意したら、Visual Studio のプロジェクト内にフォルダごと移動します。私は手書き数字の入ったディレクトリ名を training にしました。

2. 画素から輝度を得る

追加したファイルたちはプロジェクト直下にあります。しかし、実行する際に実行ファイルは bin/debug/netcoreapp3.0 にて生成されるので、ディレクトリ参照を行う際には3つ上の階層を指定する必要があります。

画像を開くためには Bitmap クラスを用います。インスタンスを生成する際に、ファイルパスを指定します。画像サイズはこちらから指定してもよいですが、bitmapクラスには幅と高さを取得するためのメソッドがあるので、これを利用します。幅は bitmap.Widthと高さは bitmap.Height で指定可能です。

輝度を得るためには Color 構造体を用います。この構造体に対して、bitmapインスタンスの GetPixel メソッドを実行し、その画素データを取得します。構造体にはその画素に関する様々なデータ(色やサイズ、輝度など)が入るので、この構造体の輝度を参照します。そのためには GetBrightness メソッドを実行します。この値を保持しておくには、あらかじめ変数が必要です。

画像データは幅と高さの2次元で表現されるので 2重 for文を用いて処理します。ちなみに、二次元配列に対して輝度データも代入しているので全体では3次元のデータを扱うことになります。配列以外にも、Vectorを用いる方法があるようです。(おそらくC++かな...?)

using System.Drawing;

static float[,] JpegData = new float[0, 0]; //クラス内にて定義
static int Width = 0;
static int Height = 0;

static void GetData(int label, int num)
{
    FilePath = $"../../../training/{label.ToString()}/two{num.ToString()}.jpg";
    Bitmap bitmap = new Bitmap(FilePath);

    Width = bitmap.Width;
    Height = bitmap.Height;

    JpegData = new float[Height, Width];

    for (int i = 0; i < Height; i++)
    {
        for (int j = 0; j < Width; j++)
        {
            Color pixel = bitmap.GetPixel(i, j); //[i, j]の色情報にアクセス
            JpegData[i, j] = pixel.GetBrightness();
        }
    }
}

3. テキスト形式に書き出す

得た輝度データをテキスト形式にして書き出すには StreamWriterクラスを用います。インスタンスを生成する際にファイルパス、上書き許可、エンコード等を指定できます。エンコードEncoding クラスの GetEncode() メソッドで指定可能です。 あと、StreamWriter による書き込みが終わった後は 必ず Close() メソッドでファイルを閉じて ください。

また、生成するテキストをまとめておくためのディレクトリも作成します。ディレクトリを新しく作るには Directory.CreateDirectory メソッドを用い、引数には作成したい階層およびディレクトリ名を入れます。

今回はカンマ区切りで画素ごとにデータを区切っていきます。また、書き込みが終わる最後の画素になった場合(今回は0から始まって31番目で終わる場合)はカンマを付けずに終わります。

static void WriteToFile(int label, int num)
{
    Encoding encode = Encoding.GetEncoding("utf-8");
    FilePath = $"../../../data/{label.ToString()}/{num.ToString()}.txt";
    Directory.CreateDirectory("../../../data/2");

    StreamWriter streamWriter = new StreamWriter(FilePath, false, encode);

    for (int i = 0; i < Height; i++)
    {
        for (int j = 0; j < Width; j++)
        {
            if (i == 31 && j == 31)
            {
                streamWriter.Write($"{JpegData[i, j]}");
                break;
            }
            streamWriter.Write($"{JpegData[i, j]},");
        }
    }

    streamWriter.Close();
}

4. 実行

あとは実行するためのコードを書きます。

static void Main(string[] args)
{
    Console.WriteLine("Get Brihtness.");

    int label = 2; //2の手書き数字を変換する
    int num = 100; //手書き数字の枚数

    for (int i = 1; i <= num; i++)
    {
        GetData(label, i);
        WriteToFile(label, i);
        Console.WriteLine($"File {i}.txt Created.");
    }

    Console.WriteLine("Finish.");
}

今回は100枚の画像データがあるので100回繰り返して輝度を取得し、ファイルを書き込みます。つまり、実行後は100個のデータが生成されます。

f:id:takunology:20200307224425p:plain

やばいですね。こんなにたくさんのファイルがわずか5秒程度で生成されました。

ファイルの生成場所はソースコード内にあるように、プロジェクトディレクトリの data/2 に入っています。

f:id:takunology:20200307224830p:plain

できたファイルの中身はどうなっているでしょうか???

f:id:takunology:20200307224940p:plain

うへぇ~すごく文字列です... (*´ω`)

ちなみにこのデータは1番目の画像データの輝度です。先ほど大量に書いた数字データの一番左上(1番目)がこれです。

5. おわりに

ファイルの中身を見ると画素の輝度が1に近いほど白で、0に近いほど黒くなるようです。驚きだったのは小数で表されていたことです。確かOpenCVとかだと 256 段階で表現されていたと思います。

今回は輝度の書き出しをやりましたが、これが最終目標ではありません。このデータを活用してこそ意味があります。

今回作ったコード

参考にどうぞ。リポジトリディープラーニングになっていますが、その途中で作ったものなので中に入っています。そのままディレクトリを移動せずに Program.cs を参照してもらえれば今回のコードが見れます。

github.com