c# image filter parallel.for takes longer time [cl

2019-07-07 02:23发布

here is the code is there any way to make it faster cause its slowerden single

public Bitmap pSetInvert(Bitmap _currentBitmap)
    {
        Bitmap temp = (Bitmap)_currentBitmap;
        Bitmap bmap = (Bitmap)temp.Clone();
        Color c;
        Parallel.For(0, bmap.Width, i =>
        {
            lock (bmap)
            {
                for (int j = 0; j < bmap.Height; j++)
                {
                    c = bmap.GetPixel(i, j);
                    bmap.SetPixel(i, j, Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B));
                } 
            }
        });
        return (Bitmap)bmap.Clone();
    }

3条回答
该账号已被封号
2楼-- · 2019-07-07 03:14

By using lock you are making parallel part of your code to work serially, exactly like a single thread application with synchronization overhead so it actually would be slower.

here is a helper class that access bitmap data directly and you can manipulate image in concurrently.

FastImage Helper Class:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;

namespace ImageManipulation
{
    class FastImage : IDisposable
    {
        private Bitmap _buffer;
        private byte[] _rawData;
        private GCHandle _rawHandle;
        private int _formatSize;
        private int _width;
        private int _height;

        public int Width
        {
            get { return _width; }
        }

        public int Height
        {
            get { return _height; }
        }

        public byte[] GetRawData()
        {
            return _rawData;
        }

        public byte this[int index]
        {
            get { return _rawData[index]; }
            set { _rawData[index] = value; }
        }

        public Color this[int x, int y]
        {
            get
            {
                return GetPixel(x, y);
            }
            set
            {
                SetPixel(x, y, value);
            }
        }

        public Color GetPixel(int x, int y)
        {
            var offset = y*_width*_formatSize;
            offset += x*_formatSize;
            return Color.FromArgb(_rawData[offset + 3], _rawData[offset + 2], _rawData[offset + 1], _rawData[offset]);
        }

        public void SetPixel(int x, int y, Color value)
        {
            var offset = y*_width*_formatSize;
            offset += x*_formatSize;

            _rawData[offset] = value.B;
            _rawData[offset + 1] = value.G;
            _rawData[offset + 2] = value.R;
            _rawData[offset + 3] = value.A;

        }

        private FastImage() { }

        public static FastImage Create(Image source)
        {
            var image = new FastImage();

            var bmpSource = new Bitmap(source);
            var bmpData = bmpSource.LockBits(new Rectangle(0, 0, source.Width, source.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmpSource.PixelFormat);

            image._width = source.Width;
            image._height = source.Height;

            image._formatSize = 4;
            var stride = bmpSource.Width * image._formatSize;
            image._rawData = new byte[stride * bmpSource.Height];
            image._rawHandle = GCHandle.Alloc(image._rawData, GCHandleType.Pinned);
            var pointer = Marshal.UnsafeAddrOfPinnedArrayElement(image._rawData, 0);
            image._buffer = new Bitmap(bmpSource.Width, bmpSource.Height, stride, PixelFormat.Format32bppArgb /*bmpSource.PixelFormat*/, pointer);
            bmpSource.UnlockBits(bmpData);

            var graphics = Graphics.FromImage(image._buffer);

            graphics.DrawImageUnscaledAndClipped(bmpSource, new Rectangle(0, 0, source.Width, source.Height));
            graphics.Dispose();

            return image;
        }

        public void Dispose()
        {
            _rawHandle.Free();
            _buffer.Dispose();
        }

        public void Save(Stream stream)
        {
            _buffer.Save(stream, ImageFormat.Bmp);
        }

        public Bitmap ToBitmap()
        {
            return (Bitmap)_buffer.Clone();
        }
    }
}

and here is your code using FastImage class:

 public Bitmap pSetInvert(Bitmap _currentBitmap)
        {
            using (var bmap = FastImage.Create(_currentBitmap))
            {
                Parallel.For(0, bmap.Width, i =>
                {
                    for (int j = 0; j < bmap.Height; j++)
                    {
                        var c = bmap.GetPixel(i, j);
                        bmap.SetPixel(i, j, Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B));

                    }
                });
                return bmap.ToBitmap();
            }
        }
查看更多
甜甜的少女心
3楼-- · 2019-07-07 03:14

The lock in the Parallel.For is going to cause the code to run slower than with a single-threaded loop. The lock only allows one thread at a time to do useful work, with the added cost of acquiring the lock.

Additionally, GetPixel and SetPixel are extremely slow. They are also not guaranteed to be thread-safe, which is probably why you are getting the InvalidOperationException

Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

http://msdn.microsoft.com/en-us/library/System.Drawing.Bitmap(v=vs.110).aspx

Have a look instead at WriteableBitmap. Though the class was introduced with WPF, you can use it from a variety of environments. I recently used it in a console application. WriteableBitmap can be converted to a standard bitmap if needed, or written to a ".bmp" file.

Alternatively, you can use unsafe code to directly access the Bitmap buffer.

If you need to, you can use multiple threads for either WriteableBitmap or unsafe access to the Bitmap buffer since you are directly reading/writing memory.

查看更多
forever°为你锁心
4楼-- · 2019-07-07 03:21

Here are two versions of your filter routine you can play with. They take the image of one PictureBox and after running through the filter assign it to a second PictureBox.

  • The first one uses LockBits and can do 10.000 (!) loops for a ca 400x400 image in 40 seconds on my machine.
  • The second uses Parallel.For in addition to the LockBits and does the same job in 35 seconds here.

Of course these timings are strongly influenced by overhead (and maybe by dumb bugs I made.) But really, using Lockbits is key to speed here as there is so little to 'compute' the parallelization management itself eats up most of the cores' time..

using System.Runtime.InteropServices;
// ..


public void filter1()
{
    if (pictureBox2.Image != null) pictureBox2.Image.Dispose();
    Bitmap bmp = new Bitmap(pictureBox1.Image);

    Size s1 = bmp.Size;
    PixelFormat fmt1 = bmp.PixelFormat;

    Rectangle rect = new Rectangle(0, 0, s1.Width, s1.Height);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, fmt1);
    byte bpp = 4;  // <<-------------------set to 3 for 24bbp !!

    int size1 = bmpData.Stride * bmpData.Height;
    byte[] data = new byte[size1];
    Marshal.Copy(bmpData.Scan0, data, 0, size1);

    for (int y = 0; y < s1.Height; y++)
    {
        for (int x = 0; x < s1.Width; x++)
        {
            int index = y * bmpData.Stride + x * bpp;

            data[index + 0] = (byte) (255 - data[index + 0]);  // Blue
            data[index + 1] = (byte) (255 - data[index + 1]);  // Green
            data[index + 2] = (byte) (255 - data[index + 2]);  // Red
            data[index + 3] = 255;   // Alpha, comment out for 24 bpp!
        }
    }

    Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
    bmp.UnlockBits(bmpData);

    pictureBox2.Image = bmp;

}


public void filter2()
{
    if (pictureBox2.Image != null) pictureBox2.Image.Dispose();

    Bitmap bmp1 = new Bitmap(pictureBox1.Image);
    Size s1 = bmp1.Size;
    Bitmap bmp2 = new Bitmap(s1.Width, s1.Height);

    PixelFormat fmt1 = bmp1.PixelFormat;

    Rectangle rect = new Rectangle(0, 0, s1.Width, s1.Height);
    BitmapData bmpData1 = bmp1.LockBits(rect, ImageLockMode.ReadOnly, fmt1);
    BitmapData bmpData2 = bmp2.LockBits(rect, ImageLockMode.WriteOnly, fmt1);
    byte bpp = 4;  // set to 3 for 24bbp !!

    int size1 = bmpData1.Stride * bmpData1.Height;
    byte[] data1 = new byte[size1];
    byte[] data2 = new byte[size1];
    Marshal.Copy(bmpData1.Scan0, data1, 0, size1);
    Marshal.Copy(bmpData2.Scan0, data2, 0, size1);

    int degreeOfParallelism = Environment.ProcessorCount - 1;
    var options = new ParallelOptions();
    options.MaxDegreeOfParallelism = degreeOfParallelism;

     Parallel.For(0, bmp1.Width, options, y =>
     {
        {
           for (int x = 0; x < s1.Width; x++)
           {
                 int index = y * bmpData1.Stride + x * bpp;

                 data2[index + 0] = (byte)(255 - data1[index + 0]);  // Blue
                 data2[index + 1] = (byte)(255 - data1[index + 1]);  // Green
                 data2[index + 2] = (byte)(255 - data1[index + 2]);  // Red
                 data2[index + 3] = 255;   // Alpha, comment out for 24 bpp!
           }
        }
    });
    Marshal.Copy(data2, 0, bmpData2.Scan0, data2.Length);
    bmp1.UnlockBits(bmpData1);
    bmp2.UnlockBits(bmpData2);

    pictureBox2.Image = bmp2;
}

Note that the 2nd filter routine needs to work on a second Bitmap for general purpose filter algorithms. This simple inversion filter doesn't really need it, but once you write things that access neighbouring pixels like eg.g a blur filter you do..

Also note the order of the channel bytes!

查看更多
登录 后发表回答