【问题标题】:Algorithm to draw outline in a colored region在彩色区域中绘制轮廓的算法
【发布时间】:2020-10-15 22:17:59
【问题描述】:

假设我有这样的图像:

每个方块都是一个像素。它们是白色或红色的。

在给定轮廓宽度 w 的情况下,我想在红色区域周围绘制绿色轮廓。

我尝试了一些算法,但结果看起来不太好,对角线看起来很奇怪,不反映原始图像:

我应该使用什么方法来获得更流畅、更好的结果和良好的性能?

为简单起见,假设我有一个点 p 属于边界。

【问题讨论】:

    标签: algorithm image math matrix


    【解决方案1】:

    这是一个使用 JavaScript 的解决方案:

    var matrix=[
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ];
    
    function createMatrixDivs() {
      for(var r=0; r<16; r++) {
        for(var c=0; c<16; c++) {
          var cell=document.createElement("div");
          cell.style="border:1px solid blue;position:absolute;width:10px;height:10px;left:"+10*c+"px;top:"+10*r+"px;";
          cell.id=r+","+c;
          document.body.append(cell);
        }
      }
    }
    
    function drawMatrixDivs() {
      for(var r=0; r<16; r++) {
        for(var c=0; c<16; c++) {
          document.getElementById(r+","+c).style.backgroundColor=(matrix[r][c]==0?"white":matrix[r][c]==1?"red":matrix[r][c]==2?"green":"gray");
        }
      }
    }
    
    function outline(w) {
      for(var r1=0; r1<16; r1++) {
        for(var c1=0; c1<16; c1++) {
          if(matrix[r1][c1]==0) {
            for(var r2=0; r2<16; r2++) {
              for(var c2=0; c2<16; c2++) {
                if(r2!=r1 && c2!=c1 && matrix[r2][c2]==1 && Math.round(Math.sqrt(Math.pow(r2-r1,2)+Math.pow(c2-c1,2)))<=w) {
                  matrix[r1][c1]=2;
                }
              }
            }
          }
        }
      }
      drawMatrixDivs();
    }
    
    createMatrixDivs();
    drawMatrixDivs();
    outline(+prompt("Enter outline width: "));

    【讨论】:

    • 结果看起来很棒。你能谈谈复杂性吗?在我看来它在 O(n^4) 中运行,但我正在寻找更小的东西。
    • 谢谢!嗯,非常复杂... O(n^4)?其中 n 是宽度或高度,它们相同 = 16... 含义 - 是的,您是正确的...
    • 我注意到你投了赞成票,你也接受吗?谢谢!
    • 我投了赞成票,因为你的努力和好看的结果,但在问题中我要求一个好的性能算法。此任务将在移动应用程序中运行,我负担不起 O(n^4) 算法,因为我的纹理不会是 16x16 xD
    • @Metabolic,谢谢! (以及赞成票 - 我猜其中之一就是你)
    【解决方案2】:

    您的绿色轮廓显示距离红色像素

    想要使用欧几里得距离测量的距离红色像素

    这有点棘手,但您实际上可以在线性 (O(width*height)) 时间内做到这一点。

    PASS1:创建一个新矩阵 M[y][x] 给出从 (x,y) 到红色像素的垂直距离,如果距离大于 w,则为 w+1。您可以通过向上然后向下扫描每一列来在线性时间内完成此操作。

    PASS2:从左到右扫描每一行。其中 M[y][x] 2-M[y][x]2) 右侧的像素。记住你已经着色了多远,并避免重新着色你已经完成的像素,所以这个过程也需要线性时间。做同样的事情从右到左扫描。

    为 sqrt(w2-M[y][x]2) 建立一个查找表以避免一直计算它。

    由于@iAmOren 可以很好地提供工作 JS,我将公然复制并修复它以使用更快的算法:

    var matrix=[
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ];
    
    
    function createMatrixDivs() {
      for(var r=0; r<16; r++) {
        for(var c=0; c<16; c++) {
          var cell=document.createElement("div");
          cell.style="border:1px solid blue;position:absolute;width:10px;height:10px;left:"+10*c+"px;top:"+10*r+"px;";
          cell.id=r+","+c;
          document.body.append(cell);
        }
      }
    }
    
    function drawMatrixDivs() {
      for(var r=0; r<16; r++) {
        for(var c=0; c<16; c++) {
          document.getElementById(r+","+c).style.backgroundColor=(matrix[r][c]==0?"white":matrix[r][c]==1?"red":matrix[r][c]==2?"green":"gray");
        }
      }
    }
    
    function outline(w) {
      var M = matrix.map(function(row) {
        return row.slice();
      });
      var x,y,d,i,s,e;
    
      //PASS 1 - put vertical distances in M
    
      for(x=0; x<16; x++) {
        d=w+1;
        for(y=0; y<16; y++) {
          if (matrix[y][x]==1) {
            d=0;
          } else if (d<=w) {
            ++d;
          }
          M[y][x]=d;
        }
        d=w+1;
        for(y=15; y>=0; y--) {
          if (matrix[y][x]==1) {
            d=0;
          } else if (d<=w) {
            ++d;
          }
          if (M[y][x] > d) {
            M[y][x] = d;
          }
        }
      }
    
      // Precalculate vertical distance -> horizontal span
      var spans=[];
      for (i=0; i<=w; ++i) {
        spans[i] = Math.sqrt((w+0.3)*(w+0.3) - i*i)|0;
      }
    
      // PASS 2 fill every pixel within w
    
      for(y=0; y<16; y++) {
        e=-1;
        for (x=0; x<16; ++x) {
          d = M[y][x];
          if (d<=w) {
            s=Math.max(x,e);
            e=Math.max(e,x+spans[d]);
            for(; s<=e && s<16; ++s) {
              matrix[y][s] = matrix[y][s]||2;
            }
          }
        }
        e=17;
        for (x=15; x>=0; --x) {
          d = M[y][x];
          if (d<=w) {
            s=Math.min(x,e);
            e=Math.min(e,x-spans[d]);
            for(; s>=e && s>0; --s) {
              matrix[y][s] = matrix[y][s]||2;
            }
          }
        }
      }
      drawMatrixDivs();
    }
    createMatrixDivs();
    drawMatrixDivs();
    outline(+prompt("Enter outline width: "));

    【讨论】:

    • 感谢您的信任(和赞成票)!我都跑了,你的更快(2ms vs 10ms+)。 (通过使用var t=new Date(); outline(3); console.log(new Date() - t);)结果似乎有点不同,但总的来说,目标实现了。我仍然不明白这除了 O(n^4) 之外是如何被认为的,如果不是,为什么我的也不小于 O(n^4)。
    • 如果你想更准确地了解复杂度,这个是 O(wid * hei),因为它对每个像素做的工作量是恒定的,而你的是 O(wid^2* hei^2),因为对于图像中的每个清晰像素,它会在整个图像中搜索附近的红色像素。您可以通过仅搜索 w 像素来轻松地将其优化为 O(wid * hei * w^2),但是当 w 很大时仍然会慢很多。
    • 因为 w & h 都是 n,甚至其中一部分也是 O(wn),而且搜索甚至受限于距离(没有真正的方法来搜索一个圆,所以) - 需要搜索一个框,O(wn),并且一起:O(w^2 * h^2) => O(n^4),因为距离可能是 n (w| H)。当然,我本可以让我的代码更高效,但是对于初学者来说,我想要一个有效的代码。在石轮上添加橡胶比发明轮子要小一步...... :)
    猜你喜欢
    • 2017-03-17
    • 2010-11-01
    • 2017-02-03
    • 1970-01-01
    • 2017-07-28
    • 1970-01-01
    • 1970-01-01
    • 2012-11-02
    • 2023-03-20
    相关资源
    最近更新 更多