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;
}
}
}
全站熱搜