Mark Rect

顔の画像に Land Mark を検出する矩形を重ねます。

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

プログラムの説明

  1. 顔の画像に Land Mark を検出する矩形を重ねて描画する image.cs のソースコードです。
    Face Object Class は、この後「顔画像を識別」まで共通に使用します。
    次のソースコードは Image Guid にまとめて掲載しているので、こちらを参照して下さい。
    BITMAPINFOHEADER 構造体の定義 BITMAPINFO 構造体の定義 Win32 API の関数宣言InitDib() 関数
    //★ Win32 API  Mark Rect    前田 稔
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    
    struct BITMAPINFOHEADER
    {
        ・・・ Image Guid を参照 ・・・
    };
    
    public class MyForm : Form
    {   Face    App;
        int     val= 3000;
    
        public MyForm()
        {
            App = new Face();
            App.OpenFile();
            Width = App.m_bmp.Width;
            Height = App.m_bmp.Height+32;
            App.InitDib();
            App.CopyBmp(App.m_DibDC);
            App.CopyBmp(App.m_MaskDC);
            Paint += new PaintEventHandler(MyHandler);
            MouseDown += new MouseEventHandler(OnMyMouseDown);
        }
    
        private void MyHandler(object sender, PaintEventArgs e)
        {
            Graphics g; 
            g = e.Graphics;
            if (App.m_bmp == null)  Application.Exit();
            App.ViewMask(g,0,0);
            App.DrawRect(g);
        }
    
        private void OnMyMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  //マウスの左ボタン
            {   val+= 1000;
                VerMsg("val: ", val);
                App.Edge(val);
            }
            if (e.Button == MouseButtons.Right) //マウスの右ボタン
            {   val = 5000;
                App.CopyBmp(App.m_MaskDC);
            }
            Invalidate();
        }
    
        public void VerMsg(string msg, int v)
        {
            string wstr;
            wstr = msg + v.ToString();
            this.Text = wstr;
        }
    }
    
    //☆ Face Object Class
    unsafe class Face
    {
        public  Bitmap  m_bmp;      // 入力画像
        public  Bitmap  m_mark;     // 入力マーク画像
        public  IntPtr  m_Dib;      // DIB 画像
        public  IntPtr  m_DibDC;    // DIB DC
        public  byte    *dat;       // DIB ピクセルデータ
        public  IntPtr  m_Mask;     // Mask 画像
        public  IntPtr  m_MaskDC;   // Mask DC
        public  byte    *msk;       // Mak ピクセルデータ
    
        string  ImgFile = "";
        public  const int BI_RGB = 0;
        public  const int DIB_RGB_COLORS = 0;
        public  const int SRCCOPY = 0xcc0020;
    
        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 };
    
        public  Point[]  m_pt = new Point[7];   //Land Mark Table
        Point       pt;                         //Mark Point
    
        int[,]      mkt = new int[5,5]          //Mark 検索パラメータ(BMP 座標)
        { { 123,46,133,95,0 },
        { 72,72,96,124,2 },{ 156,72,184,120,3 },
        { 70,172,112,202,3 },{ 144,172,186,202,2 } };
    
        [DllImport("gdi32.dll")]
    
        ・・・ Image Guid を参照 ・・・
    
        // Constructor
        public Face()
        {
            Reset();
        }
    
        // 入力画像を描画
        public void OpenFile()
        {
            OpenFileDialog opendlg = new OpenFileDialog();
            opendlg.Filter = "画像ファイル (*.bmp)|*.bmp|すべてのファイル (*.*)|*.*";
            if (opendlg.ShowDialog() == DialogResult.OK)
            {   ImgFile = opendlg.FileName;  }
            try
            {   Reset();
                m_bmp = new Bitmap(ImgFile);
                m_mark = new Bitmap("C:\\DATA\\Test\\mark.gif");
            }
            catch
            {   MessageBox.Show("画像ファイルが読めません!", ImgFile);
                return;
            }
        }
    
        // Destructor
        ~Face()
        {   Reset();  }
    
        // 入力画像を描画
        public void Reset()
        {   if (m_DibDC!=null)  DeleteDC(m_DibDC);
            if (m_Dib!=null)    DeleteObject(m_Dib);
            if (m_MaskDC!=null) DeleteDC(m_MaskDC);
            if (m_Mask!=null)   DeleteObject(m_Mask);
            if (m_bmp!=null)    m_bmp.Dispose();
            m_pt[0].X = 0;
        }
    
        // 入力画像を描画
        public void View(Graphics g, int x, int y)
        {   if (m_bmp != null)  g.DrawImage(m_bmp, x, y);  }
    
        // 一個のマーク画像を描画
        public void DrawMark(Graphics g, int x, int y)
        {   if (m_mark != null) g.DrawImage(m_mark, x, y);  }
    
        // マーク画像(m_pt[7])を描画
        public void DrawMark(Graphics g)
        {   if (m_mark == null || m_pt[0].X == 0) return;
            for(int i=0; i<7; i++)  g.DrawImage(m_mark, m_pt[i].X, m_bmp.Height-m_pt[i].Y);
        }
    
        // m_DibDC(DIB) の画像を描画する 
        public void ViewDib(Graphics g, int x, int y)
        {
            if (m_DibDC == null) return;
            IntPtr hDC = g.GetHdc();
            BitBlt(hDC, x, y, m_bmp.Width, m_bmp.Height, m_DibDC, 0, 0, SRCCOPY);
            g.ReleaseHdc(hDC);
        }
    
        // マスク画像を描画する 
        public void ViewMask(Graphics g, int x, int y)
        {
            if (m_MaskDC == null) return;
            IntPtr hDC = g.GetHdc();
            BitBlt(hDC, x, y, m_bmp.Width, m_bmp.Height, m_MaskDC, 0, 0, SRCCOPY);
            g.ReleaseHdc(hDC);
        }
    
        // hDC ← Bitmap m_bmp イメージデータの転送 
        public void CopyBmp(IntPtr hDC)
        {
            IntPtr hBmpSrc = m_bmp.GetHbitmap();
            Graphics gdraw = Graphics.FromImage(m_bmp);
            IntPtr srcHDC = gdraw.GetHdc();
            SelectObject(srcHDC, hBmpSrc);
            BitBlt(hDC, 0, 0, m_bmp.Width, m_bmp.Height, srcHDC, 0, 0, SRCCOPY);
            DeleteObject(hBmpSrc);
            gdraw.ReleaseHdc(srcHDC);
        }
        
        // 検索矩形を表示する(BMP座標⇒ディスプレイ座標)
        public void DrawRect(Graphics g)
        {   int[]   t = new int[5];
            for(int i=0; i<5; i++)
            {   t[0]= mkt[i,0];
                t[1]= (m_bmp.Height-1) - mkt[i,1];
                t[2]= mkt[i,2];
                t[3]= (m_bmp.Height-1) - mkt[i,3];
                t[4]= mkt[i,4];
                Rect(g, t);
            }
        }
    
        // ディスプレイ座標で矩形を描画
        public void Rect(Graphics g, int[] t)
        {
            Pen p = new Pen(Color.Black, 1);
            g.DrawLine(p, t[0], t[1], t[2], t[1]);
            g.DrawLine(p, t[0], t[3], t[2], t[3]);
            g.DrawLine(p, t[0], t[1], t[0], t[3]);
            g.DrawLine(p, t[2], t[1], t[2], t[3]);
            p.Dispose();
        }
    
        //☆ Dib, Mask の初期化
        unsafe public void InitDib()
        {
            ・・・ Image Guid を参照 ・・・
    
            fixed (void* ppvBits = &dat)
            {   m_Dib = CreateDIBSection(hdc, bi, 0, (IntPtr)ppvBits, (IntPtr)null, 0);
                if (m_Dib == null) MessageBox.Show("CreateDIBSection error");
            }
            m_DibDC = CreateCompatibleDC(hdc);
            SelectObject(m_DibDC, m_Dib);
    
            fixed (void* ppvBits2 = &msk)
            {   m_Mask = CreateDIBSection(hdc, bi, 0, (IntPtr)ppvBits2, (IntPtr)null, 0);
                if (m_Mask == null) MessageBox.Show("CreateDIBSection error");
            }
            m_MaskDC = CreateCompatibleDC(hdc);
            SelectObject(m_MaskDC, m_Mask);
            ReleaseDC(IntPtr.Zero, hdc);
        }
    
        // *dat を参照して *msk にエッジ画像を作成
        public void  Edge(int val)
        {
            if (m_DibDC == null)    return;
            for(int y=1; y<m_bmp.Height-1; y++)
                for(int x=1; x<m_bmp.Width-1; x++)
                {
                    if (Sobel(x, y, val))   MASK(x,y,0,0,0);
                    else    MASK(x,y,255,255,255);
            }
        }
    
        // 座標[x,y]周辺のピクセル(*dat)を調べてエッジを検出する
        public bool  Sobel(int x, int y, int val)
        {   int[]   fx = new int[3];    //横3色分
            int[]   fy = new int[3];    //縦3色分
            int     dx, dy;
            byte    *pt;
    
            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];
                pt = dat+(dy*m_bmp.Width+dx)*4;
                for(int c=0; c<3; c++)
                {   fx[c] += *(pt+c) * hCoef[i];
                    fy[c] += *(pt+c) * vCoef[i];
                }
            }
            for(int c=0; c<3; c++)
                if (fx[c]*fx[c]+fy[c]*fy[c] > val)  return true;
            return false;
        }
    
        // *msk(y, x, c) の色を取得
        public byte MASK(int x, int y, int c)
        {
            byte *pt;
            pt = msk+(y*m_bmp.Width+x)*4+c;
            return *pt;
        }
    
        // *msk(y, x) に R,G,B を設定(A はそのまま)
        public void MASK(int x, int y, byte r, byte g, byte b)
        {
            byte *pt;
            pt = msk+(y*m_bmp.Width+x)*4;
            *pt = b;
            *(pt+1)= g;
            *(pt+2)= r;
        }
    
        // Mark 検索パラメータを使って pt_t[7] にマーク座標を格納する
        public void LandMark()
        {
            int[] t = new int[5];
            m_pt[0].X = 128;
            m_pt[0].Y = 128;
            m_pt[1].X = 128;
            m_pt[1].Y = 192;
            for(int n=0; n<5; n++)
            {   for(int i=0; i<5; i++)  t[i] = mkt[n, i];
                LandMK(t);
                m_pt[n+2]= pt;
            }
        }
    
        // m_Mask の画像から t[5] の範囲で Land Mark を検出する
        // t[0],t[1]=左下座標(X,Y),  t[2],t[3]=右上座標(X,Y), t[4]=検索方向
        public void LandMK(int[] t)
        {   int w,h,cx,cy,x,y,x1,x2,y1,y2;
    
            pt.X = pt.Y = 0;
            w= t[2]-t[0];
            h= t[3]-t[1];
            cx= t[0]+w/2;
            cy= t[1]+h/2;
    
            switch(t[4])
            {   case 0:     //上方向
                    for(y=0; y<h; y++)
                        for(x=0; x<w/2; x++)
                        {
                            y1= t[1]+y;
                            x1= cx+x;
                            x2= cx-x;
                            if (MASK(x1,y1,0)==0)
                            {   pt.X= x1;
                                pt.Y= y1;
                                return;
                            }
                            if (MASK(x2,y1,0)==0)
                            {   pt.X= x2;
                                pt.Y= y1;
                                return;
                            }
                        }
                    break;
                case 1:     //下方向
                    for(y=0; y<h; y++)
                        for(x=0; x<w/2; x++)
                        {
                            y1= t[3]-y;
                            x1= cx+x;
                            x2= cx-x;
                            if (MASK(x1,y1,0)==0)
                            {   pt.X= x1;
                                pt.Y= y1;
                                return;
                            }
                            if (MASK(x2,y1,0)==0)
                            {   pt.X= x2;
                                pt.Y= y1;
                                return;
                            }
                        }
                    break;
                case 2:     //右方向
                    for(x=0; x<w; x++)
                        for(y=0; y<h/2; y++)
                        {
                            x1= t[0]+x;
                            y1= cy+y;
                            y2= cy-y;
                            if (MASK(x1,y1,0)==0)
                            {   pt.X= x1;
                                pt.Y= y1;
                                return;
                            }
                            if (MASK(x1,y2,0)==0)
                            {   pt.X= x1;
                                pt.Y= y2;
                                return;
                            }
                        }
                    break;
                case 3:     //左方向
                    for(x=0; x<w; x++)
                        for(y=0; y<h/2; y++)
                        {
                            x1= t[2]-x;
                            y1= cy+y;
                            y2= cy-y;
                            if (MASK(x1,y1,0)==0)
                            {   pt.X= x1;
                                pt.Y= y1;
                                return;
                            }
                            if (MASK(x1,y2,0)==0)
                            {   pt.X= x1;
                                pt.Y= y2;
                                return;
                            }
                        }
                    break;
            }
        }
    }
    
    class image01
    {
        [STAThread]
        public static void Main()
        {
            MyForm mf = new MyForm();
            Application.Run(mf);
        }
    }
    
  2. 白黒2階調のエッジ画像を使って Land Mark を検出するのですが、その準備段階として顔の画像と検索用の矩形を重ねて描画します。
    顔の画像(m_bmp)と Land Mark の画像(m_mark)を入力する領域を定義します。
    エッジを検出して二値画像を作成するので、二枚の DIB 領域(m_Dib, m_Mask)を定義します。
    エッジの検出は エッジ検出 を参照して下さい。
        public  Bitmap  m_bmp;      // 入力する顔の画像
        public  Bitmap  m_mark;     // 入力するマーク画像
        public  IntPtr  m_Dib;      // DIB 画像
        public  IntPtr  m_DibDC;    // DIB DC
        public  byte    *dat;       // DIB ピクセルデータ
        public  IntPtr  m_Mask;     // Mask 画像
        public  IntPtr  m_MaskDC;   // Mask DC
        public  byte    *msk;       // Mak ピクセルデータ
    
  3. Land Mark を検出する矩形領域の定義です。
    「下唇と口の左右と左目と右目の内側」の検索パラメータを設定してみました。
    座標は、左下を起点にした BMP 座標系です。(ディスプレイ系とは上下が反転)
        int[,]      mkt = new int[5,5]      //Mark 検索パラメータ(BMP 座標)
        { { 123,46,133,95,0 },
        { 72,72,96,124,2 },{ 156,72,184,120,3 },
        { 70,172,112,202,3 },{ 144,172,186,202,2 } };
    
    パラメータ 説明
    123,46 矩形領域の左下座標
    133,95 矩形領域の右上座標
    0 検索の方向(0:上, 1:下, 2:右, 3:左)
  4. MyForm では Face Class を生成してから OpenFile(); で顔の画像を選択して入力します。
    入力する画像は Face Image Size で、鼻の頭を中心に 256*256 に整えた画像です。
    m_DibDC と m_MaskDC に App.CopyBmp(); で画像を転送します。
    m_DibDC の画像は濃淡の違いを調べるときに Sobel() 関数から参照します。
    MyHandler から描画する画像は m_MaskDC に作成された画像です。
    public class MyForm : Form
    {   Face    App;
        int     val= 3000;
    
        public MyForm()
        {
            App = new Face();
            App.OpenFile();
            Width = App.m_bmp.Width;
            Height = App.m_bmp.Height+32;
            App.InitDib();
            App.CopyBmp(App.m_DibDC);
            App.CopyBmp(App.m_MaskDC);
            Paint += new PaintEventHandler(MyHandler);
            MouseDown += new MouseEventHandler(OnMyMouseDown);
        }
    
  5. 顔の画像を入力する OpenFile() 関数です。
    新しい顔の画像を入力する前に、今まで使っていた領域を Reset() 関数で開放します。
        // 入力画像を描画
        public void OpenFile()
        {
            OpenFileDialog opendlg = new OpenFileDialog();
            opendlg.Filter = "画像ファイル (*.bmp)|*.bmp|すべてのファイル (*.*)|*.*";
            if (opendlg.ShowDialog() == DialogResult.OK)
            {   ImgFile = opendlg.FileName;  }
            try
            {   Reset();
                m_bmp = new Bitmap(ImgFile);
                m_mark = new Bitmap("C:\\DATA\\Test\\mark.gif");
            }
            catch
            {   MessageBox.Show("画像ファイルが読めません!", ImgFile);
                return;
            }
        }
    
  6. MyHandler() では App.ViewMask(g,0,0); で m_MaskDC の画像を描画します。
    顔の画像に重ねて App.DrawRect(g); で Land Mark を検出する矩形領域を表示します。
        private void MyHandler(object sender, PaintEventArgs e)
        {
            Graphics g; 
            g = e.Graphics;
            if (App.m_bmp == null)  Application.Exit();
            App.ViewMask(g,0,0);
            App.DrawRect(g);
        }
    
  7. マウスの左クリックで val(閾値)をアップしながらエッジを検出して二値画像を作成します。
    二値画像に重ねて Land Mark を検出する矩形領域を表示します。
    矩形領域の範囲を検索方向から調べて最初に黒のピクセルが検出された座標が Land Mark になります。
    エッジ検出の最適な閾値(しきいち)と、マークを検索する矩形領域を設定して下さい。
    val はエッジを検出するための閾値(しきいち)で、値が大きくなるほど黒い部分が少なくなります。
    マウスの右クリックで val を最初の値に戻して、元のカラー画像に設定します。
        int     val= 4000;
    
        private void OnMyMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  //マウスの左ボタン
            {   val+= 1000;
                VerMsg("val: ", val);
                App.Edge(val);
            }
            if (e.Button == MouseButtons.Right) //マウスの右ボタン
            {   val = 4000;
                App.CopyBmp(App.m_MaskDC);
            }
            Invalidate();
        }
    
  8. 検索矩形を表示する DrawRect() 関数です。
    mkt[5,5] は BMP 座標系ですが、矩形の描画はディスプレイ系なので座標を変換して Rect() 関数を呼び出します。
        // 検索矩形を表示する(BMP座標⇒ディスプレイ座標)
        public void DrawRect(Graphics g)
        {   int[]   t = new int[5];
            for(int i=0; i<5; i++)
            {   t[0]= mkt[i,0];
                t[1]= (m_bmp.Height-1) - mkt[i,1];
                t[2]= mkt[i,2];
                t[3]= (m_bmp.Height-1) - mkt[i,3];
                t[4]= mkt[i,4];
                Rect(g, t);
            }
        }
        // ディスプレイ座標で矩形を描画
        public void Rect(Graphics g, int[] t)
        {
            Pen p = new Pen(Color.Black, 1);
            g.DrawLine(p, t[0], t[1], t[2], t[1]);
            g.DrawLine(p, t[0], t[3], t[2], t[3]);
            g.DrawLine(p, t[0], t[1], t[0], t[3]);
            g.DrawLine(p, t[2], t[1], t[2], t[3]);
            p.Dispose();
        }
    
  9. LandMK() 関数で Land Mark を検出するのですが、この後のページで説明します。
    C言語(Windows)のプログラムは Mark Rect を参照して下さい。

[Next Chapter ↓] Land Mark
[Previous Chapter ↑] エッジ検出

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