c#image filter parallel.for需要更长的时间

时间:2014-10-15 15:37:39

标签: c# image visual-studio image-processing filter

这里的代码是否有任何方法可以使其更快,因为它更慢的单一

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 个答案:

答案 0 :(得分:2)

通过使用lock,您可以使代码的并行部分串行工作,就像具有同步开销的单个线程应用程序一样,实际上它会更慢。

这是一个直接访问位图数据的辅助类,您可以同时操作图像。

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();
        }
    }
}

以下是使用FastImage类的代码:

 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();
            }
        }

答案 1 :(得分:1)

lock中的Parallel.For将导致代码运行速度慢于单线程循环。锁只允许一次一个线程做有用的工作,增加了获取锁的成本。

此外,GetPixel和SetPixel 非常慢。它们也不保证是线程安全的,这可能是你获得InvalidOperationException的原因

  

此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的。不保证任何实例成员都是线程安全的。

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

请改为WriteableBitmap。尽管该类是随WPF引入的,但您可以在各种环境中使用它。我最近在控制台应用程序中使用它。如果需要,可以将WriteableBitmap转换为标准位图,或者将其写入“.bmp”文件。

或者,您可以使用unsafe代码来directly access位图缓冲区。

如果需要,可以使用多个线程进行WriteableBitmap或不安全地访问Bitmap缓冲区,因为您直接读/写内存。

答案 2 :(得分:1)

以下是您可以使用的两个版本的过滤器例程。他们拍摄一张PictureBox的图片,然后在浏览过滤器后将其分配给第二个PictureBox

  • 第一个使用LockBits并且可以在我的机器上在40秒内为一个400x400图像执行10.000(!)循环。
  • 除了Parallel.For之外,第二个使用LockBits并在35秒内执行相同的工作。

当然,这些时间很大程度上受到开销的影响(也许是我所做的愚蠢错误。)但实际上,使用Lockbits是提高速度的关键,因为有很少的计算和计算。并行化管理本身会占用大部分内核。时间..

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;
}

请注意,第二个过滤器例程需要处理第二个Bitmap以用于通用过滤器算法。这个简单的反演滤波器并不是真的需要它,但是一旦你编写了访问相邻像素的东西,比如像你那样的模糊滤镜。

还要注意通道字节的顺序!