C#でグレイコードを作成する

タイトルの通りC#でグレイコードを作成して画像に保存したいと思います。

グレイコードはノイズに強いので例えば、プロジェクターからパターンを投影してイメージセンサーで画像を撮影したときに座標の対応関係をとるのに使用できます。

グレイコードの考え方

0~16 までの数値でグレイコードを作成する場合の手順です。

  1. 全ての数字をビット列に直す
  2. 一番大きい数値のビット数でゼロ埋めする
  3. 以下の図のように数列を取得する

f:id:Takachan:20181215230605p:plain

一番大きい数が16なので、4ビットで表すことができるため、作成するグレイコードは4つとなります。

赤枠の一番左の赤枠の列が1つめのグレイコードで右に1桁づつ移動して4つのグレイコードとなります。そして0に黒、1に白を割り当てて保存したい画像の高さ分複製して保存すると以下のような画像が取得できます。以下例の画像の高さは32pxです。

f:id:Takachan:20181216153339p:plain

128x128で作成すると以下のようになります。

実装コード

実装環境は以下の通り。

  • VisualStudio2017
  • C#7.0
  • OpenCvSharp3

作成したプログラムはコンソールから起動可能で以下書式で起動できます。

> GrayCodePtnGenerator ${画像の幅} ${画像の高さ} ${出力先ディレクトリ}

Mainクラス

MainクラスでOpenCvSharp3を使って画像を保存しています。

通常の白黒画像と、その画像を反転した画像の2つをビットマップへ保存しています。

internal static class AppMain
{
    public static void Main(string[] args)
    {
        int width = int.Parse(args[0]);
        int height = int.Parse(args[1]);
        string destDir = args[2];

        List<byte[]> bs = GrayCode.Generate(0, width);

        // 画像の生成
        for (int i = 0; i < bs[0].Length; i++)
        {
            byte[] line = ArrayUtil.ExtBits(bs, i);
            byte[] linenega = ArrayUtil.ExtBits(bs, i);
            for (int p = 0; p < line.Length; p++)
            {
                if (line[p] != 0)
                {
                    line[p] = 255;
                    linenega[p] = 0;
                }
                else
                {
                    linenega[p] = 255;
                }
            }

            var image = new Mat(new Size(width, height), MatType.CV_8UC1); // 白黒画像用
            var imagenega = new Mat(new Size(width, height), MatType.CV_8UC1); // 白黒(反転)画像用
            for (int y = 0; y < height; y++)
            {
                OpenCvHelper.MemoryCopyToData(line, image, line.Length * y);
                OpenCvHelper.MemoryCopyToData(linenega, imagenega, line.Length * y);
            }

            Cv2.ImWrite($"{i}.bmp", image);
            Cv2.ImWrite($"{i}_nega.bmp", imagenega);
        }
    }
}

グレイコードを作成するクラス

中心となるグレイコードを生成するクラスは以下の通りです。

単一のグレイコードを生成するならToGrayCode、0~1024(とか、100~200)までのグレイコードを生成するならGenerateを使用します。

// グレーコードを作成する機能を持つクラス
public static class GrayCode
{
    // 指定した自然数をグレーコードのバイナリ配列に変換する
    public static byte[] ToGrayCode(uint n)
    {
        uint _gray = n ^ (n >> 1);
        return BitUtil.ToBinaryArray(_gray);
    }

    // 指定したグレイコードのバイナリ配列を自然数に変換する
    public static uint ToUInt(byte[] bs)
    {
        uint x = (uint)Convert.ToInt32(string.Join("", bs), 2);
        uint n = x;

        while (x != 0)
        {
            x = x >> 1;
            n ^= x;
        }

        return n;
    }

    // CからN個のグレイコードを生成します。
    public static List<byte[]> Generate(uint c, int n)
    {
        var bs = new List<byte[]>();

        for (int i = 0; i < n; i++)
        {
            bs.Add(ToGrayCode((uint)i + c));
        }

        BitUtil.PaddingZeroMaxLen(bs);

        return bs;
    }
}

その他の補助クラス

OpenCvHelper、BitUtil、ArrayUtil で汎用処理を記述しています。

// OpenCVの汎用操作を持つクラス
public static class OpenCvHelper
{
    public static unsafe void MemoryCopyToData(byte[] buffer, Mat img, int offset)
    {
        fixed (void* s = &buffer[0])
        {
            byte* b = (byte*)img.Data.ToPointer();
            Buffer.MemoryCopy(s, &b[offset], img.DataEnd.ToInt32() - img.DataStart.ToInt32(), buffer.Length);
        }
    }
}

// ビット操作に関係する汎用操作を持つクラス
public static class c
{
    // 指定したある整数をビット配列に変換する
    public static byte[] ToBinaryArray(uint n)
    {
        // ex) 10 → "1010"
        string _bit_str = Convert.ToString(n, 2);

        // "1010" → byte[] { 1, 0, 1 , }
        byte[] bits = new byte[_bit_str.Length];
        for (int i = 0; i < _bit_str.Length; i++)
        {
            bits[i] = (byte)(_bit_str[i] == '0' ? 0 : 1);
        }

        return bits;
    }

    // 指定したリスト中の最大の配列長にbyte[]の長さを揃え最長ビットに合わせてゼロ埋めする
    public static void PaddingZeroMaxLen(List<byte[]> byteList)
    {
        int maxLen = byteList.Max(array => array.Length);

        for (int bi = 0; bi < byteList.Count() - 1; bi++)
        {
            byte[] bs = byteList[bi];
            var _bsList = new List<byte>(bs);
            for (int i = 0; i < maxLen - bs.Length; i++)
            {
                _bsList.Insert(0, 0);
            }

            byteList[bi] = _bsList.ToArray();
        }
    }
}

// 配列に関係する汎用操作を持つクラス
public static class ArrayUtil
{
    // 指定しバイト配列のN番目を列挙する
    public static byte[] ExtBits(List<byte[]> byteList, int n)
    {
        byte[] byteArray = new byte[byteList.Count];

        for (int i = 0; i < byteList.Count; i++)
        {
            byteArray[i] = byteList[i][n];
        }

        return byteArray;
    }
}

ここでは使用していませんが、グレイコードをの数字に戻するにはGrayCode.Decodeを使用します。

GrayCode.Decode(new byte[] { 0, 0, 0, 0 }) => 0;

割と簡単なことですがコードが多少中くなってしまいました。今回は以上です。