【问题标题】:Binary Image "Lines-of-Sight" Edge Detection二值图像“视线”边缘检测
【发布时间】:2015-08-29 22:34:47
【问题描述】:

考虑这个二进制图像:

正常的边缘检测算法(如Canny)将二值图像作为输入,并生成红色显示的轮廓。我需要另一种将点“P”作为第二条输入数据的算法。 “P”是上一张图像中的黑点。该算法应导致蓝色轮廓。蓝色轮廓表示点“P”二值图像的视线边缘。

我搜索了很多实现此目的的图像处理算法,但没有找到。我也试着去想一个新的,但我还是有很多困难。

【问题讨论】:

  • 你的轮廓是如何表示的?它们是多边形还是位图?
  • @Kaganar 他们是位图。

标签: algorithm image-processing edge-detection


【解决方案1】:

既然你有位图,你可以使用位图算法。

Here's a working example(在 JSFiddle 中或见下文)。 (Firefox、Chrome,但不是 IE)

伪代码:

// part 1: occlusion
mark all pixels as 'outside'
for each pixel on the edge of the image
    draw a line from the source pixel to the edge pixel and
    for each pixel on the line starting from the source and ending with the edge
        if the pixel is gray mark it as 'inside'
        otherwise stop drawing this line

// part 2: edge finding
for each pixel in the image
    if pixel is not marked 'inside' skip this pixel
    if pixel has a neighbor that is outside mark this pixel 'edge'

// part 3: draw the edges
highlight all the edges

起初这听起来很糟糕......但实际上,它是O(p),其中p 是图像中的像素数。

完整代码在这里,最好是整页:

var c = document.getElementById('c');
c.width = c.height = 500;
var x = c.getContext("2d");

//////////// Draw some "interesting" stuff ////////////
function DrawScene() {
    x.beginPath();
    x.rect(0, 0, c.width, c.height);
    x.fillStyle = '#fff';
    x.fill();

    x.beginPath();
    x.rect(c.width * 0.1, c.height * 0.1, c.width * 0.8, c.height * 0.8);
    x.fillStyle = '#000';
    x.fill();
    
    x.beginPath();
    x.rect(c.width * 0.25, c.height * 0.02 , c.width * 0.5, c.height * 0.05);
    x.fillStyle = '#000';
    x.fill();

    x.beginPath();
    x.rect(c.width * 0.3, c.height * 0.2, c.width * 0.03, c.height * 0.4);
    x.fillStyle = '#fff';
    x.fill();

    x.beginPath();
    var maxAng = 2.0;
    function sc(t) { return t * 0.3 + 0.5; }
    function sc2(t) { return t * 0.35 + 0.5; }
    for (var i = 0; i < maxAng; i += 0.1)
        x.lineTo(sc(Math.cos(i)) * c.width, sc(Math.sin(i)) * c.height);
    for (var i = maxAng; i >= 0; i -= 0.1)
        x.lineTo(sc2(Math.cos(i)) * c.width, sc2(Math.sin(i)) * c.height);
    x.closePath();
    x.fill();

    x.beginPath();
    x.moveTo(0.2 * c.width, 0.03 * c.height);
    x.lineTo(c.width * 0.9, c.height * 0.8);
    x.lineTo(c.width * 0.8, c.height * 0.8);
    x.lineTo(c.width * 0.1, 0.03 * c.height);
    x.closePath();
    x.fillStyle = '#000';
    x.fill();
}

//////////// Pick a point to start our operations: ////////////
var v_x = Math.round(c.width * 0.5);
var v_y = Math.round(c.height * 0.5);

function Update() {
    if (navigator.appName == 'Microsoft Internet Explorer'
        ||  !!(navigator.userAgent.match(/Trident/)
        || navigator.userAgent.match(/rv 11/))
        || $.browser.msie == 1)
    {
        document.getElementById("d").innerHTML = "Does not work in IE.";
        return;
    }
    
    DrawScene();

    //////////// Make our image binary (white and gray) ////////////
    var id = x.getImageData(0, 0, c.width, c.height);
    for (var i = 0; i < id.width * id.height * 4; i += 4) {
        id.data[i + 0] = id.data[i + 0] > 128 ? 255 : 64;
        id.data[i + 1] = id.data[i + 1] > 128 ? 255 : 64;
        id.data[i + 2] = id.data[i + 2] > 128 ? 255 : 64;
    }

    // Adapted from http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#JavaScript
    function line(x1, y1) {
        var x0 = v_x;
        var y0 = v_y;
        var dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
        var dy = Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1; 
        var err = (dx>dy ? dx : -dy)/2;

        while (true) {
            var d = (y0 * c.height + x0) * 4;
            if (id.data[d] === 255) break;
            id.data[d] = 128;
            id.data[d + 1] = 128;
            id.data[d + 2] = 128;

            if (x0 === x1 && y0 === y1) break;
            var e2 = err;
            if (e2 > -dx) { err -= dy; x0 += sx; }
            if (e2 < dy) { err += dx; y0 += sy; }
        }
    }

    for (var i = 0; i < c.width; i++) line(i, 0);
    for (var i = 0; i < c.width; i++) line(i, c.height - 1);
    for (var i = 0; i < c.height; i++) line(0, i);
    for (var i = 0; i < c.height; i++) line(c.width - 1, i);
    
    // Outline-finding algorithm
    function gb(x, y) {
        var v = id.data[(y * id.height + x) * 4];
        return v !== 128 && v !== 0;
    }
    for (var y = 0; y < id.height; y++) {
        var py = Math.max(y - 1, 0);
        var ny = Math.min(y + 1, id.height - 1);
                    console.log(y);

        for (var z = 0; z < id.width; z++) {
            var d = (y * id.height + z) * 4;
            if (id.data[d] !== 128) continue;
            var pz = Math.max(z - 1, 0);
            var nz = Math.min(z + 1, id.width - 1);
            if (gb(pz, py) || gb(z, py) || gb(nz, py) ||
                gb(pz, y) || gb(z, y) || gb(nz, y) ||
                gb(pz, ny) || gb(z, ny) || gb(nz, ny)) {
                id.data[d + 0] = 0;
                id.data[d + 1] = 0;
                id.data[d + 2] = 255;
            }
        }
    }

    x.putImageData(id, 0, 0);

    // Draw the starting point
    x.beginPath();
    x.arc(v_x, v_y, c.width * 0.01, 0, 2 * Math.PI, false);
    x.fillStyle = '#800';
    x.fill();
}

Update();

c.addEventListener('click', function(evt) {
    var x = evt.pageX - c.offsetLeft,
        y = evt.pageY - c.offsetTop;
    v_x = x;
    v_y = y;
    Update();
}, false);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>
<center><div id="d">Click on image to change point</div>
<canvas id="c"></canvas></center>

【讨论】:

  • 如果您希望生成轮廓的矢量表示,您可以连接线命中 - 您最终会得到与@LouisRicci 建议的非常相似的东西,但它' d 比等角径向扫掠具有更好的性能。
【解决方案2】:

第一种方法

主要思想

  1. 运行边缘检测算法(Canny 应该可以做到)。
  2. 对于每个轮廓点C,计算三元组(slope, dir, dist),其中:
    • slope 是通过PC 的直线的斜率
    • dir 是一个位,如果CP 的右侧(在x 轴上),则设置该位,如果在左侧则重置;它用于区分具有相同斜率但位于P 两侧的点
    • distPC 之间的距离。
  3. 对轮廓点集进行分类,使一个类包含具有相同键 (slope, dir) 的点,并保留每个此类具有最小 dist 的点。让S 成为这些最近点的集合。
  4. S 按顺时针顺序排序。
  5. 再次遍历已排序的集合,当两个连续点相距太远时,在它们之间画一条线段,否则只画这些点。

注意事项

  • 您实际上并不需要计算PC 之间的实际距离,因为您只使用dist 在第3 步确定最接近P 的点。相反,您可以保留@ 987654342@ 在dist。这条信息还应该告诉您具有相同斜率的两个点中哪一个最接近P。此外,C.x - P.x 吞噬了dir 参数(在符号位中)。所以你也不需要dir

  • 理想情况下,第 3 步中的分类可以通过散列来完成(因此,以线性步数),但由于双精度/浮点数需要四舍五入,您可能需要通过四舍五入来允许出现小错误的斜坡。

第二种方法

主要思想

您可以从P 开始执行一种 BFS,例如在尝试确定 P 所在的国家/地区时。对于每个像素,查看它周围已经被 BFS 访问过的像素(称为邻居)。根据视线内相邻像素的分布,确定当前访问的像素是否也在视线内。您可能可以在相邻像素上应用一种卷积运算符(与任何其他过滤器一样)。此外,您实际上不需要立即决定像素是否在视线内。相反,您可以计算其为真的概率。

注意事项

  • 由于您的图形是 2D 图像,BFS 应该非常快(因为边数与顶点数成线性关系)。
  • 第二种方法无需运行边缘检测算法。此外,如果P 所在的国家/地区比图像小得多,那么整体性能应该比单独运行边缘检测算法要好。

【讨论】:

    【解决方案3】:

    我只会用光线碰撞估计 P 的视线轮廓。

    RESOLUTION = PI / 720;
    For rad = 0 To PI * 2 Step RESOLUTION
      ray = CreateRay(P, rad)
      hits = Intersect(ray, contours)
      If Len(hits) > 0
        Add(hits[0], lineOfSightContour)
    

    【讨论】:

      【解决方案4】:

      https://en.wikipedia.org/wiki/Hidden_surface_determination 与例如Z-Buffer 相对容易。边缘检测看起来要复杂得多,可能需要一些调整。为什么不从其他人调整过的库中获取现有的边缘检测算法,然后加入一些 Z 缓冲代码来从红色计算蓝色轮廓?

      【讨论】:

        猜你喜欢
        • 2010-12-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-11
        • 2016-01-08
        • 2016-09-21
        相关资源
        最近更新 更多