Win32 Color ⇒ 二値画像

GetPixel(), SetPixel() でカラー画像を二値画像に変換すると実行時間が気になります。
そこで Win32 API を使ってもっと早く変換するプログラムを作成します。

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

プログラムの説明

  1. Win32 API を使ってカラー画像を二値画像に変換する image.cs のソースコードです。
    次のソースコードは Image Guid にまとめて掲載しているので、こちらを参照して下さい。
    BITMAPINFOHEADER 構造体の定義 BITMAPINFO 構造体の定義 Win32 API の関数宣言InitDib() 関数
    //★ Win32 API で Binary Mode で描画    前田 稔
    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= 30;
    
        public MyForm()
        {
            BackColor = SystemColors.AppWorkspace;
            App = new Face();
            Width = App.m_bmp.Width;
            Height = App.m_bmp.Height+32;
            App.InitDib();
            App.CopyBmp(App.m_DibDC);
            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.ViewDib(g,0,0);
        }
    
        private void OnMyMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  //マウスの左ボタン
            {   val+= 2;
                VerMsg("val: ", val);
                App.BinMode(val);
            }
            if (e.Button == MouseButtons.Right) //マウスの右ボタン
            {   val = 30;
                App.CopyBmp(App.m_DibDC);
            }
            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  IntPtr  m_Dib;      // DIB 画像
        public  IntPtr  m_DibDC;    // DIB DC
        public  byte    *dat;       // DIB ピクセルデータ
        string  ImgFile = "";
        public  const int BI_RGB = 0;
        public  const int DIB_RGB_COLORS = 0;
        public  const int SRCCOPY = 0xcc0020;
    
        [DllImport("gdi32.dll")]
    
        ・・・ Image Guid を参照 ・・・
    
        // Constructor
        public Face()
        {
            OpenFileDialog opendlg = new OpenFileDialog();
            opendlg.Filter = "画像ファイル (*.bmp)|*.bmp|すべてのファイル (*.*)|*.*";
            if (opendlg.ShowDialog() == DialogResult.OK)
            {   ImgFile = opendlg.FileName; }
            try
            {   m_bmp = new Bitmap(ImgFile); }
            catch
            {   MessageBox.Show("画像ファイルが読めません!", ImgFile);
                return;
            }
        }
    
        // Destructor
        ~Face()
        {   DeleteDC(m_DibDC);
            DeleteObject(m_Dib);
        }
    
        // 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);
        }
    
        // 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);
        }
    
        //☆ DIB の初期化
        unsafe public void InitDib()
        {
            ・・・ Image Guid を参照 ・・・
        }
    
        // *dat を Binary Mode に変換
        public void BinMode(int val)
        {   byte    R, G, B;
            int     gry;
    
            if (m_bmp == null)  return;
            CopyBmp(m_DibDC);
            for (int y = 0; y < m_bmp.Height; y++)
                for (int x = 0; x < m_bmp.Width; x++)
                {
                    B = MAP(x, y, 0);
                    G = MAP(x, y, 1);
                    R = MAP(x, y, 2);
                    gry = (B * 2 + G * 4 + R) / 7;
                    if (gry < val)  MAP(x,y,0,0,0);
                    else    MAP(x,y,255,255,255);
                }
        }
    
        // *dat(y, x, c) の色を取得
        public byte MAP(int x, int y, int c)
        {
            byte *pt;
            pt = dat+(y*m_bmp.Width+x)*4+c;
            return *pt;
        }
    
        // *dat(y, x) に R,G,B を設定(A はそのまま)
        public void MAP(int x, int y, byte r, byte g, byte b)
        {
            byte *pt;
            pt = dat+(y*m_bmp.Width+x)*4;
            *pt = b;
            *(pt+1)= g;
            *(pt+2)= r;
        }
    }
    
    class image01
    {
        [STAThread]
        public static void Main()
        {
            MyForm mf = new MyForm();
            Application.Run(mf);
        }
    }
    
  2. カラー画像は RGB をそれぞれ8ビットで記録されています。
    青色(B)の8ビット 緑色(G)の8ビット 赤色(R)の8ビット Alpha(A)の8ビット
    これを白黒2階調の二値画像に変換します。
    二値画像はグレースケールやエッジ画像と共に画像を解析するときに役に立ちます。
    Color ⇒ 二値画像 では、GetPixel(), SetPixel() で作成したのですが、実行速度が気になります。
    そこで Win32 API を使ってプログラムを作成したので [Color ⇒ 二値画像] と実行速度を比べてみて下さい。
  3. GetPixel() 関数と SetPixel() 関数を使うと簡単なのですが、Win32 API を使った場合は少し面倒です。
    Win32 API の使い方の基本を Image Guid で説明しています。
  4. MyForm では Face Class の Constructor から BMP File を選択して画像を入力します。
    入力した画像に合わせてウインドウサイズを設定するのですが、BitBlt で描画するので「ピクセル/インチ」は考慮しません。
    App.InitDib(); 関数で DIB を初期化します。
    MyHandler では m_DibDC の画像を描画するので App.CopyBmp(); で画像を転送します。
        public MyForm()
        {
            BackColor = SystemColors.AppWorkspace;
            App = new Face();
            Width = App.m_bmp.Width;
            Height = App.m_bmp.Height+32;
            App.InitDib();
            App.CopyBmp(App.m_DibDC);
            Paint += new PaintEventHandler(MyHandler);
            MouseDown += new MouseEventHandler(OnMyMouseDown);
        }
    
  5. MyHandler() では App.ViewDib(g,0,0); で DIB の画像を描画します。
        private void MyHandler(object sender, PaintEventArgs e)
        {
            Graphics g; 
            g = e.Graphics;
            if (App.m_bmp == null)  Application.Exit();
            App.ViewDib(g,0,0);
        }
    
  6. マウスの左クリックで val をアップしながら二値画像に変換します。
    VerMsg() で現在の var(閾値)の値をタイトルバーに印字しています。
    マウスの右クリックで val を最初の値に戻して、元のカラー画像に設定します。
        int     val= 30;
    
        private void OnMyMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  //マウスの左ボタン
            {   val+= 2;
                VerMsg("val: ", val);
                App.BinMode(val);
            }
            if (e.Button == MouseButtons.Right) //マウスの右ボタン
            {   val = 30;
                App.CopyBmp(App.m_DibDC);
            }
            Invalidate();
        }
    
  7. タイトルバーにメッセージを印字する VerMsg() 関数です。
        public void VerMsg(string msg, int v)
        {
            string wstr;
            wstr = msg + v.ToString();
            this.Text = wstr;
        }
    
  8. 画像を描画する Face Object Class では、ポインターを使うので unsafe を設定して下さい。
    m_bmp; が選択した BMP 画像を入力する領域です。
    m_Dib; が DIB の画像の領域で m_DibDC; がそのハンドルです。
    byte *dat; が DIB のピクセルデータへのポインターです。
    //☆ Face Object Class
    unsafe class Face
    {
        public  Bitmap  m_bmp;
        public  IntPtr  m_Dib;
        public  IntPtr  m_DibDC;
        public  byte    *dat;
    
  9. ViewDib() 関数が DIB の画像を描画する関数です。
        // 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);
        }
    
  10. BinMode() 関数でカラー画像を二値画像に変換します。
    ピクセル毎に演算して二値画像に変換するのですが、色に対する明るさを考慮して計算しています。
    val は二値画像の閾値(しきいち)で値が大きくなるほど、黒のピクセルが増えます。
    byte *dat がピクセルデータのポインタで、ポインターを通じてピクセルデータを修正すると直ちに画像に反映します。
        // *dat を Binary Mode に変換
        public void BinMode(int val)
        {   byte    R, G, B;
            int     gry;
    
            if (m_bmp == null)  return;
            CopyBmp(m_DibDC);
            for (int y = 0; y < m_bmp.Height; y++)
                for (int x = 0; x < m_bmp.Width; x++)
                {
                    B = MAP(x, y, 0);
                    G = MAP(x, y, 1);
                    R = MAP(x, y, 2);
                    gry = (B * 2 + G * 4 + R) / 7;
                    if (gry < val)  MAP(x,y,0,0,0);
                    else    MAP(x,y,255,255,255);
                }
        }
    
  11. *dat から x, y, c の byte を取得します。
        // *dat(y, x, c) の色を取得
        public byte MAP(int x, int y, int c)
        {
            byte *pt;
            pt = dat+(y*m_bmp.Width+x)*4+c;
            return *pt;
        }
    
  12. 渡された r, g, b を *dat の x, y に格納します。
    普通 Alpha には 255(0xff) が格納されているのですが、この値はそのまま残します。
        // *dat(y, x) に R,G,B を設定
        public void MAP(int x, int y, byte r, byte g, byte b)
        {
            byte *pt;
            pt = dat+(y*m_bmp.Width+x)*4;
            *pt = b;
            *(pt+1)= g;
            *(pt+2)= r;
        }
    

[Next Chapter ↓] 画像のエッジを検出
[Previous Chapter ↑] W32 Color ⇒ Gray

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