本人最近在寫一套計算機視覺處理軟件,用的就是C# + Emgu,因為用到的OpenCV方法就那么幾個(大概10多個),為了這些為數不多的方法而帶着數MB的Emgu DLL,心里很是不爽,於是乎萌生了將這些方法全部用C# unsafe代碼重寫的想法,反正OpenCV是開源的,算法可以寫成一樣的,效率上應該差不到哪去。
下面是我自己寫的一個圖像類:
///<summary>灰度圖像處理類,作者:wmesci</summary>
unsafe class Image :CriticalHandle, IDisposable { [DllImport("kernel32.dll")] static extern IntPtr LocalAlloc(int flags, int size); [DllImport("kernel32.dll")] static extern IntPtr LocalFree(IntPtr memBlock); [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] static extern unsafe void CopyMemory(byte* dst, byte* src, int count); [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] static extern unsafe void CopyMemory(byte* dst, IntPtr src, int count); [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] static extern unsafe void CopyMemory(byte* dst, byte[] src, int count); const byte Max = 255; const byte Min = 0; public Image(int width, int height) : base(IntPtr.Zero) { if (width <= 0 || height <= 0) throw new ArgumentOutOfRangeException(); Width = width; Height = height; Length = Width * Height; base.SetHandle(LocalAlloc(0x40, width * height)); Pointer = (byte*)handle.ToPointer(); } public Image(int width, int height, byte[] dat) : this(width, height) { if (dat != null) { CopyMemory(Pointer, dat, Length); } } public Image(int width, int height, byte* dat) : this(width, height) { CopyMemory(Pointer, dat, Length); } public Image(int width, int height, IntPtr dat) : this(width, height) { CopyMemory(Pointer, dat, Length); } public readonly int Width; public readonly int Height; public readonly int Length; public readonly byte* Pointer; public byte this[int x, int y] { get { return *(Pointer + y * Width + x); } set { *(Pointer + y * Width + x) = value; } } public Image Clone() { return new Image(Width, Height, Pointer); } public void Add(Image img) { Action<int> act = y => { byte* p1 = Pointer + y * Width, p2 = (byte*)img.Pointer + y * img.Width; for (int x = 0; x < Width; x++, p1++, p2++) { double d = *p1 + *p2; if (d < 0) *p1 = 0; else if (d > 255) *p1 = 255; else *p1 = (byte)d; } }; Parallel.For(0, Height, act); } public void Sub(Image img) { Action<int> act = y => { byte* p1 = Pointer + y * Width, p2 = (byte*)img.Pointer + y * img.Width; for (int x = 0; x < Width; x++, p1++, p2++) { double d = *p1 - *p2; if (d < 0) *p1 = 0; else if (d > 255) *p1 = 255; else *p1 = (byte)d; } }; Parallel.For(0, Height, act); } public void Mul(Image img, double scale) { Action<int> act = y => { byte* p1 = Pointer + y * Width, p2 = (byte*)img.Pointer + y * img.Width; for (int x = 0; x < Width; x++, p1++, p2++) { double d = scale * *p1 * *p2; if (d < 0) *p1 = 0; else if (d > 255) *p1 = 255; else *p1 = (byte)d; } }; Parallel.For(0, Height, act); } public void Threshold(byte threshold) { Action<int> act = y => { byte* p = Pointer + y * Width; for (int x = 0; x < Width; x++, p++) { *p = *p > threshold ? Max : Min; } }; Parallel.For(0, Height, act); } public void AddWeighted(Image img, double a, double b) { Action<int> act = y => { byte* p1 = this.Pointer + y * this.Width, p2 = (byte*)img.Pointer + y * img.Width; for (int x = 0; x < this.Width; x++, p1++, p2++) { double d = a * *p1 + b * *p2; if (d < 0) *p1 = 0; else if (d > 255) *p1 = 255; else *p1 = (byte)d; } }; Parallel.For(0, this.Height, act); } public static void Smooth(Image src, Image dst, int n) { int* tmp = (int*)Marshal.AllocHGlobal(src.Width * src.Height * 4).ToPointer(); Action<int> act = y => { byte* p = src.Pointer + y * src.Width; int d = 0; for (int i = -n; i <= n; i++) { int xx = GetIndex(i, src.Width); d += p[xx]; } tmp[y * src.Width] = d; }; Parallel.For(0, src.Height, act); act = y => { int i = y * src.Width; byte* p = src.Pointer + y * src.Width; for (int x = 1; x < src.Width; x++) { int d = tmp[i]; int x1 = GetIndex(x - n - 1, src.Width); int x2 = GetIndex(x + n, src.Width); d += (p[x2] - p[x1]); tmp[++i] = d; } }; Parallel.For(0, src.Height, act); double f = 1.0 / (2 * n + 1); f *= f; act = x => { int d = 0; byte* p = dst.Pointer + x; for (int j = -n; j <= n; j++) { int yy = GetIndex(j, src.Height); d += tmp[x + yy * src.Width]; } *p = (byte)(d * f); p += src.Width; for (int y = 1; y < src.Height; y++, p += src.Width) { int y1 = GetIndex(y - n - 1, src.Height); int y2 = GetIndex(y + n, src.Height); d += (tmp[x + y2 * src.Width] - tmp[x + y1 * src.Width]); *p = (byte)(d * f); } }; Parallel.For(0, src.Width, act); Marshal.FreeHGlobal(new IntPtr(tmp)); } private static int GetIndex(int i, int max) { if (i < 0) return 0; if (i >= max) return max - 1; return i; } public override bool IsInvalid { get { return handle == IntPtr.Zero; } } protected override bool ReleaseHandle() { LocalFree(handle); return true; } }
(如有可以優化的地方,煩請指正)
用WPF寫了個簡單的測試程序,其中運行時間使用Stopwatch計算,取其ElapsedTicks值。
先看下運行環境:
OpenCV使用2.2版本。測試圖像大小為600*896,預先進行了灰度化,然后再計算處理時間。
下面直接上結果:
1、Add:
Image:imgt.Add(img)
OpenCV:CvInvoke.cvAdd(img, img, img, IntPtr.Zero)
各執行50次,取平均數:
Image花費時間3246,OpenCV花費時間1514
2、Sub:
Image:imgt.Sub(img)
OpenCV:CvInvoke.cvSub(img, img, img, IntPtr.Zero)
各執行50次,取平均數:
Image花費時間3378,OpenCV花費時間1370
3、Mul:
Image:imgt.Mul(img, 1)
OpenCV:CvInvoke.cvMul(img, img, img, 1)
各執行50次,取平均數:

Image花費時間3817,OpenCV花費時間7480
4、Threshold:
Image:imgt.Threshold(120)
OpenCV:CvInvoke.cvcvThreshold(img, img, 120, 255, Emgu.CV.CvEnum.THRESH.CV_THRESH_BINARY)
各執行50次,取平均數:

Image花費時間1645,OpenCV花費時間1361
5、Smooth:
Image:Image.Smooth(img, dst, 3)
OpenCV:CvInvoke.cvSmooth(img, dst, Emgu.CV.CvEnum.SMOOTH_TYPE.CV_BLUR, 7, 0, 0, 0)
各執行50次,取平均數:
6、AddWeighted:
Image:dst.AddWeighted(img, 0.4, 0.4)
OpenCV:CvInvoke.cvAddWeighted(img, 0.4, img, 0.4, 0, dst)
各執行50次,取平均數:

Image花費時間3952,OpenCV花費時間9845
總結一下:
從上表可以看出,Image類和OpenCV基本上是勝率對半。至於為什么,且聽下回分解~~~
更新在另一台電腦上運行的結果:
Image慘敗!!
源碼及測試代碼下載地址:http://download.csdn.net/detail/wmesci/3841089
相關討論帖:http://topic.csdn.net/u/20111124/23/1F236D07-420E-4E2E-83EE-C9C29E689477.html