算法 - 给定一组带坐标的像素,如何以有效的方式找到所有连续的线?

时间:2017-05-07 05:01:42

标签: algorithm 2d pixels outline

我正在研究一个挤出功能,在给定2D纹理及其厚度的情况下创建网格。

示例:

通过简单地查找靠近边缘或接近透明的像素,我已经找到了纹理的轮廓。它甚至对于凹形(圆环形)形状也很有效,但现在我留下了一系列轮廓像素。

结果如下:

enter image description here

问题是这些值是从左上角到右下角排序的,它们不适合构建实际的3D轮廓。

我目前的想法如下:

第1步。

从索引[0],在右侧查看与起点不同的最近的连续点。

  • 如果找到,请将其移至另一个阵列。

  • 如果没有,请查看底部。继续,直到达到起点。

Step2。

从阵列中剩余的像素中选择另一个像素(如果有)。

从Step1重复。

在我看来,这会起作用,但效果似乎很低。研究,我发现了摩尔 - 邻居跟踪算法,但我找不到任何一个与凸形状一起工作的例子。

有什么想法吗?

2 个答案:

答案 0 :(得分:0)

算法循环使用像素,我们只检查每个像素一次,跳过空单元格,并将其存储在列表中,因为不存在重复项。

isEmpty 实现取决于您的情况下透明度的工作方式,如果某种颜色被视为透明,则下面是我们拥有Alpha通道的情况。

阈值是Alpha级别,表示要将单元格视为非空的最低可见性。

isBorder 将检查Moore邻居是否为空,在这种情况下它是一个边框单元格,否则不是因为它被填充的单元格包围。

isEmpty(x,y): image[x,y].alpha <= threshold

isBorder(x,y)
:   if isEmpty(x  , y-1): return true
:   if isEmpty(x  , y+1): return true
:   if isEmpty(x-1, y  ): return true
:   if isEmpty(x+1, y  ): return true
:   if isEmpty(x-1, y-1): return true
:   if isEmpty(x-1, y+1): return true
:   if isEmpty(x+1, y-1): return true
:   if isEmpty(x+1, y+1): return true
:   otherwise: return false

getBorderCellList()
:   l = empty-list
:   for x in 0..image.width
:   : for y in 0..image.height
:   : : if !isEmpty(x,y)
:   : : : if isBorder(x,y)
:   : : : : l.add(x,y)
:   return l

优化您可以通过预先计算boolean e[image.width][image.height]e[x,y] = 1 image[x,y]如果isBorder(x,y): e[x-1,y] | e[x+1,y] | .. | e[x+1,y+1]不为空来优化此项,然后直接使用它进行检查,比如init() : for x in 0..image.width : : for y in 0..image.height : : : e[x,y] = isEmpty(x,y) isEmpty(x,y): image[x,y].alpha <= threshold isBorder(x,y): e[x-1,y] | e[x+1,y] | .. | e[x+1,y+1] getBorderCellList() : l = empty-list : for x in 0..image.width : : for y in 0..image.height : : : if not e[x,y] : : : : if isBorder(x,y) : : : : : l.add(x,y) : return l

show databases

答案 1 :(得分:0)

最后,我找到了自己的答案,所以我想在此分享:

找到给定图像的轮廓(使用每个像素的alpha值)后,像素将按行排序,适合绘制它们但不适合构建网格。

因此,下一步是找到连续的行。这是通过首先检查找到的像素的任何邻居是否优先于顶部/左/右/底部(否则它将跳过角落)来完成。

继续前进,直到原始数组中没有像素。

这是实际的实现(对于Babylon.js,但这个想法适用于任何其他引擎):

游乐场:https://www.babylonjs-playground.com/#9GPMUY#11

var GetTextureOutline = function (data, keepOutline, keepOtherPixels) {
    var not_outline = [];
    var pixels_list = [];
    for (var j = 0; j < data.length; j = j + 4) {
        var alpha = data[j + 3];
        var current_alpha_index = j + 3;
        // Not Invisible
        if (alpha != 0) {
            var top_alpha = data[current_alpha_index - (canvasWidth * 4)];
            var bottom_alpha = data[current_alpha_index + (canvasWidth * 4)];
            var left_alpha = data[current_alpha_index - 4];
            var right_alpha = data[current_alpha_index + 4];

            if ((top_alpha === undefined || top_alpha == 0) ||
                (bottom_alpha === undefined || bottom_alpha == 0) ||
                (left_alpha === undefined || left_alpha == 0) ||
                (right_alpha === undefined || right_alpha == 0)) {
                pixels_list.push({
                    x: (j / 4) % canvasWidth,
                    y: parseInt((j / 4) / canvasWidth),
                    color: new BABYLON.Color3(data[j] / 255, data[j + 1] / 255, data[j + 2] / 255),
                    alpha: data[j + 3] / 255
                });

                if (!keepOutline) {
                    data[j] = 255;
                    data[j + 1] = 0;
                    data[j + 2] = 255;
                }
            } else if (!keepOtherPixels) {
                not_outline.push(j);
            }
        }

    }

    // Remove not-outline pixels
    for (var i = 0; i < not_outline.length; i++) {
        if (!keepOtherPixels) {
            data[not_outline[i]] = 0;
            data[not_outline[i] + 1] = 0;
            data[not_outline[i] + 2] = 0;
            data[not_outline[i] + 3] = 0;
        }
    }


    return pixels_list;
}

var ExtractLinesFromPixelsList = function (pixelsList, sortPixels) {
    if (sortPixels) {
        // Sort pixelsList
        function sortY(a, b) {
            if (a.y == b.y) return a.x - b.x;
            return a.y - b.y;
        }
        pixelsList.sort(sortY);
    }

    var lines = [];
    var line = [];
    var pixelAdded = true;
    var skipDiagonals = true;
    line.push(pixelsList[0]);
    pixelsList.splice(0, 1);

    var countPixels = 0;
    while (pixelsList.length != 0) {
        if (!pixelAdded && !skipDiagonals) {
            lines.push(line);
            line = [];
            line.push(pixelsList[0]);
            pixelsList.splice(0, 1);
        } else if (!pixelAdded) {
            skipDiagonals = false;
        }

        pixelAdded = false;
        for (var i = 0; i < pixelsList.length; i++) {
            if ((skipDiagonals && (
                line[line.length - 1].x + 1 == pixelsList[i].x && line[line.length - 1].y == pixelsList[i].y ||
                line[line.length - 1].x - 1 == pixelsList[i].x && line[line.length - 1].y == pixelsList[i].y ||
                line[line.length - 1].x == pixelsList[i].x && line[line.length - 1].y + 1 == pixelsList[i].y ||
                line[line.length - 1].x == pixelsList[i].x && line[line.length - 1].y - 1 == pixelsList[i].y)) || (!skipDiagonals && (
                    line[line.length - 1].x + 1 == pixelsList[i].x && line[line.length - 1].y + 1 == pixelsList[i].y ||
                    line[line.length - 1].x + 1 == pixelsList[i].x && line[line.length - 1].y - 1 == pixelsList[i].y ||
                    line[line.length - 1].x - 1 == pixelsList[i].x && line[line.length - 1].y + 1 == pixelsList[i].y ||
                    line[line.length - 1].x - 1 == pixelsList[i].x && line[line.length - 1].y - 1 == pixelsList[i].y
                ))) {
                line.push(pixelsList[i]);
                pixelsList.splice(i, 1);
                i--;
                pixelAdded = true;
                skipDiagonals = true;
            }
        }


    }
    lines.push(line);
    return lines;
}