c#图像过滤器并行运行需要更长的时间
这是代码,有没有办法让它更快,因为它的单曲速度较慢c#图像过滤器并行运行需要更长的时间,c#,image,visual-studio,image-processing,filter,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 => {
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();
}
公共位图pSetInvert(位图\u当前位图)
{
位图温度=(位图)\u当前位图;
位图bmap=(位图)临时克隆();
颜色c;
对于(0,bmap.Width,i=>
{
锁(bmap)
{
对于(int j=0;j并行中的锁
。For
将导致代码运行速度比单线程循环慢。锁一次只允许一个线程执行有用的工作,这会增加获取锁的成本
此外,GetPixel和SetPixel的速度非常慢。它们也不能保证线程安全,这可能就是为什么会出现InvalidOperationException
此类型的任何公共静态(在Visual Basic中共享)成员都是线程安全的。任何实例成员都不能保证线程安全
看一看。虽然该类是随WPF引入的,但您可以在各种环境中使用它。我最近在一个控制台应用程序中使用了它。如果需要,WriteableBitmap可以转换为标准位图,也可以写入“.bmp”文件
或者,您也可以使用代码来创建位图缓冲区
如果需要,您可以使用多个线程来执行WriteableBitmap或对位图缓冲区的不安全访问,因为您正在直接读取/写入内存。这里有两个版本的筛选例程可供使用。它们获取一个PictureBox
的图像,并在运行过滤器后将其分配给第二个PictureBox
- 第一个使用了
锁位
,在我的机器上可以在40秒内对一个ca 400x400映像执行10.000(!)循环
- 第二个使用
Parallel.For
,除了LockBits
之外,它在35秒内完成相同的工作
当然,这些时间安排受到开销的强烈影响(可能还受到我制造的愚蠢错误的影响),但实际上,使用锁位是加速的关键,因为“计算”的时间太少了,并行化管理本身占用了大部分核心的时间
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;
}
使用System.Runtime.InteropServices;
// ..
公共无效筛选器1()
{
如果(pictureBox2.Image!=null)pictureBox2.Image.Dispose();
位图bmp=新位图(pictureBox1.Image);
大小s1=bmp.Size;
PixelFormat fmt1=bmp.PixelFormat;
矩形rect=新矩形(0,0,s1.宽度,s1.高度);
BitmapData bmpData=bmp.LockBits(rect,ImageLockMode.ReadWrite,fmt1);
字节bpp=4;//通过使用lock
可以使代码的并行部分串行工作,这与具有同步开销的单线程应用程序完全相同,因此实际上会更慢
下面是一个帮助器类,它可以直接访问位图数据,并且您可以同时操作图像
FastImage助手类:
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
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();
}
}
公共位图pSetInvert(位图\u当前位图)
{
使用(var bmap=FastImage.Create(_currentBitmap))
{
对于(0,bmap.Width,i=>
{
对于(int j=0;j
阅读您的代码。您希望并行循环中的锁能做什么?基本上,您的并行程序是无用的如果我不写锁,它会给我系统无效操作异常错误,您会立即在循环体中锁定。这破坏了使用TPL的全部目的,因为所有其他线程都必须等待正在工作的单个线程。@hassan,为了消除锁定,您可以将位图分成多个数组,并分别并行处理这些数组,这样线程之间就不会相互干扰。我只需要准备好的过滤器使其并行,我不做大的东西,请帮助我that@hassan:GetPixel()和SetPixel()根本不是线程安全的。您无法使用这些调用并行处理数据。您可以使用不安全的方法直接访问位图缓冲区(请参阅我答案中的链接)并使用多个线程来处理缓冲区。但是,不安全的线程将比GetPixel/SetPixel快得多,因此您可能不需要额外的线程来处理小图像。它们应该只调用它们GetPixelReallySlow()
和SetPixelEvenSlower()
,也许这样就不会有这么多这样的问题了。@hassan怎么做?你得到了一个很好的答案,只要按照其中的建议去做。@hassan:你不能使用并行。对于GetPixel和SetPixel,它根本不起作用。如果你必须使用并行,你必须使用我概述的其他方法之一来更新位图。对于。我不知道我不知道我可以用多少种不同的方式来表达,所以我只想用这句话来结束我的评论。