我正在尝试使用相同的旧GDI技术使用C#进行一些图像处理,使用嵌套的for循环迭代每个像素,然后在该(位图)图像上使用GetPixel和SetPixel方法。
我已经使用指针方法得到了相同的结果(使用不安全的上下文)但我现在正在尝试用老式的Get / Set-Pixel方法来玩我的位图......
Bitmap ToGrayscale(Bitmap source)
{
for (int y = 0; y < source.Height;y++ )
{
for (int x = 0; x < source.Width; x++)
{
Color current = source.GetPixel(x, y);
int avg = (current.R + current.B + current.G) / 3;
Color output = Color.FromArgb(avg, avg, avg);
source.SetPixel(x, y, output);
}
}
return source;
}
考虑上面代码的性能......在强调用户等待他的1800x1600图像完成处理时,需要花费太多时间才能完成。
所以我认为我可以使用我们使用HLSL的技术,为每个像素运行一个单独的函数(Pixel Shader引擎(因为我被拍))复制函数在GPU上返回float4(Color)数千次并行处理。
所以我尝试为每个像素运行一个单独的Task(函数),将这些Task变量放入List并将List.ToArray()放入'await'。但是我没有做到这一点,因为每个新的任务'等待'在下一个任务运行之前完成。
我想为每个像素调用一个新任务来运行它:
Color current = source.GetPixel(x, y);
int avg = (current.R + current.B + current.G) / 3;
Color output = Color.FromArgb(avg, avg, avg);
source.SetPixel(x, y, output);
在一天结束时,我得到了一个异步非阻塞代码,但不是并行... 有什么建议吗?
答案 0 :(得分:3)
GetPixel
和SetPixel
可能是此处的主要瓶颈。
我建议使用Bitmap.LockBits
来更有效地处理解析,而不是试图并行化。
话虽如此,您可以通过以下方式并行化当前版本:
Bitmap ToGrayscale(Bitmap source)
{
Parallel.For(0, source.Height, y =>
{
for (int x = 0; x < source.Width; x++)
{
Color current = source.GetPixel(x, y);
int avg = (current.R + current.B + current.G) / 3;
Color output = Color.FromArgb(avg, avg, avg);
source.SetPixel(x, y, output);
}
});
return source;
}
但是,Bitmap
类不是线程安全的,因此这可能会导致问题。
更好的方法是使用LockBits
,然后直接并行处理原始数据(如上所述)。请注意,我只是并行化外循环(故意),因为这样可以防止核心过度饱和。
答案 1 :(得分:1)
使用任务只会在CPU上设置多个线程 - 它不会使用图形处理器。另外,我很确定您使用的Bitmap对象不是线程安全的,因此无论如何您都无法使用多个线程来访问它们。
如果您要做的只是将图像转换为灰度,我会首先看一下内置功能。通常,.NET框架中内置的东西可以使用较低级别的“不安全”代码来比其他方式更有效地执行操作,而不会造成不安全。试试How to: Convert an Image to Greyscale。
如果您真的想要使用多个线程进行自定义位图处理,我认为您必须创建一个字节数组,以多线程方式修改它,然后在字节数组的末尾创建一个位图对象。请查看https://stackoverflow.com/a/15290190/1453269,了解如何做到这一点。
答案 2 :(得分:1)
并行化工作的一个好方法不是按像素调度任务,而是调度与处理器内核一样多的线程。
你还说你可以通过指针操纵你的像素,所以如果你走这条路线,这里有另一个重要的建议:让每个线程都能在相邻的像素上工作。
一个有效的场景是线程1使用前25%像素,线程2使用下一个25%,依此类推,直到线程4
以上非常非常重要,以避免False Sharing,您实际上会解除缓存的服务,使您的算法慢很多。
除此之外,你可以使用你的显卡,但这完全不属于我的联盟。