为一组点生成最近邻距离图

时间:2014-06-03 16:34:15

标签: python nearest-neighbor

我有一个(x,y)点的列表。我正在尝试将每个点的距离绘制成图像,所以我的天真函数看起来像:

from scipy.spatial.distance import cdist
from numpy import *

def findDistances(imageSize, points):
    image = zeros(imageSize)
    for x in range(0,imageSize[1]):
        for y in range(0,imageSize[0]):
            image[y,x] = np.min(cdist(array([[x,y]]), points))
    return image

这个功能很好,它给出了我想要的东西(见下图)。对于~1MP图像,这需要大约100秒,这对于仅需要进行一次的事情来说很好,但我认为还有改进的余地。我也尝试过:

image[y,x] = min(linalg.norm(array([[x,y]])- points, axis=1))

哪个在相同的时间内运行 - 这是有道理的,他们可能会在引擎盖下做类似的事情,但我没有检查来源以确定。

我看过Scipy的cKDTree,其中包括:

from scipy.spatial import cKDTree

def findDistancesTree(imageSize, points):
    tree = cKDTree(points)
    image = zeros(imageSize)
    for x in range(0,imageSize[1]):
        for y in range(0,imageSize[0]):
            image[y,x] = tree.query([x,y])[0]
    return image

tree.query的调用大约需要50μs(来自%timeit),实际上需要70-80秒才能生成1MP距离地图。 20s的改善比腹股沟的改善更好,但我不知道我是否可以进一步改善它。

%timeit np.min(linalg.norm(array([[random.random()*1000,random.random()*1000]]) - points, axis=1))
%timeit np.min(cdist(array([[random.random()*1000,random.random()*1000]]), points))
%timeit tree.query(array([[random.random()*1000,random.random()*1000]]))

10000 loops, best of 3: 82.8 µs per loop
10000 loops, best of 3: 67.9 µs per loop
10000 loops, best of 3: 52.3 µs per loop

就复杂性而言,暴力应该类似于O(NM),其中N是图像中的像素数,M是要检查的点数。我期待更多的加速,因为对于O(N log(M))像素,搜索时间应更像N,每个像素的查找时间log(M) - 我错过了什么?

Imgur

3 个答案:

答案 0 :(得分:2)

这听起来像一个问题,即使使用GPU的基本暴力实施也会带来很好的改善。所以我试了一下。而且改善非常好。

我使用pyopencl进行了测试。

import pyopencl as cl 
import numpy as np 

def findDistances_cl(imageSize, points):
    #Boilerplate opencl code
    ctx = cl.create_some_context()
    queue = cl.CommandQueue(ctx)

    f = open('nn.cl', 'r')
    fstr = "".join(f.readlines())
    program = cl.Program(ctx, fstr).build()

    #Creating buffers for the opencl kernel
    mf = cl.mem_flags
    img = np.empty(imageSize, dtype=np.float32)
    x_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.USE_HOST_PTR, hostbuf=points[:,0].astype(np.float32))
    y_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.USE_HOST_PTR, hostbuf=points[:,1].astype(np.float32))
    n_points = cl.Buffer(ctx, mf.READ_ONLY | mf.USE_HOST_PTR, hostbuf=np.array([len(points)],dtype=np.int))
    img_buf = cl.Buffer(ctx, mf.WRITE_ONLY, img.nbytes)

    #Run the kernel
    exec_evt = program.nn(queue, img.shape, None, img_buf, x_buf, y_buf, n_points)
    exec_evt.wait()
    #read back the result
    cl.enqueue_read_buffer(queue, img_buf, img).wait()

    return img

opencl内核(nn.cl)

__kernel void nn(__global float *output, __global constant float *x , __global constant float *y, __global constant int *numPoints)
    {
        int row = get_global_id(0);
        int col = get_global_id(1);

        int numRows = get_global_size(0);
        int numCols = get_global_size(1);

        int gid = col+ row*numCols;

        float minDist = numRows * numCols;

        for(int i = 0; i < *numPoints; i++){
          minDist = min(minDist, sqrt((row - y[i])*(row - y[i]) + (col - x[i])*(col - x[i])));
        }
        output[gid] = minDist;
    }

计时结果。

imageSize = [1000, 1000]
points = np.random.random((1000,2))*imageSize[0]

In [4]: %timeit findDistancesTree(imageSize, points)
1 loops, best of 3: 27.1 s per loop

In [7]: %timeit findDistances_cl(imageSize, points)
10 loops, best of 3: 55.3 ms per loop

速度提升约490倍。如果你需要更高的速度,那里有更高级的算法用于与GPU最近的邻居。

答案 1 :(得分:0)

您可以使用分层聚类来聚类点,即树形图。对于每棵树,您可以计算voronoi图和点多边形函数。

答案 2 :(得分:-1)

或许Voronoi diagramFortune's algorithm可以找到您想要的解决方案。

这个想法将是:

  • 计算Voronoi图或M点(O ln(M))
  • 对于N个像素中的每一个
    • 找到相应的区域
    • 计算到区域点的距离

现在的问题是如何在O(ln(M))中找到与像素对应的区域。

希望这有帮助!