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!

Fast Image Processing in C#

原文地址:https://www.cnblogs.com/carsonzhu/p/12460872.html