当前位置:编程学习 > C#/ASP.NET >>

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

图像处理中,有很多算法由于其内在的复杂性是天然的耗时大户,加之图像本身蕴涵的数据量比一般的对象就大,因此,针对这类算法,执行速度的提在很大程度上依赖于硬件的性能,现在流行的CPU都是至少2核的,稍微好点的4核,甚至8核,因此,如果能充分利用这些资源,必将能发挥机器的强大优势,为算法的执行效果提升一个档次。
     在单核时代,多线程程序的主要目的是防止UI假死,而一般情况下此时多线程程序的性能会比单线程的慢,这种情况五六年前是比较普遍的,所有哪个时候用VB6写的图像程序可能比VC6的慢不了多少。可在多核时代,多线程的合理利用可以使得程序速度线性提升。
     在一般的编程工具中,都有提供线程操作的相关类。比如在VS2010中,提供了诸如System.Threading、System.Threading.Tasks等命名空间,方便了大家对多线程程序的编制。但是直接的使用Threading类还是很不方便,为此,在C#的几个后续版本中,加入了Parallel这样的并行计算类,在实际的编码中,配合Partitioner.Create方法,我们会发现这个类特别适合于图像处理中的并行计算,比如下面这个简单的代码就实现反色算法的并行计算:
private void Invert(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* 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++)
                {
                    *CurP = (byte)(255 - *CurP);
                    *(CurP + 1) = (byte)(255 - *(CurP + 1));
                    *(CurP + 2) = (byte)(255 - *(CurP + 2));
                    CurP += 3;
                }
            }
        });
        Bmp.UnlockBits(BmpData);
    }
}
     和经典的反色代码相比,只是增加了
Parallel.ForEach(Partitioner.Create(0, BmpData.Height), (H) =>
     以及将
for (Y = 0; Y < Height; Y++)
  修改为
for (Y = H.Item1; Y < H.Item2; Y++)
    但是在效率上我们做如下对比(笔记本I3cpu): 
图像大小 单线程时间/ms 多线程时间/ms
1024*768 4 2
1600*1200 11 6
4000*3000 78 40
 
 
 
 
 
     再举个Photoshop中去色算法的例子,如果用并行计算则相应代码为:
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上依然能够发挥多线程的速度优势。
     由以上两个简单的例子,我们先总结一下使用Parallel.ForEach结合Partitioner.Create进行并行计算的一些事情。 
     第一:这种并行编程非常之方便,特别是对于图像这种类似于矩阵方式存储的数据,算法基本都是先行后列或先列后行方式进行计算的。
     第二:凡是变量的值会在并行程序改变的变量,都必须定义在Parallel的大括号内,否则会出现莫名的错误。
  第三:在并行代码内部直进行读取而不进行复制的单个变量,可以放到Parallel大括号之外,但也建议放在括号内,因为实际表明,这样速度会快,比如上
补充:软件开发 , C# ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,