ラプラシアンフィルタ

## 導入
今回はラプラシアンフィルタを C# で書いてみます。画像ソフトでよく「輪郭強調」というのがありますがそれのことです。アルゴリズムとしては「二階微分」を使います。詳しくは「ラプラシアンフィルタ」で検索してみてください。
## プログラム
### Image.cs
イメージ(画像)を扱うクラスです。データは、byte 型の二次元配列で保持します。メソッドは、画像ファイルを読み書きするものなどがあります。
```
using System.Drawing;

namespace ImageFilter
{
    internal class Image
    {
        private byte[,] img;

        public Image()
        {
            img = new byte[0, 0];
        }

        public Image(int width, int height)
        {
            img = new byte[width, height];
        }

        public byte[,] GetImage()
        {
            return img;
        }

        public int GetWidth()
        {
            return img.GetLength(0);
        }

        public int GetHeight()
        {
            return img.GetLength(1);
        }

        public byte GetPixel(int x, int y)
        {
            return img[x, y];
        }

        public void SetPixel(int x, int y, byte value)
        {
            img[x, y] = value;
        }

        public void LoadImageGray(string filename)
        {
            Bitmap bitmap = new Bitmap(filename);
            int w = bitmap.Width;
            int h = bitmap.Height;
            img = new byte[w, h];

            for (int i = 0; i < h; i++)
            {
                for (int j = 0; j < w; j++)
                {
                    SetPixel(j, i, (byte)((bitmap.GetPixel(j, i).R + bitmap.GetPixel(j, i).B + bitmap.GetPixel(j, i).G) / 3));
                }
            }
        }

        public void SaveImage(Image image, string filename)
        {
            int w = GetWidth();
            int h = GetHeight();
            Bitmap bitmap = new(w, h);

            for (int i = 0; i < h; i++)
            {
                for (int j = 0; j < w; j++)
                {
                    byte value = image.GetPixel(j, i);
                    bitmap.SetPixel(j, i, Color.FromArgb(value, value, value));
                }
            }

            bitmap.Save(filename);
        }
    }
}
```
### Filter.cs
フィルタークラスです。イメージ（画像）クラスとマスクデータを保持します。
ExecFilter メソッドはサブクラスで実装します。
```
using System.Drawing;

namespace ImageFilter
{
    internal class Filter
    {
        private readonly Image img;
        private double[,] mask;

        public Filter()
        {
            img = new Image();
            mask = new double[0, 0];
        }

        protected byte[,] GetImage()
        {
            return img.GetImage();
        }

        protected int GetImageWidth()
        {
            return img.GetWidth();
        }

        protected int GetImageHeight()
        {
            return img.GetHeight();
        }

        protected double[,] GetMask()
        {
            return mask;
        }

        protected void SetMask(double[,] mask)
        {
            this.mask = mask;
        }

        protected int GetMaskSize()
        {
            return mask.GetLength(0);
        }

        virtual protected Image ExecFilter()
        {
            return new Image();
        }

        protected void LoadImageGray(string filename)
        {
            img.LoadImageGray(filename);
        }

        protected void SaveImage(Image image, string filename)
        {
            img.SaveImage(image, filename);
        }

        protected byte DoubleToByte(double num)
        {
            if (num > 255.0)
            {
                return 255;
            }
            else if (num < 0)
            {
                return 0;
            }
            else
            {
                return (byte)num;
            }
        }
    }
}
```
### LaplacianFilter.cs
ラプラシアンフィルタクラスです。Filter のサブクラスになっていて、ExecFilter を実装します。
```
namespace ImageFilter
{
    internal class LaplacianFilter : Filter
    {
        public LaplacianFilter(String inputFilename, String outputFilename)
        {
            LoadImageGray(inputFilename);
            SetLaplacianMask();
            SaveImage(ExecFilter(), outputFilename);
        }

        private void SetLaplacianMask()
        {
            const int maskSize = 3;
            SetMask(new double[maskSize, maskSize]
            {
                {1.0, 1.0, 1.0},
                {1.0, -8.0, 1.0},
                {1.0, 1.0, 1.0}
            });
        }

        override protected Image ExecFilter()
        {
            int w = GetImageWidth();
            int h = GetImageHeight();
            int masksize = GetMaskSize();
            Image image = new(w, h);

            for (int i = 0; i < h; i++)
            {
                for (int j = 0; j < w; j++)
                {
                    double sum = 0;
                    for (int k = -masksize / 2; k <= masksize / 2; k++)
                    {
                        for (int n = -masksize / 2; n <= masksize / 2; n++)
                        {
                            if (j + n >= 0 && j + n < w && i + k >= 0 && i + k < h)
                            {
                                sum += GetImage()[j + n, i + k] * GetMask()[n + masksize / 2, k + masksize / 2];
                            }
                        }
                    }
                    image.SetPixel(j, i, DoubleToByte(sum));
                }
            }
            return image;
        }
    }
}
```
### Program.cs
エントリポイントとなるメインプログラムです。
```
using ImageFilter;

new LaplacianFilter("src.png", "dst.png");
```
## 実行の前に
変換したい画像を「src.png」として用意してください。
## 実行結果
元の画像
![src.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3430684/bf1bdef0-4fd3-d871-e260-4d12c72bb609.png)

輪郭強調した画像
![dst.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3430684/476990a8-1d53-c710-f284-ea1051f93f71.png)

## まとめ
ラプラシアンフィルタを C# で実装してみました。フィルタのマスクデータを取り替えると、画像を鮮明化（シャープ）にしたり、ぼやかしたりという画像加工もできるのでいろいろ試してみてください。