C#:优化图像像素操作
以图像阈值化为例:
# very slow solution public static unsafe Bitmap ApplyThreshold(Bitmap scrBitmap, int lower_value, int upper_value) { Color newColor = Color.Red; Bitmap newBitmap = new Bitmap(scrBitmap.Width, scrBitmap.Height); //Locking the bitmap's bits allows you to iterate through it's color-data many times faster than using GetPixel, using unsafe code. lock (_imageLock) { var data = scrBitmap.LockBits(new Rectangle(0, 0, scrBitmap.Width, scrBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); var offset = data.Stride - scrBitmap.Width * 3; var p = (byte*)data.Scan0.ToPointer(); for (var i = 0; i < scrBitmap.Height; i++) { for (var j = 0; j < scrBitmap.Width; j++, p += 3) { var v = (int)(p[0] + p[1] + p[2]) / 3; var c = Color.FromArgb(p[2], p[1], p[0]); if (v > upper_value || v < lower_value) newBitmap.SetPixel(j, i, newColor); else newBitmap.SetPixel(j, i, c); } p += offset; } scrBitmap.UnlockBits(data); } return newBitmap; } # speed up using mutiple threads public static unsafe Bitmap ApplyThresholdParallel2(Bitmap scrBitmap, int lower_value, int upper_value) { //Locking the bitmap's bits allows you to iterate through it's color-data many times faster than using GetPixel, using unsafe code. lock (_imageLock) { var data = scrBitmap.LockBits(new Rectangle(0, 0, scrBitmap.Width, scrBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int bytesPerPixel = 3; int stride = data.Stride; var p = (byte*)data.Scan0.ToPointer(); Task[] tasks = new Task[4]; for (int i = 0; i < tasks.Length; i++) { int ii = i; tasks[i] = Task.Factory.StartNew(() => { int minY = ii < 2 ? 0 : data.Height / 2; int maxY = ii < 2 ? data.Height / 2 : data.Height; int minX = ii % 2 == 0 ? 0 : data.Width / 2; int maxX = ii % 2 == 0 ? data.Width / 2 : data.Width; for (int y = minY; y < maxY; y++) { byte* row = p + (y * data.Stride); for (int x = minX; x < maxX; x++) { int bIndex = x * bytesPerPixel; int gIndex = bIndex + 1; int rIndex = bIndex + 2; byte pixelR = row[rIndex]; byte pixelG = row[gIndex]; byte pixelB = row[bIndex]; int v = (pixelR + pixelG + pixelB) / 3; if (v > upper_value || v < lower_value) { row[rIndex] = 255; row[gIndex] = 0; row[bIndex] = 0; } } } }); } Task.WaitAll(tasks); scrBitmap.UnlockBits(data); } return scrBitmap; } # speed up using Parallel for public static unsafe Bitmap ApplyThresholdParallel(Bitmap scrBitmap, int lower_value, int upper_value) { var rect = new Rectangle(0, 0, scrBitmap.Width, scrBitmap.Height); Bitmap targetBmp = scrBitmap.Clone(rect, PixelFormat.Format24bppRgb); //Locking the bitmap's bits allows you to iterate through it's color-data many times faster than using GetPixel, using unsafe code. lock (_imageLock) { var data = targetBmp.LockBits(rect, ImageLockMode.ReadWrite, targetBmp.PixelFormat); int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(targetBmp.PixelFormat) / 8; int heightInPixels = data.Height; int widthInBytes = data.Width * bytesPerPixel; byte* PtrFirstPixel = (byte*)data.Scan0; Parallel.For(0, heightInPixels, y => { byte* currentLine = PtrFirstPixel + (y * data.Stride); for (int x = 0; x < widthInBytes; x = x + bytesPerPixel) { int b = currentLine[x]; int g = currentLine[x + 1]; int r = currentLine[x + 2]; var v = (b + g + r) / 3; if (v > upper_value || v < lower_value) { currentLine[x] = (byte)0; currentLine[x + 1] = (byte)0; currentLine[x + 2] = (byte)255; } } }); targetBmp.UnlockBits(data); } return targetBmp; }
参考:
Fast Pixel Operations in .NET (With and Without unsafe)
Why the use of GetPixel and SetPixel is so inefficient!
朱颜辞镜花辞树,敏捷开发靠得住!