• 图像处理的多线程计算


    图像处理的算法复杂度通常都比较高,计算也相应比较耗时。利用CPU多线程处理能力可以大幅度加快计算速度。但是,为了保证多线程处理的结果和单线程处理的结果完全相同,图像的多线程计算有一些需要特别考虑的地方。

    基本思路:为了能让多个线程同时并行处理,那么各自处理的数据不能有交集,这很好理解。那么基本思路是将一副图像分成多个子块,每个子块数据肯定是没有交集的,每个线程对一个子块数据进行处理,完成后将所有子块处理结果合成最终图像。

    首先,每个子块的大小当然是必须考虑的问题。通常当应用进行一个较长时间的操作,应该用合适的方式告知用户。既然我们把图像分子块处理,如果单个子块处理时间很短,那么每当有一个子块的数据处理完成,我们就可以立即把它相应的处理结果展示给用户。用户就会看到这个图像各个部分的处理结果不断展示出来,直至整个图像完成。这样某种程度上用这种方式就是在告知用户正在处理进行中,避免为了把整个图像处理完成,用户需要等待太长时间。从这个角度来说,如果子块尺寸取的太大,每个子块计算时间肯定相应地加长,对于快速显示部分处理结果给用户是不利的。但是如果子块太小,子块总数就会增加,肯定会增加线程开销和其他一些开销(分割图像,分配子块数据等等),对于总的计算时间是不利的。这是一个权衡问题,可以根据具体情况确定。

    另外,很多图像处理都要考虑像素领域范围的信息,因此对于每个子块的处理不能仅仅使用这个子块的内容。具体地,对于靠近子块边缘的像素,还要把子块外的部分像素信息考虑进来,加入计算,才能保证相应像素的处理结果是正确的。准确来说,如果领域半径为r(对方形或圆形领域来说,其他领域可做相应调整),那么子块处理所需要的所有数据是子块四周向外扩展r像素的范围。

    CRect rect1, rect2;
    rect1.CopyRect(pRect[i]);
    rect1.InflateRect(extend, extend);
    rect2.CopyRect(pRect[i]);
    rect2.MoveToXY(extend, extend);
    if (rect1.top < 0)
    {
        rect2.OffsetRect(0, rect1.top);
        rect1.top = 0;
    }
    if (rect1.left < 0)
    {
        rect2.OffsetRect(rect1.left, 0);
        rect1.left = 0;
    }
    if (rect1.bottom > Height) rect1.bottom = Height;
    if (rect1.right > Width) rect1.right = Width;

    代码中extend就是子块要向四周扩张的大小,其实就是领域半径r。pRect[i]是分割的第i个子块的大小。Height和Width是原图的高宽,扩展子块自然不能超过原图的尺寸。那么最后rect1就是计算所需要的数据在原图中所在的领域范围,应用原图的尺寸对它进行了限制。由于我是把每个子块当成一个新的图像进行处理,rect2就是新图像中子块处理结果所在的位置,用它来合成最终图像。

    最后关于线程具体创建销毁,资源分配与回收,线程同步和通信,不做具体讨论。只讨论一下在这里多线程如何协调工作的问题。由于计算子块的线程只负责处理子块,还需要有人来做分割子块,分配数据给子块计算线程等等工作。本来应该画流程图的,实在懒得画了,这里简单描述一下几个线程如何协调工作的,其实也很简单。界面线程A,处理和用户之间的交互,接受用户命令,发送计算消息给线程B。计算协调线程B接受A的消息,分割子块,分配子块数据,创建子块计算线程Ci。子块计算线程Ci负责子块计算,发送处理结果(成功或失败)消息给线程B或者A。界面线程A收到子块完成消息,可以立即显示子块处理结果,当然也可以什么都不做,等到所有子块处理完再显示。协调线程B收到第i个子块完成消息,回收分配给线程Ci的资源,销毁Ci。如果所有的Ci完成了工作,B发送图像处理完成的消息给A,A可以接着做后续的工作。这里单独用了一个线程B来做子块计算协调的工作,感觉这样比较清晰一些。当然也可以让界面线程A来做这个工作,协调的工作量也不是很大,这样就可以不需要B线程。

    单线程和多线程处理时间对比

    多线程处理速度肯定不能简单地是单线程处理速度的N倍,这只是理想状况。由于很多额外工作(线程开销,准备每个线程的数据,处理结果的合成,线程间同步,图像子块结合部的部分重复计算),多线程是不可能达到理想状况的。下表列出了一副2400x1350大小的24bit图像分成了12个子块,分别在一台I5 4300U(双核四线程)笔记本上和一台I5 6500(四核四线程)台式机上,处理高斯模糊的大概的平均耗时。高斯模糊算法是简单的行列方向两次一维计算,半径取50。在我的测试中,还实时显示了分块的处理结果,速度上可能要更慢一点。

      亮度通道 RGB通道
      单线程 多线程 提速 单线程 多线程 提速
    I5 4300U(双核四线程) 1100毫秒 550毫秒 50% 3060毫秒 1250毫秒 59%
    I5 6500(四核四线程) 670毫秒 240毫秒 64% 1850毫秒 560毫秒 69.7%

    理想状况下四线程耗时最多能减少75%,实际上肯定达不到。在双核四线程平台上,对亮度通道处理,多线程比单线程耗时减少了一半(50%)。对RGB通道,多线程比单线程耗时减少了大概59%。在四核四线程平台上,多线程耗时在亮度通道和RGB通道处理时,分别减少了64%和69%。可以看到,多线程处理的加速效果还是相当明显的。话说牙膏厂超线程的效果还是挺惊人的,不然在双核CPU上耗时减少是不可能超过50%的。当然物理内核的数量就更重要了。

    而且还可以看到一个现象,在单线程处理下,RGB三通道的处理耗时是亮度通道处理耗时3倍略少,约为2.8倍(亮度通道还包括一些在RGB和亮度之间转换的额外计算量)。而在多线程下,RGB三通道的处理时间大大小于亮度一个通道处理时间的3倍,约为2.38倍。相对于单线程,节省的时间更多了。这是因为RGB的处理也是在子块的一个线程中处理的,并没有增加新的线程开销。因此线程开销也是必须考虑的一个因素,不可忽略。

     

  • 相关阅读:
    1000F.One Ocurrence(可持久化线段树+思维)
    P2184.贪婪大陆(思维+树状数组)
    438D.The Child and Sequence(线段树+取模性质)
    P2894 [USACO08FEB]Hotel G(线段树维护区间子串)
    620E New Year Tree(线段树维护状压)
    P6492 [COCI2010-2011#6] STEP(线段树维护最长子串)
    242E.XOR on segment(线段树维护区间异或)
    1527D.MEX Tree(树上分类讨论+容斥)
    解决for循环中写异步函数,异步函数中输出下标一样问题
    vue拦截器
  • 原文地址:https://www.cnblogs.com/mightycode/p/6294035.html
Copyright © 2020-2023  润新知