close

 

昨天在建置EmguCV的時候也有邊想,

自己玩這個可以玩什麼,

想來想去,還是決定以現在主流技術來走,

那就是自動辨識。

 

以目前網路上的資源量來說,入門並不難,

看了幾篇後發現,其實很多都是抄來抄去的,

但是學習一門技術,我最害怕的就是知其然而不知其所以然,

所以,咱家還是認為,我先從最基本的影像處理開始學起,

打好基礎,在往上發展,才有辦法寫出不一樣的東西。

 

So,這一篇我不用opencv也不用emgucv,就是很單純的用C#來處理影像,

順道一提,用C#處理影像真的比用C/C++/MFC簡單多了 .... 

 

 

今天來介紹三種處理影像的方式,

1. 提取像素法

2. 內存法

3. 指針法

在速度上 ( 3 > 2 > 1 )

寫法容易度 ( 1 > 2 > 3 )

 

接下來,我們寫點Code吧 

1. 像素提取法 : 

            if (curBitmap != null)
            {
                myTimer.ClearTimer();
                myTimer.Start();
                Color curColor;
                int ret;
                for (int i = 0 ; i < curBitmap.Width ; i++ )
                {
                    for (int j = 0 ; j < curBitmap.Height ; j++ )
                    {
                        curColor = curBitmap.GetPixel(i, j);
                        ret = (int)(curColor.R * 0.299 + curColor.G * 0.587 + curColor.B * 0.114);
                        curBitmap.SetPixel(i, j, Color.FromArgb(ret, ret, ret));
                    }
                }
                myTimer.Stop();
                tb_RunTime.Text = myTimer.Duration.ToString("####.##") + " 毫秒";
                Invalidate();
            }
 

2. 內存法 :
  
            if (curBitmap != null)
            {
                myTimer.ClearTimer();
                myTimer.Start();
                // Bitmap rectangle
                Rectangle rect = new Rectangle(0, 0, curBitmap.Width, curBitmap.Height);
                // 以可以讀寫的方式鎖定全部位圖的像素
                System.Drawing.Imaging.BitmapData bmpData = curBitmap.LockBits(rect,
                    System.Drawing.Imaging.ImageLockMode.ReadWrite, curBitmap.PixelFormat);

                // 得到首地址
                IntPtr ptr = bmpData.Scan0;

                // 被定義所定的數組大小,由位圖數據與未用空間組成
                int bytes = bmpData.Stride * bmpData.Height;
                byte[] rgbValues = new byte[bytes];
                // 複製被鎖定的位圖像素值到該數組內
                System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

                // 灰度化
                double colorTemp = 0;
                for (int i =0 ; i< bmpData.Height; i++)
                {
                    // 只處理每行中是圖像的數據,捨棄未用空間
                    for (int j = 0 ;j < bmpData.Width*3 ;j+=3)
                    {
                        colorTemp = rgbValues[i * bmpData.Stride + j + 2] * 0.299 +
                            rgbValues[i * bmpData.Stride + j + 1] * 0.587 +
                            rgbValues[i * bmpData.Stride + j] * 0.114;
                        rgbValues[i * bmpData.Stride + j] = rgbValues[i * bmpData.Stride + j + 1] =
                            rgbValues[i * bmpData.Stride + j + 2] = (byte)colorTemp;
                    }
                }
                myTimer.Stop();
                tb_RunTime.Text = myTimer.Duration.ToString("####.##") + " 毫秒"; 
                // 把數組複製回去給位圖
                System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
                // 解除位圖像素
                curBitmap.UnlockBits(bmpData);
                // 對窗體進行重新繪製,這將強制執行Paint事件處理程序
                Invalidate();
            }

3. 指針法 :
  
            if (curBitmap != null )
            {
                myTimer.ClearTimer();
                myTimer.Start();
                Rectangle rect = new Rectangle(0, 0, curBitmap.Width, curBitmap.Height);
                System.Drawing.Imaging.BitmapData bmpData = curBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, curBitmap.PixelFormat);
                byte temp = 0;
                // 啟動不安全模式
                unsafe
                {
                    byte* ptr = (byte*)(bmpData.Scan0);
                    for (int i = 0 ; i < bmpData.Height ; i++ )
                    {
                        for (int j = 0; j < bmpData.Width ; j++ )
                        {
                            temp = (byte)(0.299 * ptr[2] + 0.587 * ptr[1] + 0.114 * ptr[0]);
                            ptr[0] = ptr[1] = ptr[2] = temp;
                            ptr += 3;
                        }
                        ptr += bmpData.Stride - bmpData.Width * 3;
                    }
                }
                curBitmap.UnlockBits(bmpData);
                myTimer.Stop();
                tb_RunTime.Text = myTimer.Duration.ToString("####.##") + " 毫秒";
                Invalidate();
            }




 

打開檔案 和 儲存檔案的方式 



        private void btn_Open_Click(object sender, EventArgs e)
        {
            OpenFileDialog opnDlg = new OpenFileDialog();
            opnDlg.Filter = "所有圖片文件 | *.bmp; *.pcx; *.png; *.jpg; *.gif;" +
                "*.tif; *.ico; *.dxf; *.cgm; *.cdr; *.wmf; *.eps; *.emf|" +
                "位圖( *.bmp; *.jpg; *.png;...) | *.bmp; *.pcx; *.png; *.jpg; *.gif; *.tif; *.ico|" +
                "矢量圖( *.wmf; *.eps; *.emf;...) | *.dxf; *.cgm; *.cdr; *.wmf; *.eps; *.emf";
            opnDlg.Title = "Open file";
            opnDlg.ShowHelp = true;
            if (opnDlg.ShowDialog() == DialogResult.OK)
            {
                curFileName = opnDlg.FileName;
                try
                {
                    curBitmap = (Bitmap)Image.FromFile(curFileName);
                }
                catch(Exception exp)
                {
                    MessageBox.Show(exp.Message);
                }
            }
        }

        private void btn_Save_Click(object sender, EventArgs e)
        {
            if (curBitmap == null) return;

            SaveFileDialog saveDlg = new SaveFileDialog();
            saveDlg.Title = "保存為";
            // OverwritePrompt: 控制在將要在改寫現在檔時是否提示用戶,只有在ValidateNames為真值時,才適用 
            saveDlg.OverwritePrompt = true;
            saveDlg.Filter =
                "BMP文件 (*.bmp) | *.bmp|" +
                "GIF文件 (*.gif) | *.gif|" +
                "JPEG文件 (*.jpg) | *.jpg|" +
                "PNG文件 (*.png) | *.png";
            saveDlg.ShowHelp = true;
            if (saveDlg.ShowDialog() == DialogResult.OK)
            {
                string fileName = saveDlg.FileName;
                string strFilExtn = fileName.Remove(0, fileName.Length - 3);
                switch (strFilExtn)
                {
                    case "bmp":
                        curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Bmp);
                        break;
                    case "jpg":
                        curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);
                        break;
                    case "gif":
                        curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Gif);
                        break;
                    case "tif":
                        curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Tiff);
                        break;
                    case "png":
                        curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
                        break;
                    default:
                        break;
                }
            }
        }

 

Timer class



  using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Threading;



namespace StudyImg_Gray
{
    internal class HiPerfTimer
    {
        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out long lpFrequency);

        private long startTime, stopTime;
        private long freq;

        // Constructor
        public HiPerfTimer()
        {
            startTime = 0;
            stopTime = 0;

            // QueryPerformanceFrequency :精確獲得時間 
            if (QueryPerformanceFrequency (out freq) == false)
            {
                // high-performance counter not supported.
                throw new Win32Exception();
            }
        }
        // Start the timer
        public void Start()
        {
            // lets do the waiting threads there work.
            Thread.Sleep(0);
            // QueryPerformanceCounter: 要求電腦從硬體上支持高精度定時器。
            QueryPerformanceCounter(out startTime);
        }

        // Stop the timer
        public void Stop()
        {
            QueryPerformanceCounter(out stopTime);
        }

        // Returns the duration of the timer ( in millisecond ).
        public double Duration
        {
            get
            {
                return (double)(stopTime - startTime) * 1000 / (double)freq;
            }
        }

        // Clear Timer
        public void ClearTimer()
        {
            startTime = 0;
            stopTime = 0;
        }
    }
}


 

arrow
arrow
    全站熱搜

    Eric 發表在 痞客邦 留言(0) 人氣()