好吧,我有一个代码可以在" x"图像中应用雨弓滤波器,我必须通过两种方式进行:顺序&并行,我的顺序代码无问题,但并行部分不起作用。我不知道,为什么?
代码
public static Bitmap RainbowFilterParallel(Bitmap bmp)
{
Bitmap temp = new Bitmap(bmp.Width, bmp.Height);
int raz = bmp.Height / 4;
Parallel.For(0, bmp.Width, i =>
{
Parallel.For(0, bmp.Height, x =>
{
if (i < (raz))
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R / 5, bmp.GetPixel(i, x).G, bmp.GetPixel(i, x).B));
}
else if (i < (raz * 2))
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R, bmp.GetPixel(i, x).G / 5, bmp.GetPixel(i, x).B));
}
else if (i < (raz * 3))
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R, bmp.GetPixel(i, x).G, bmp.GetPixel(i, x).B / 5));
}
else if (i < (raz * 4))
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R / 5, bmp.GetPixel(i, x).G, bmp.GetPixel(i, x).B / 5));
}
else
{
temp.SetPixel(i, x, Color.FromArgb(bmp.GetPixel(i, x).R / 5, bmp.GetPixel(i, x).G / 5, bmp.GetPixel(i, x).B / 5));
}
});
});
return temp;
}
此外,在片刻中,程序返回相同的错误,但说"对象已经在使用"。
ps。我是C#的初学者,我在另一篇文章中搜索了此主题,但我什么也没发现。
非常感谢您
正如评论者罗恩·贝耶(Ron Beyer)指出的那样,使用SetPixel()
和GetPixel()
方法非常慢。对这些方法之一的每种调用都涉及托管代码之间的过渡到Bitmap
对象所代表的实际二进制缓冲区的过渡中的 lot 。那里有很多层,通常涉及视频驱动程序,需要在用户和内核级执行之间进行过渡。
但是,除了慢外,这些方法还使对象"忙",因此,如果尝试使用位图(包括调用其中一种方法),则在调用其中一种方法之间,并且当它返回时(即,在呼叫正在进行时),您看到的例外发生了错误。
,由于将当前代码并行的唯一方法是有用的是,如果这些方法调用可能同时发生,并且由于它们根本无法进行,因此此方法无法正常工作。
另一方面,使用LockBits()
方法不仅可以保证起作用,而且很有可能会发现使用LockBits()
的性能要好得多,甚至不需要并行化算法。但是,您是否应该决定自己这样做,因为LockBits()
的工作方式&mdash;您可以访问代表位图映像&mdash的字节的原始缓冲区;您可以轻松地并行化算法并利用多个CPU内核(如果存在)。
请注意,使用LockBits()
时,您将以可能不习惯的级别使用Bitmap
对象。如果您尚未了解位图在"引擎盖下"的真正工作方式,那么您将不得不熟悉位图实际存储在内存中的方式。这包括了解不同的像素格式的含义,如何解释和修改给定格式的像素以及如何在内存中列出一个位图(例如,行的顺序可能会根据位图而变化,以及"大步"。
这些东西并不难学习,但这需要耐心。不过,如果表现是您的目标,这是值得的。
平行在奇异的头脑上很难。并将其与传统GDI 代码混合会导致奇怪的结果。
您的代码有许多问题:
- 您每位像素三次致电get像素,而不是一次
- 您正在访问像素的水平,不像您应该
- 您叫y x和x i;机器不介意,但我们的人会
- 您正在使用方法太多并行化。没有比您拥有更多的核心的用途。它会产生头顶上的限制,除非您的内部循环要做的工作非常艰难,例如数百万的计算。 。
但是,您得到的例外与这些问题无关。您不犯的一个错误是并行访问同一像素...所以为什么崩溃?
清理代码后,我发现堆栈跟踪中的错误指向SetPixel
,并在System.Drawing.Image.get_Width()
上指向。前者很明显,后者不是我们代码的一部分。!?
所以我挖掘了reforencesource.microsoft.com上的源代码,并找到了以下内容:
/// <include file='docBitmap.uex' path='docs/doc[@for="Bitmap.SetPixel"]/*' />
/// <devdoc>
/// <para>
/// Sets the color of the specified pixel in this <see cref='System.Drawing.Bitmap'/> .
/// </para>
/// </devdoc>
public void SetPixel(int x, int y, Color color) {
if ((PixelFormat & PixelFormat.Indexed) != 0) {
throw new InvalidOperationException(SR.GetString(SR.GdiplusCannotSetPixelFromIndexedPixelFormat));
}
if (x < 0 || x >= Width) {
throw new ArgumentOutOfRangeException("x", SR.GetString(SR.ValidRangeX));
}
if (y < 0 || y >= Height) {
throw new ArgumentOutOfRangeException("y", SR.GetString(SR.ValidRangeY));
}
int status = SafeNativeMethods.Gdip.GdipBitmapSetPixel(new HandleRef(this, nativeImage), x, y, color.ToArgb());
if (status != SafeNativeMethods.Gdip.Ok)
throw SafeNativeMethods.Gdip.StatusException(status);
}
实际工作是由SafeNativeMethods.Gdip.GdipBitmapSetPixel
完成的,但是在此之前,该方法对位图的宽度和高度进行了界限。尽管在我们的情况下,这些系统当然永远不会更改,但系统仍然不允许并行访问它们,因此当检查发生在某个时候时,崩溃了。当然,完全不理想,但是您去了..
so GetPixel
(具有相同行为)和SetPixel
不能安全地用于并行处理。
有两种方式:
我们可以将locks
添加到代码中,因此请确保在"同一"时间进行检查:
public static Bitmap RainbowFilterParallel(Bitmap bmp)
{
Bitmap temp = new Bitmap(bmp);
int raz = bmp.Height / 4;
int height = bmp.Height;
int width = bmp.Width;
// set a limit to parallesim
int maxCore = 7;
int blockH = height / maxCore + 1;
//lock (temp)
Parallel.For(0, maxCore, cor =>
{
//Parallel.For(0, bmp.Height, x =>
for (int yb = 0; yb < blockH; yb++)
{
int i = cor * blockH + yb;
if (i >= height) continue;
for (int x = 0; x < width; x++)
{
{
Color c;
// lock the Bitmap just for the GetPixel:
lock (temp) c = temp.GetPixel(x, i);
byte R = c.R;
byte G = c.G;
byte B = c.B;
if (i < (raz)) { R = (byte)(c.R / 5); }
else if (i < raz + raz) { G = (byte)(c.G / 5); }
else if (i < raz * 3) { B = (byte)(c.B / 5); }
else if (i < raz * 4) { R = (byte)(c.R / 5); B = (byte)(c.B / 5); }
else { G = (byte)(c.G / 5); R = (byte)(c.R / 5); }
// lock the Bitmap just for the SetPixel:
lock (temp) temp.SetPixel(x, i, Color.FromArgb(R,G,B));
};
}
};
});
return temp;
}
请注意,限制等分非常重要,在平行类别类中甚至还有一个成员,并且Parallel.For
中的参数可以控制它!我将最大核心数设置为7,但这会更好:
int degreeOfParallelism = Environment.ProcessorCount - 1;
所以这应该为我们节省一些开销。但仍然:我希望它会比更正的顺序方法慢!
彼得和罗恩建议的方法使事情变得非常快(1Ox),而添加并行性甚至可能更快。
因此,最终要完成这个长度的答案,这里有一个锁定和有限并行解决方案:
public static Bitmap RainbowFilterParallelLockbits(Bitmap bmp)
{
Bitmap temp = null;
temp = new Bitmap(bmp);
int raz = bmp.Height / 4;
int height = bmp.Height;
int width = bmp.Width;
Rectangle rect = new Rectangle(Point.Empty, bmp.Size);
BitmapData bmpData = temp.LockBits(rect,ImageLockMode.ReadOnly, temp.PixelFormat);
int bpp = (temp.PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3;
int size = bmpData.Stride * bmpData.Height;
byte[] data = new byte[size];
System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, data, 0, size);
var options = new ParallelOptions();
int maxCore = Environment.ProcessorCount - 1;
options.MaxDegreeOfParallelism = maxCore > 0 ? maxCore : 1;
Parallel.For(0, height, options, y =>
{
for (int x = 0; x < width; x++)
{
{
int index = y * bmpData.Stride + x * bpp;
if (y < (raz)) data[index + 2] = (byte) (data[index + 2] / 5);
else if (y < (raz * 2)) data[index + 1] = (byte)(data[index + 1] / 5);
else if (y < (raz * 3)) data[index ] = (byte)(data[index ] / 5);
else if (y < (raz * 4))
{ data[index + 2] = (byte)(data[index + 2] / 5);
data[index] = (byte)(data[index] / 5); }
else
{ data[index + 2] = (byte)(data[index + 2] / 5);
data[index + 1] = (byte)(data[index + 1] / 5);
data[index] = (byte)(data[index] / 5); }
};
};
});
System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
temp.UnlockBits(bmpData);
return temp;
}
虽然不是严格相关的,但我想发布的版本比答案中给出的任何一个更快。这是我通过位图迭代并将结果保存在C#中的最快方法。在我的工作中,我们需要浏览数百万张大图像,这只是我抓住红色频道并为自己的目的保存它,但它应该给您如何工作的想法
//Parallel Unsafe, Corrected Channel, Corrected Standard div 5x faster
private void TakeApart_Much_Faster(Bitmap processedBitmap)
{
_RedMin = byte.MaxValue;
_RedMax = byte.MinValue;
_arr = new byte[BMP.Width, BMP.Height];
long Sum = 0,
SumSq = 0;
BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
//this is a much more useful datastructure than the array but it's slower to fill.
points = new ConcurrentDictionary<Point, byte>();
unsafe
{
int bytesPerPixel = Image.GetPixelFormatSize(bitmapData.PixelFormat) / 8;
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
_RedMin = byte.MaxValue;
_RedMax = byte.MinValue;
byte* PtrFirstPixel = (byte*)bitmapData.Scan0;
Parallel.For(0, heightInPixels, y =>
{
//pointer to the first pixel so we don't lose track of where we are
byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
//0+2 is red channel
byte redPixel = currentLine[x + 2];
Interlocked.Add(ref Sum, redPixel);
Interlocked.Add(ref SumSq, redPixel * redPixel);
//divide by three since we are skipping ahead 3 at a time.
_arr[x/3, y] = redPixel;
_RedMin = redPixel < _RedMin ? _RedMin : redPixel;
_RedMax = redPixel > RedMax ? RedMax : redPixel;
}
});
_RedMean = Sum / TotalPixels;
_RedStDev = Math.Sqrt((SumSq / TotalPixels) - (_RedMean * _RedMean));
processedBitmap.UnlockBits(bitmapData);
}
}