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!
朱颜辞镜花辞树,敏捷开发靠得住!

浙公网安备 33010602011771号