代码之家  ›  专栏  ›  技术社区  ›  gatapia

快速扩散强度算法

  •  2
  • gatapia  · 技术社区  · 16 年前

    首先,为这个标题道歉,我不知道它是否描述了我正在努力实现的目标,但它是我所拥有的最好的。

    基本上我有一个描述二维空间强度的数组。然后,我想在给定的迭代集中将这种强度分布给邻居,也就是说,我有以下数组:

    intensity = [ 0, 0, 0, 0, 0, 
                  0, 0, 0, 0, 0, 
                  0, 0, 0, 0, 0, 
                  0, 0, 100, 0, 0, 
                  0, 0, 0, 0, 0, 
                  0, 0, 0, 0, 0,
                  0, 0, 0, 0, 0 ]
    

    然后我做了一次分发强度算法(将强度的50%分发给邻居)。然后我会:

                [ 0,  0,   0,  0, 0, 
                  0,  0,   0,  0, 0, 
                  0, 50,  50, 50, 0, 
                  0, 50, 100, 50, 0, 
                  0, 50,  50, 50, 0, 
                  0,  0,   0,  0, 0,
                  0,  0,   0,  0, 0 ]
    

    如果我对原始数组进行2次传递,则得到的数组将是:

              [ 0,   0,   0,   0, 0, 
               25,  50,  75,  50, 25, 
               50, 150, 200, 150, 50, 
              75, 200, 300, 200, 75, 
               50, 150, 200, 150, 50, 
               25,  50,  75,  50, 25,
                0,   0,   0,   0, 0 ]
    

    我当前的代码是:

    this.distributeIntensities = function(passes, shareRatio) {     
        for (var i = 0; i < passes; i++) { this.distributeIntensity(shareRatio); }
    }
    
    this.distributeIntensity = function(shareRatio) {       
        var tmp = hm.intensity.slice(0); // copy array
        for (var i = 0; i < tmp.length; i++) {          
            if (hm.intensity[i] <= 0) { continue; }
            var current = hm.intensity[i];
            var shareAmount = current * shareRatio;                     
            this.shareIntensityWithNeighbours(tmp, shareAmount, i);                                                             
        }       
        hm.intensity = tmp;
    }
    
    this.shareIntensityWithNeighbours = function(arr, heat, i) {                    
        // This should be var x = Math.floor(...) however 
        // this is slower and without gives satisfactory results
        var x = i % hm.columnCount; 
        var y = i / hm.columnCount; 
    
        if (x > 0) {
            if (y > 0) arr[i - hm.columnCount - 1] += heat;
            arr[i - 1] += heat;
            if (y < (hm.rowCount - 1)) arr[i + hm.columnCount - 1] += heat;
        }               
    
        if (y > 0) arr[i - hm.columnCount] += heat;     
        if (y < (hm.rowCount - 1)) arr[i + hm.columnCount] += heat;
    
        if (x < (hm.columnCount - 1)) {
            if (y > 0) arr[i - hm.columnCount + 1] += heat;
            arr[i + 1] += heat;
            if (y < (hm.rowCount - 1)) arr[i + hm.columnCount + 1] += heat;
        }               
    }
    

    现在,这项工作,但是它非常慢(我正在处理一个巨大的数组和8个通道)。我知道有一种更快、更好、更干净的方法,但这超出了我的能力范围,所以我把它放在那里,希望有人能给我指明正确的方向(注意:我不会讲流利的数学,事实上我在数学上是文盲)。

    提前谢谢

    圭多

    4 回复  |  直到 16 年前
        1
  •  6
  •   ephemient    16 年前

    Convo lution 是一种常见的图像处理技术(现在您有一个关键字要搜索!).

    [[ 0.5, 0.5, 0.5 ],
     [ 0.5, 1.0, 0.5 ],
     [ 0.5, 0.5, 0.5 ]]
    

    看起来您已经用这个内核手工实现了卷积。

    为了加快速度:因为卷积是关联的,所以可以预先计算一个过滤器,而不是多次应用原始过滤器。例如,如果passes=2,

    once = [[ 0.5, 0.5, 0.5 ], [ 0.5, 1.0, 0.5 ], [ 0.5, 0.5, 0.5 ]]
    twice = once ⊗ once =
        [[ 0.25, 0.50, 0.75, 0.50, 0.25 ],
         [ 0.50, 1.50, 2.00, 1.50, 0.50 ],
         [ 0.75, 2.00, 3.00, 2.00, 0.75 ], 
         [ 0.50, 1.50, 2.00, 1.50, 0.50 ], 
         [ 0.25, 0.50, 0.75, 0.50, 0.25 ]]
    
    distribute(hm) = hm ⊗ once ⊗ once
                   = hm ⊗ twice
    

    如果你要反复这样做,可能值得学习 Fourier Transform 有一个定理说明

    FT(X ⊗ Y) = FT(X) ⋅ FT(Y)
    

    或者在应用逆傅立叶变换之后,

    X ⊗ Y = IFT(FT(X) ⋅ FT(Y))
    

    换句话说,复杂的卷积可以用简单的乘法来代替。

        2
  •  1
  •   Christian C. Salvadó    16 年前

    嗯,在你的 for 环上 distributeIntensity 我想你可以做些小改动:

    • 存储数组 length .
    • 颠倒 if 语句以避免 continue 语句。


    this.distributeIntensity = function(shareRatio) {       
        var tmp = hm.intensity.slice(0); // copy array
        for (var i = 0, n = tmp.length; i < n; i++) {                      
            if (hm.intensity[i] > 0) {
              var current = hm.intensity[i];
              var shareAmount = current * shareRatio;
              this.shareIntensityWithNeighbours(tmp, shareAmount, i);
            }
        }
        hm.intensity = tmp;
    };
    

    如果迭代顺序对您的算法不重要,您可以反向迭代您的数组,即 known 更快:

    this.distributeIntensity = function(shareRatio) {       
        var tmp = hm.intensity.slice(0); // copy array
        var i = tmp.length;
        while (i--) {
            if (hm.intensity[i] > 0) {
              var current = hm.intensity[i];
              var shareAmount = current * shareRatio;
              this.shareIntensityWithNeighbours(tmp, shareAmount, i);
            }
        }
        hm.intensity = tmp;
    };
    

    您可能还需要考虑集成 shareIntensityWithNeighbours 函数在循环中,函数调用可能有些昂贵。

    但是,我强烈建议您使用分析器(内置于 Firebug 非常好),以真正衡量性能并快速发现瓶颈。

        3
  •  0
  •   Willi Ballenthin    16 年前

    需要注意的一点是,在相对稀疏的情况下(例如第一个设置,其中只有一个强度为>0的条目),您只需要关注其中的一些条目。

    根据您的需要,可以跟踪实际需要更新的条目,并忽略额外的计算。

    编辑

    我去挖掘一个这种方法的例子。 对康威生命/细胞自动机游戏的模拟在优化方面也存在类似的困难。在每个阶段,每个单元的状态都必须根据周围的单元进行计算。

    一个非常强大的实现是 Hashlife . 基本上,它使用一个聪明的散列来跟踪哪些单元格实际需要更新,以及记忆(缓存模式)计算。你可以从它的策略中找到灵感。

        4
  •  0
  •   gatapia    16 年前

    由于上面的ephemient的响应,我能够得到修复该算法所需的信息。最后我得到了一个速度快50-55%的算法。还要感谢CMS对javascript性能的攻击(它们实际上有很大的区别)。

    为了完整性,我将代码包括在内:

    注意:代码是经过优化的,因此它不是neatest代码(展开后继续、数组的后向迭代等)许多本地var缓存等。

        this.distributeHeatMapIntensities = function(passes, shareRatio) {              
            var tmp = hm.intensity.slice(0);
    
            var distances = {};
            for (var i = -passes; i <= passes; i++) { distances[i] = Math.abs(i); }
            var shares = [];
            for (var i = 0; i <= passes; i++) { shares.push(i === 0 ? 0 : Math.pow(shareRatio, i)); }
            var len = tmp.length - 1;
    
            for(var i = len; i >= 0; i--) {     
                var intens = hm.intensity[i];
                if (intens > 0) {                       
                    var x = Math.floor(i % hm.columnCount);
                    var y = Math.floor(i / hm.columnCount);                     
    
                    for (var tx = -passes; tx <= passes; tx++) { 
                        var nx = x + tx;
                        if (nx >= 0 && nx < hm.columnCount) {               
                            var dx = distances[tx];
                            for (var ty = -passes; ty <= passes; ty++) { 
                                var ny = y + ty;
                                if (ny >= 0 && ny < hm.rowCount) {                      
                                    var dy = distances[ty];
                                    var distance = dx >= dy ? dx : dy;
                                    var share = shares[distance] * intens;
                                    var i2 = (ny * hm.columnCount) + nx;
                                    tmp[i2] += share;
                                }
                            }
                        }
                    }
                }
            }   
            hm.intensity = tmp; 
        }
    

    谢谢大家

    圭多

    推荐文章