二进制图像“视线”边缘检测

时间:2015-06-14 10:03:44

标签: algorithm image-processing edge-detection

考虑这个二进制图像: enter image description here

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

我搜索了许多实现此目的的图像处理算法,但没有找到。我也试着考虑一个新的,但我仍然有很多困难。

4 个答案:

答案 0 :(得分:3)

由于你有一个位图,你可以使用位图算法。

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>

答案 1 :(得分:2)

我只是用光线碰撞来估计P&#39的视线轮廓。

node a = { .next = NULL };

答案 2 :(得分:1)

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

答案 3 :(得分:1)

第一种方法

主要想法

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

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

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

    第二种方法

    主要想法

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

    备注

    • 由于您的图表是2D图像,因此BFS应该非常快(因为边缘数量与顶点数量呈线性关系)。
    • 第二种方法消除了运行边缘检测算法的需要。此外,如果国家/地区P所在的位置远远小于图像,则整体性能应优于仅运行边缘检测算法。