C#での画像処理 – 土台編

今回から数回に分けてC#での画像処理で遊んでみようかと思います。第1回目となる今回は土台づくり。

画像といえばBitmap、非圧縮でRGBの色抽出もでき、Pixel単位で処理をするならこれ以上ないデータ形式です。ただこのBitmap、2, 3厄介なところがあります。それは

  • 画素ごとのSetPixelが遅いためLockBits, UnlockBitsが必要
  • その結果必要なデータがBitmapとBitmapDataに跨っている
  • さらにBitmapはIDisposableを実装するのでusingのたびにネストが1段深まる

あたりです。この辺りを忘れて処理ができる構造のほうが画像処理に向いている、と思うんです。私は。なので今回は画像処理の土台となるデータ構造を作りましょう。1つ大きな制限を課しておきます、それはモノクロ画像しか扱わないということです。(ちょっとお遊びするにはカラーは情報量が多すぎるのが一番の理由です。

 MonochromeImage.cs

namespace NormalizeCorrelation
{
    using System;
    using System.Drawing;
    using System.Drawing.Imaging;
    internal class MonochromeImage
    {
        private byte[,] data;
        private int width;
        private int height;

        internal MonochromeImage(int width, int height)
        {
            data = new byte[width, height];
            this.width = width;
            this.height = height;
        }
        internal unsafe MonochromeImage(Bitmap srcImage)
            : this(srcImage, new Rectangle(Point.Empty, srcImage.Size))
        {
        }

        internal unsafe MonochromeImage(Bitmap srcImage, Rectangle targetRect)
        {
            if ((targetRect.X < 0) ||
                (srcImage.Width < (targetRect.X + targetRect.Width)) ||
                (targetRect.Y < 0) ||
                (srcImage.Height < (targetRect.Y + targetRect.Height)))
            {
                throw new ArgumentOutOfRangeException();
            }
            BitmapData bitmapData = srcImage.LockBits(new Rectangle(Point.Empty, srcImage.Size),
                ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            data = new byte[srcImage.Width, srcImage.Height];
            byte* topAddress = (byte*)bitmapData.Scan0;
            width = targetRect.Width;
            height = targetRect.Height;
            int stride = bitmapData.Stride;

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    data[x, y] = (byte)(
                        (0.114478 * topAddress[y * stride + 3 * x]) +       // B
                        (0.586611 * topAddress[y * stride + 3 * x + 1]) +   // G
                        (0.298912 * topAddress[y * stride + 3 * x + 2])     // R
                        );
                }
            }
            srcImage.UnlockBits(bitmapData);
        }
        internal int Width
        {
            get { return width; }
            set { width = value; }
        }
        internal int Height
        {
            get { return height; }
            set { height = value; }
        }
        internal byte this[int x, int y]
        {
            // 3x3フィルタ処理などで画像領域外にアクセスしたときに
            // 自動的に折り返して境界を意識不要にしてやる。
            get {
                int roundedX = x < 0 ? 0 : ((width <= x) ? width - 1 : x);
                int roundedY = y < 0 ? 0 : ((height <= y) ? height - 1 : y);
                return data[roundedX, roundedY];
            }
            set {
                if (0 <= x && x < width && 0 <= y && y < height)
                {
                    data[x, y] = value;
                }
            }
        }
        internal unsafe Bitmap GetAsBitmap()
        {
            Bitmap saveImage = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);
            BitmapData bitmapData = saveImage.LockBits(new Rectangle(Point.Empty, new Size(Width, Height)),
                ImageLockMode.WriteOnly,
                PixelFormat.Format8bppIndexed);
            byte* topAddress = (byte*)bitmapData.Scan0;
            int stride = bitmapData.Stride;
            for(int y = 0; y < Height; y++){
                for (int x = 0; x < Width; x++)
                {
                    topAddress[y * stride + x] = data[x, y];
                }
            }
            saveImage.UnlockBits(bitmapData);

            // ColorPaletteはコンストラクタ公開なしなので参照コピーしてくる。
            ColorPalette palette = saveImage.Palette;
            for(int i = 0; i <= byte.MaxValue; i++){
                palette.Entries[i] = Color.FromArgb(i, i, i);
            }
            saveImage.Palette = palette;
            return saveImage;
        }

    }
}

こんな感じでいかがでしょうか。対象としたのは24bpp(RGB888)のBitmapです。α含めた32bppも対応しても良かったのですがやめときました。NTSCのグレー係数という重みをつけてモノクロ画素を算出・保持しています。データの並びは、B, G, Rなので注意。

画像アクセスにはインデクサを公開します。読み書き両方可能にしています。よくある画像処理フィルタに、オペレータとして3×3や5×5を取るものがあります。つまり対象とする画素を基準としてX, Y方向に±1画素の自分含めた周囲9画素や、±2画素の周囲25画素の画素値に対して演算した結果を自画素値とする処理です。この処理をする際、画像のフチでは領域外アクセスしないよう気をつけねばなりませんが、インデクサとしているので適宜内部で丸めたり例外スローしたりできて便利です。

出力結果確認のためBitmap出力をつけました。8bppのモノクロ画像にしていますが、24bppのほうが都合がよいかも。。?これは今後作りながら手を加えていくことになるかと思います。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中