【节前分享】用最简单的方式在C#中使用多线程加速耗时的图像处理算法的执行(多核机器)。

laviewpbt 2013-09-28 12:24:15
加精
图像处理中,有很多算法由于其内在的复杂性是天然的耗时大户,加之图像本身蕴涵的数据量比一般的对象就大,因此,针对这类算法,执行速度的提在很大程度上依赖于硬件的性能,现在流行的CPU都是至少2核的,稍微好点的4核,甚至8核,因此,如果能充分利用这些资源,必将能发挥机器的强大优势,为算法的执行效果提升一个档次。

在单核时代,多线程程序的主要目的是防止UI假死,而一般情况下此时多线程程序的性能会比单线程的慢,这种情况五六年前是比较普遍的,所有哪个时候用VB6写的图像程序可能比VC6的慢不了多少。可在多核时代,多线程的合理利用可以使得程序速度线性提升。

在一般的编程工具中,都有提供线程操作的相关类。比如在VS2010中,提供了诸如System.Threading、System.Threading.Tasks等命名空间,方便了大家对多线程程序的编制。但是直接的使用Threading类还是很不方便,为此,在C#的几个后续版本中,加入了Parallel这样的并行计算类,在实际的编码中,配合Partitioner.Create方法,我们会发现这个类特别适合于图像处理中的并行计算,比如下面这个简单的代码就实现去色算法的并行计算:


private void Desaturate(Bitmap Bmp)
{
if (Bmp.PixelFormat == PixelFormat.Format24bppRgb)
{
BitmapData BmpData = Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadOnly, Bmp.PixelFormat);
Parallel.ForEach(Partitioner.Create(0, BmpData.Height), (H) =>
{
int X, Y, Width, Height, Stride;
byte Red, Green, Blue, Max, Min, Value;
byte* Scan0, CurP;
Width = BmpData.Width; Height = BmpData.Height; Stride = BmpData.Stride; Scan0 = (byte*)BmpData.Scan0;
for (Y = H.Item1; Y < H.Item2; Y++)
{
CurP = Scan0 + Y * Stride;
for (X = 0; X < Width; X++)
{
Blue = *CurP; Green = *(CurP + 1); Red = *(CurP + 2);
if (Blue > Green)
{
Max = Blue;
Min = Green;
}
else
{
Max = Green;
Min = Blue;
}
if (Red > Max)
Max = Red;
else if (Red < Min)
Min = Red;
Value = (byte)((Max + Min) >> 1);
*CurP = Value; *(CurP + 1) = Value; *(CurP + 2) = Value;
CurP += 3;
}
}
});
Bmp.UnlockBits(BmpData);
}

  去色的原理就是取彩色图像RGB通道最大值和最小值的平均值作为新的三通道的颜色值。
做个速度比较:

图像大小 单线程时间/ms 多线程时间/ms
1024*768 5 2
1600*1200 15 8
4000*3000 117 60

去色是轻量级的数字图像算法,但是再多核CPU上依然能够发挥多线程的速度优势。

我们在看看复杂点的算法的例子,这里我们举一个缩放模糊的例子。


public static void ZoomBlur(Bitmap Bmp, int SampleRadius = 100, int Amount = 100, int CenterX = 256, int CenterY = 256)
{
int Width, Height, Stride;
BitmapData BmpData = Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
Width = BmpData.Width; Height = BmpData.Height; Stride = BmpData.Stride;

byte* BitmapClone = (byte*)Marshal.AllocHGlobal(BmpData.Stride * BmpData.Height);
CopyMemory(BitmapClone, BmpData.Scan0, BmpData.Stride * BmpData.Height);

Parallel.ForEach(Partitioner.Create(0, Height, Height / Environment.ProcessorCount), (H) =>
{
int SumRed, SumGreen, SumBlue,Fx, Fy, Fcx, Fcy;
int X, Y, I;
byte* Pointer, PointerC;
uint* Row, RowP;
Fcx = CenterX << 16 + 32768;
Fcy = CenterY << 16 + 32768;

Row = (uint*)Marshal.AllocHGlobal(SampleRadius * 4);
for (Y = H.Item1; Y < H.Item2; Y++)
{
Pointer = (byte*)BmpData.Scan0 + Stride * Y;
Fy = (Y << 16) - Fcy;
RowP = Row;
for (I = 0; I < SampleRadius; I++)
{
Fy -= ((Fy >> 4) * Amount) >> 10;
*RowP = (uint)(BitmapClone + Stride * ((Fy + Fcy) >> 16));
RowP++;
}
for (X = 0; X < Width; X++)
{
Fx = (X << 16) - Fcx;
SumRed = 0; SumGreen = 0; SumBlue = 0;
RowP = Row;
for (I = 0; I < SampleRadius; I++)
{
Fx -= ((Fx >> 4) * Amount) >> 10;
PointerC = (byte*)*RowP + ((Fx + Fcx) >> 16) * 3; // *3不需要优化,编译器会变为lea eax,[eax+eax*2]
SumBlue += *(PointerC);
SumGreen += *(PointerC + 1);
SumRed += *(PointerC + 2);
RowP++;
}
*(Pointer) = (byte)(SumBlue / SampleRadius);
*(Pointer + 1) = (byte)(SumGreen / SampleRadius);
*(Pointer + 2) = (byte)(SumRed / SampleRadius);
Pointer += 3;
}
}
Marshal.FreeHGlobal((IntPtr)Row);
});
Marshal.FreeHGlobal((IntPtr)BitmapClone); // 释放掉备份数据
Bmp.UnlockBits(BmpData);
}

其中的CopyMemory函数声明如下:

[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = true)]
internal static extern void CopyMemory(byte* Dest, byte* src, int Length);

我们看看速度提升:

图像大小 单线程时间(ms) 多线程时间(ms) PS用时(s)
1024*768 926 556 0.7
1600*1200 2986 1214 1.5
4000*3000 21249 6047 7.2


从上图中可以看到,图像越大,单线程和多线程之间的时间比例就越大,也越能发挥多线程的优势。

源码及更多分析详见:http://www.cnblogs.com/Imageshop/p/3344103.html


*********作者: laviewpbt 时间: 2013.9.28 联系QQ: 33184777 转载请保留本行信息*********





...全文
5390 104 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
104 条回复
切换为时间正序
请发表友善的回复…
发表回复
new_fengzi 2013-10-14
  • 打赏
  • 举报
回复
不错,值得学习下
yqb_last 2013-10-12
  • 打赏
  • 举报
回复
谢谢信息共享啊
mnxm 2013-10-11
  • 打赏
  • 举报
回复
yqb_last 2013-10-11
  • 打赏
  • 举报
回复
挺好的标记下
Guhw008 2013-10-11
  • 打赏
  • 举报
回复
GPU 是硬件吗?嘿嘿!
butter0000 2013-10-10
  • 打赏
  • 举报
回复
支持一下!!!
终期于尽 2013-10-09
  • 打赏
  • 举报
回复
谢过各位大神啦
beyondcj 2013-10-09
  • 打赏
  • 举报
回复
fengxing 2013-10-09
  • 打赏
  • 举报
回复
不错 感谢分享!Thanks!
daxiaoyuyu 2013-10-08
  • 打赏
  • 举报
回复
感谢分享!正在学习图像处理的知识。
Frog1228 2013-10-08
  • 打赏
  • 举报
回复
引用 32 楼 imtns 的回复:
引用 9 楼 laviewpbt 的回复:
论坛里,sp1234以及caozhy 等都是很有能力的,不过说实在的也是很"嚣张的" ,适度保持克制吧,吧内因为两位 而起火的帖子确实不少的,幸好我是天天16个小时在CSDN,知道两位的脾气,才不会上火。 sp1234我对多线程的理解还很原始,我那段话的意思很多也是我的臆想,我原本的意思就是: 在单核的情况下,对于图像的算法本身,用多线程去实现,仅仅从速度上考虑,应该不会有提高。那么这样同一样一个算法,由于VB6的程序一般都是单线程的,速度上也就不会比VC差多少,即使VC是使用多线程编码的。 但是这种情况在多核的机器下就不同了。
哎呀,太感同身受了,这个sp1234,光看他那个头像就够让人恶心了,而且论坛里到处都是他这个头像,最让人受不了的是他每次留言都是一副说教人的样子,自己技术可能是牛逼点吧,也用不着到处说别人吧,反正这个人超级恶心的,头像代表一切~~
引用 65 楼 hudsonhuang 的回复:
引用 46 楼 imtns59521 的回复:
[quote=引用 42 楼 youaway 的回复:] 看见SP1234在这里自说自话就烦。
哈哈,顶一个~~~他老是一副说教的样子,很烦
最搞笑经常都没有看清楚人家说什么就开喷啊,明显楼主的意思他都理解错了 而且他自己也经常说错话啊。。。。[/quote] +1,我就碰到过。
Frog1228 2013-10-08
  • 打赏
  • 举报
回复
引用 41 楼 wodegege10 的回复:
LZ使用的并行方法,不是多线程 正如有人所说,并行可以说是GPU的天下,若是想把图像处理效率有数量级的提高, GPU则是很好的武器 NVIDIA的的CUDA可以看下, 以前做医疗影像的时候,算法部的同事,将灌注的同一算法移植到GPU上,效率提高了5倍,而且还是在没有怎么优化的情况下
除了CUDA,还有一个叫什么来着,一下子想不起来了?
guan_tu 2013-10-08
  • 打赏
  • 举报
回复
q107770540 2013-10-08
  • 打赏
  • 举报
回复
longruliang 2013-10-08
  • 打赏
  • 举报
回复
标记,受教了,谢谢楼主分享。
leayh 2013-10-08
  • 打赏
  • 举报
回复
关注一下,CUDA确实是不错,但必须用C去实现,开发效率就低了。
fengxing 2013-10-06
  • 打赏
  • 举报
回复
感谢分享,学习了。
Bahdisd 2013-10-05
  • 打赏
  • 举报
回复
我们实验室都是用VS2010在GPU上跑,速度确实很快。。。
gofei27 2013-10-05
  • 打赏
  • 举报
回复
不错,支持,很好的软件
热情的菜鸟 2013-10-05
  • 打赏
  • 举报
回复
加载更多回复(66)

111,092

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧