Color ⇒ エッジ画像

カラー画像から隣り合っているピクセルの濃淡の違いを検出して、エッジ画像を作成します。
冗長なソースコードを省略して、なるべくシンプルに作成してみました。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プログラムの説明

  1. カラー画像からエッジを検出する image.cs のソースコードです。
    //★ エッジを検出して描画    前田 稔
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Diagnostics;
    
    public class MyForm : Form
    {
        Face    App;
        int     val= 4000;      // 閾値
        public MyForm()
        {
            App = new Face();
            App.edge = (Bitmap)App.bmp.Clone();
            Width  = App.bmp.Width;
            Height = App.bmp.Height+32;
            Paint += new PaintEventHandler(MyHandler);
            MouseDown += new MouseEventHandler(OnMyMouseDown);
        }
    
        private void MyHandler(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            App.ViewEdge(g,0,0);
        }
    
        private void OnMyMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  //マウスの左ボタン
            {   val+= 1000;
                App.Edge(val);
                VerMsg("val: ", val);
            }
            if (e.Button == MouseButtons.Right) //マウスの右ボタン
            {   App.edge = (Bitmap)App.bmp.Clone();
                val= 4000;
            }
            Invalidate();
        }
    
        public void VerMsg(string msg, int v)
        {
            string wstr;
            wstr = msg + v.ToString();
            this.Text = wstr;
        }
    }
    
    //☆ Face Object Class
    class Face
    {
        public  Bitmap  bmp = null;
        public  Bitmap  edge = null;
        string  ImgFile = "";
    
        int[,]      vect = new int[9,2]         //エッジ検出テーブル
        { {-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,0}, {0,1}, {1,-1}, {1,0}, {1,1} };
        int[]       hCoef = new int[9]{ 1, 0, -1,  2, 0, -2,  1, 0, -1 };
        int[]       vCoef = new int[9]{ 1, 2, 1,  0, 0, 0,  -1, -2, -1 };
    
        // Constructor
        public Face()
        {
            OpenFileDialog opendlg = new OpenFileDialog();
            opendlg.Filter = "画像ファイル (*.bmp)|*.bmp|すべてのファイル (*.*)|*.*" ;
            if (opendlg.ShowDialog() == DialogResult.OK)
            {   ImgFile = opendlg.FileName;
                bmp = new Bitmap(ImgFile);
            }
        }
    
        // BMP 画像を描画
        public void View(Graphics g, int x, int y)
        {   if (bmp != null)    g.DrawImage(bmp, x, y);  }
        // Edge 画像を描画
        public void ViewEdge(Graphics g, int x, int y)
        {   if (edge != null)   g.DrawImage(edge, x, y);  }
    
        // 座標[x,y]周辺のピクセルを調べてエッジを検出する
        public void  Edge(int val)
        {   Color   cor;
            for(int y=1; y<bmp.Height-1; y++)
                for(int x=1; x<bmp.Width-1; x++)
                {   if (Sobel(x, y, val))   cor = Color.Black;
                    else cor = Color.White;
                    edge.SetPixel(x,y,cor);
                }
        }
    
        // m_bmp[x,y] の周辺のピクセルを調べてエッジを検出する
        public bool  Sobel(int x, int y, int val)
        {   int[]   fx = new int[3];    //3色分
            int[]   fy = new int[3];    //3色分
            int     dx, dy;
            Color   cor;
            fx[0]= fx[1]= fx[2]= 0;
            fy[0]= fy[1]= fy[2]= 0;
            for(int i=0; i<9; i++)
            {   dx = x + vect[i,0];
                dy = y + vect[i,1];
                cor = bmp.GetPixel(dx,dy);
                fx[0] += cor.B * hCoef[i];
                fy[0] += cor.B * vCoef[i];
                fx[1] += cor.G * hCoef[i];
                fy[1] += cor.G * vCoef[i];
                fx[2] += cor.R * hCoef[i];
                fy[2] += cor.R * vCoef[i];
            }
            for(int c=0; c<3; c++)
                if (fx[c]*fx[c]+fy[c]*fy[c] > val)  return true;
            return false;
        }
    }
    
    class image01
    {
        [STAThread]
        public static void Main()
        {
            MyForm mf = new MyForm();
            Application.Run(mf);
        }
    }
    
  2. カラー画像からエッジを検出して白黒2階調の二値画像を作成します。
    C#のプログラムは、C言語を使って作成する場合に比べて非常に簡単です。
    C言語(Windows)のプログラムは エッジ検出 を参照して下さい。
    C言語では CreateDIBSection() 関数でピクセルデーターの配列にアクセスしているのですが、C#ではこれに相当する関数が見つかりません。
    そこで Bitmap 画像からピクセルを取得する GetPixel() 関数と、ピクセル毎に色を設定する SetPixel() 関数を使って作成します。
    これを使えばプログラムは簡単で解り易いのですが、難点は実行速度が遅いことでしょうか?
  3. カラー画像は RGB をそれぞれ8ビットで記録されています。
    青色(B)の8ビット 緑色(G)の8ビット 赤色(R)の8ビット
  4. エッジの検出は Sobel(ソーベル、またはゾーベル)フィルタと呼ばれる濃淡の違いを検出する手法があります。
    濃淡の違いを調べるピクセルを中心において、8方向の濃さを調べます。
    横方向の濃さを次のウエイトを掛けて演算します。
    1 0 -1
    2 0 -2
    1 0 -1
    同様に縦方向の濃さを次のウエイトを掛けて演算します。
    1 2 1
    0 0 0
    -1 -2 -1
    ウエイトの配列と、方向を決めるベクトルを定義しています。
        int[,]      vect = new int[9,2]         //エッジ検出テーブル
        { {-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,0}, {0,1}, {1,-1}, {1,0}, {1,1} };
        int[]       hCoef = new int[9]{ 1, 0, -1,  2, 0, -2,  1, 0, -1 };
        int[]       vCoef = new int[9]{ 1, 2, 1,  0, 0, 0,  -1, -2, -1 };
    
  5. 元の画像から隣り合っているピクセルの濃淡の違いを検出してエッジ画像を作成するので、二枚の画像を使います。
    bmp が入力する画像で、edge がエッジを検出して二値画像を作成する領域です。
        public  Bitmap  bmp = null;
        public  Bitmap  edge = null;
    
  6. Face Class の Constructor では、HD(ハードディスク)からカラー画像を選択します。
        // Constructor
        public Face()
        {
            OpenFileDialog opendlg = new OpenFileDialog();
            opendlg.Filter = "画像ファイル (*.bmp)|*.bmp|すべてのファイル (*.*)|*.*" ;
            if (opendlg.ShowDialog() == DialogResult.OK)
            {   ImgFile = opendlg.FileName;
                bmp = new Bitmap(ImgFile);
            }
        }
    
  7. View() が bmp の描画で、ViewEdge() が edge の画像です。
    一応画像のサイズに合わせてウインドウサイズを設定しているのですが、BMP FILE の DPI によってはサイズが合わない場合があります。
        // BMP 画像を描画
        public void View(Graphics g, int x, int y)
        {   if (bmp != null)    g.DrawImage(bmp, x, y);  }
        // Edge 画像を描画
        public void ViewEdge(Graphics g, int x, int y)
        {   if (edge != null)   g.DrawImage(edge, x, y);  }
    
  8. 濃淡の違いを検出して、エッジ画像を作成する Edge() 関数です。
    指定された座標のピクセルと隣接するピクセルの濃淡の違いを Sobel() 関数で調べます。
    両端のピクセルは対象から外します。
    edge.SetPixel() で edge にピクセルごとに設定して画像を作成します。
        // 座標[x,y]周辺のピクセルを調べてエッジを検出する
        public void  Edge(int val)
        {   Color   cor;
            for(int y=1; y<bmp.Height-1; y++)
                for(int x=1; x<bmp.Width-1; x++)
                {   if (Sobel(x, y, val))   cor = Color.Black;
                    else cor = Color.White;
                    edge.SetPixel(x,y,cor);
                }
        }
    
  9. 横方向と縦方向の濃淡を調べる Sobel() 関数です。
    val が閾値(しきいち)で、三原色を別々に濃淡の違いを調べています。
        // m_bmp[x,y] の周辺のピクセルを調べてエッジを検出する
        public bool  Sobel(int x, int y, int val)
        {   int[]   fx = new int[3];    //3色分
            int[]   fy = new int[3];    //3色分
            int     dx, dy;
            Color   cor;
            fx[0]= fx[1]= fx[2]= 0;
            fy[0]= fy[1]= fy[2]= 0;
            for(int i=0; i<9; i++)
            {   dx = x + vect[i,0];
                dy = y + vect[i,1];
                cor = bmp.GetPixel(dx,dy);
                fx[0] += cor.B * hCoef[i];
                fy[0] += cor.B * vCoef[i];
                fx[1] += cor.G * hCoef[i];
                fy[1] += cor.G * vCoef[i];
                fx[2] += cor.R * hCoef[i];
                fy[2] += cor.R * vCoef[i];
            }
            for(int c=0; c<3; c++)
                if (fx[c]*fx[c]+fy[c]*fy[c] > val)  return true;
            return false;
        }
    
  10. MyForm Class のコンストラクタでは Face Class を生成して画像を入力します。
    MyHandler() ではエッジ検出で作成した画像を描画するので、最初は入力した画像のクローンを edge にコピーします。
    画像サイズに合わせてウインドウサイズを設定します。(DPI が 96 以外のときは合わない場合があります)
        public MyForm()
        {
            App = new Face();
            App.edge = (Bitmap)App.bmp.Clone();
            Width  = App.bmp.Width;
            Height = App.bmp.Height+32;
            Paint += new PaintEventHandler(MyHandler);
            MouseDown += new MouseEventHandler(OnMyMouseDown);
        }
    
  11. MyHandler() では ViewEdge() 関数で edge の画像を描画します。
    画像を入力した直後はカラー画像が、左クリックでエッジ画像を作成すると二値画像が描画されます。
        private void MyHandler(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            App.ViewEdge(g,0,0);
        }
    
  12. マウスの左クリックで val(閾値)をアップしながらエッジ画像を作成します。
    閾値(しきいち)が大きくなるほど白い部分が増えます。
    左クリックの度に Edge() 関数を呼び出すのですが、実行速度が遅く反応が遅れるのでタイトルバーの表示を確認しながらクリックして下さい。
    マウスの右クリックで val を最初の値に戻して、元のカラー画像に設定します。
        Face    App;
        int     val= 4000;  // 閾値
    
        private void OnMyMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  //マウスの左ボタン
            {   val+= 1000;
                App.Edge(val);
                VerMsg("val: ", val);
            }
            if (e.Button == MouseButtons.Right) //マウスの右ボタン
            {   App.edge = (Bitmap)App.bmp.Clone();
                val= 4000;
            }
            Invalidate();
        }
    

[Next Chapter ↓] Line をピックアップ
[Previous Chapter ↑] Select Color

超初心者のプログラム入門(C# Frame Work)