【问题标题】:Fast way to apply custom function to every pixel in image将自定义函数应用于图像中每个像素的快速方法
【发布时间】:2018-08-24 03:27:01
【问题描述】:

我正在寻找一种更快的方法来将自定义函数应用于我用来移除蓝色背景的图像。我有一个函数可以计算每个像素与背景中大约蓝色的距离。带有循环的原始代码如下所示:

def dist_to_blue(pix):
    rdist = 76 - pix[0]
    gdist = 150 - pix[1]
    bdist = 240 - pix[2]
    return rdist*rdist + gdist*gdist + bdist*bdist

imgage.shape #outputs (576, 720, 3)
for i, row in enumerate(image):
    for j, pix in enumerate(row):
        if dist_to_blue(pix) < 12000: image[i,j] = [255,255,255]

但是,对于这个相对较小的图像,运行此代码大约需要 8 秒。我一直在尝试使用 numpy 的“vectorize”函数,但这会将函数单独应用于每个值。但是我想对每个像素都这样做,也就是不扩展 z/rgb 维度

我提出的唯一改进是将 for 循环替换为以下内容:

m = np.apply_along_axis(lambda pix: (255,255,255) if dist_to_blue(pix) &lt; 12000 else pix, 2, image)

运行大约 7 秒,但速度仍然非常缓慢。有什么我遗漏的东西可以加速到合理的执行时间

【问题讨论】:

    标签: python image numpy


    【解决方案1】:

    这应该快一点... ;)

    import numpy as np 
    blue = np.full_like(image, [76,150,250])
    mask = np.sum((image-blue)**2,axis=-1) < 12000
    image[mask] = [255,0,255]
    

    在这里,您正在生成理想的蓝色图像,将图像的差异逐个像素平方,然后在最后一个轴(rgb 向量)上求和,然后生成蒙版并使用它来修改原始图像中的值。

    【讨论】:

    • 完美!这将运行时间减少到 0.01 秒!然而,为了让它工作,我必须避免 8 位 uint 在通过将其更改为 16 位或 32 位 uint 来平方差时溢出:mask = np.sum((image-blue)**2,axis=-1, dtype=np.uint16) &lt; 12000
    • 而不是blue = np.full_like(image, [76,150,250]),您可以只使用blue = np.array([76,150,250]),然后让广播完成其余的工作。而且由于您没有使用*_like 运算符(这使得blue.dtype == image.dtype == 'uint8'),您以后不必指定uint16,因为它已经是标准的uint32
    【解决方案2】:
    from scipy.spatial.distance import cdist
    
    blue = np.array([76, 150, 250])
    
    def crush_color(image, color, thr = np.sqrt(12000), new = np.array([255, 255, 255]));
        dist_to_color = cdist(image.reshape(-1, 3), color, 'sqeuclidean').reshape(image.shape[:-1])
        image[dist_to_color[..., None] < thr**2] = new
    
    crush_color(image, blue)
    

    1) 使用 cdist 代替手动计算距离,这将比 numpy 广播更快地计算距离(在这种情况下为 ueclidean 平方)。

    2) 原地更换

    【讨论】:

      【解决方案3】:

      结合@dash-tom-bang 和@kevinkayaks 答案的方法

      # Assume the image is of shape (h, w, 3)
      # Precompute some data
      RDIST = np.array([(76 - r)**2 for r in range(256)])
      GDIST = np.array([(150 - g)**2 for g in range(256)])
      BDIST = np.array([(240 - b)**2 for b in range(256)])
      
      # Calculate and apply mask
      mask = (RDIST[image[:,:,0]] + GDIST[image[:,:,1]] + BDIST[image[:,:,2]]) < 12000
      image[mask] = [255,255,255]
      

      【讨论】:

      • 我喜欢这个想法,但我不认为它是这样工作的。您需要开发差异数组,然后使用这些预先计算的值来开发平方差矩阵。我想看看!它会起作用,而且会更快
      • @kevinkayaks,是的,我之前假设(3,h,w)有轻微的尺寸混合。已更正。
      • 这仍然不起作用。您假设列表支持精美的索引
      • 将预先计算的值转换为 numpy 数组后,它就像一个魅力:RDIST = np.array([(76 - r)**2 for r in range(256)])
      【解决方案4】:

      这只是在黑暗中的一个镜头,但也许预先计算一些数据会有所帮助?我不确定,但查表可能比加法和乘法更快?

      def square(x): # maybe there's a library function for this?
          return x*x
      
      RDIST = [square(76 - r) for r in range(256)]
      GDIST = [square(150 - g) for g in range(256)]
      BDIST = [square(240 - b) for b in range(256)]
      
      def dist_to_blue(pix):
          return RDIST[pix[0]] + GDIST[pix[1]] + BDIST[pix[2]]
      

      我也怀疑你是否有办法只获得每行的像素数组,这可能会更快,而不是索引每个单独的像素,但我不知道正在使用的库。

      【讨论】:

        【解决方案5】:

        有一些方法可以通过使用for loops 来加速您的 Numpy 代码,例如使用 Numpy 的 ufunc(+-***&lt;...)、聚合(summaxminmean...)、广播、屏蔽、花式索引。 下面的代码可能会给你一些提示:

        dist = np.expand_dims(np.array([76, 150, 240]), axis=0)
        image[np.where(np.sum((image-dist)**2, axis=2) < 12000)]=255
        

        【讨论】:

        • np.where 不是必需的,但它更快吗?我喜欢使用扩展暗淡。这很酷。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多