有没有比使用 np.where 更快的迭代一个非常大的 2D numpy 数组的方法?

Is there a faster method for iterating over a very big 2D numpy array than using np.where?

我有一个巨大的二维 numpy 数组,其中填充了整数值。我通过 gdal.GetRasterBand() 从 .tif-image 收集它们。 图像的像素值表示唯一的集群标识号。因此,一个簇内的所有像素都具有相同的值。 在我的脚本中,我想检查簇的像素是否超过特定阈值。如果集群大小大于阈值,我想保留集群并给它们一个像素值 1。如果集群的像素小于阈值,则该集群的所有像素都应获得值 0。

我的代码目前有效,但非常非常慢。而且因为我想改变阈值,所以需要永远。 我将衷心感谢您的帮助。谢谢。

# Import GeoTIFF via GDAL and convert to NumpyArray
data = gdal.Open(image)
raster = data.GetRasterBand(1)
raster = raster.ReadAsArray()

# Different thresholds for iteration
thresh = [0,10,25,50,100,1000,2000]

for threshold in thresh:
        clusteredRaster = np.array(raster.copy(), dtype = int)

        for clump in np.unique(clusteredRaster): # Unique ids of the clusters in image

            if clusteredRaster[np.where(clusteredRaster == clump)].size >= threshold: 
                clusteredRaster[np.where(clusteredRaster == clump)] = int(1)

            else:
                clusteredRaster[np.where(clusteredRaster == clump)] = int(0)
'''

[ClusterImage][1]

In the image you can see the cluster image. Each color stands vor a specific clusternumber. I want to delete the small ones (under a specific size) and just keep the big ones.

  [1]: https://i.stack.imgur.com/miEKg.png

可以进行一些修改来提高性能,

clusteredRaster = np.array(raster.copy(), dtype = int)

可以替换为

clusteredRaster = raster.astype(int)

这本质上既是复制运算符又是转换运算符,因此此操作速度更快。

现在

clusteredRaster[np.where(clusteredRaster == clump)] = int(1)

您不需要调用 np.where,这样会更快

clusteredRaster[clusteredRaster == clump] = int(1)

这部分也完成了

clusteredRaster[np.where(clusteredRaster == clump)].size

您还可以删除 clusteredRaster==clump 的计算两次,如下所示:

for clump in np.unique(clusteredRaster): # Unique ids of the clusters in image
    indicies = clusteredRaster==clump

    if clusteredRaster[indicies].size >= threshold: 
        clusteredRaster[indicies] = int(1)

    else:
        clusteredRaster[indicies] = int(0)

我认为你的函数现在的运行速度是原来的两倍,但是如果你想 运行 更快,你必须使用更小的数据类型,比如 np.uint8 而不是普通的 int,前提是你的图像是用 RGB 编码的并且可以用 8 位整数表示(或者如果 8 位太低,则可能是 np.uint16?) 这是从 python 端获得的最快速度。

有一些更快的方法,例如使用带有 openmp 的 C 模块来多线程处理您的工作 across 多个内核,这可以通过 numba or cython 之类的方法轻松完成,而不必担心编写 C 代码,但是如果你想获得有史以来最快的性能,还有很多阅读要做,比如使用哪个线程后端(TBB vs openmp)和一些 os 和设备相关的功能。

除了 之外,您还可以在 for 循环之外计算唯一值、索引和计数。另外,您不需要每次都复制 raster - 您可以制作一个 np.uint8 的数组。

这给出了与您的原始实施相同的结果:

data = gdal.Open(image)
raster = data.GetRasterBand(1).ReadAsArray()

# Different thresholds for iteration
thresh = [0, 10, 25, 50, 100, 1000, 2000]

# determine the unique clumps and their frequencies outside of the for loops
clumps, counts = np.unique(raster, return_counts=True)

# only determine the indices once, rather than for each threshold
indices = np.asarray([raster==clump for clump in clumps])

for threshold in thresh:
    
    clustered_raster = np.zeros_like(raster, dtype=np.uint8)

    for clump_indices, clump_counts in zip(indices, counts):
    
        clustered_raster[clump_indices] = clump_counts >= threshold

根据您的有用回答,我得到了一个简单的解决方案! 这个想法是找到每个阈值的唯一值和簇大小,并立即填写正确的值,从而避免循环。 它将每次迭代的迭代时间从最初的 142 秒减少到 0.52 秒并重现相同的结果。

data = gdal.Open(image)
raster = data.GetRasterBand(1).ReadAsArray()

thresh = [0, 10, 25, 50, 100, 1000, 2000]   
for threshold in thresh:
    # Create new 0-raster with same dimensions as input raster
    clusteredRaster = np.zeros(raster.shape, dtype = uint8)
    
    # Get unique cluster IDs and count the size of the occurence
    clumps, counts = np.unique(raster, return_counts=True)

    # Get only the clumps which are bigger than the threshold
    biggerClumps = clumps[counts >= threshold]

    # fill in ones for the relevant cluster IDs
    clusteredRaster[np.isin(raster,biggerClumps)] = 1