【问题标题】:Python code to quickly reduce the resolution of an image using numpy使用 numpy 快速降低图像分辨率的 Python 代码
【发布时间】:2015-05-08 12:02:08
【问题描述】:

以下代码通过将小像素合并为较大像素来降低 2D numpy 数组(图像)的分辨率。我想知道它是否可以做得更快,或者是否有更快的替代品。此外,任何一般的建议都会受到赞赏。例如,如果有一个代码在速度上相似,但会产生更平滑的缩小图像(例如通过使用样条)

import numpy as np

def reduce_img ( img, bin_fac=1 ):
    assert( len( img.shape ) == 2 )
    imgX0 = img.shape[0] # slow axis
    imgX1 = img.shape[1] # fast axis

    r0 = imgX0 % bin_fac
    r1 = imgX1 % bin_fac

    img = np.copy( img) [:imgX0 - r0, :imgX1-r1]
    # bin the fast  axis 
    img_C = img.ravel(order='C')
    img = np.average( [ img_C[i::bin_fac] for i in xrange( bin_fac )   ],axis=0)
    img = img.reshape( (imgX0-r0, (imgX1-r1)/bin_fac ) , order='C')

    # bin the slow axis
    img_F = img.ravel(order='F')
    img = np.average( [ img_F[i::bin_fac] for i in xrange( bin_fac )   ],axis=0)
    img = img.reshape( ((imgX0-r0)/bin_fac, (imgX1-r1)/bin_fac ), order='F' )

    return img

这是一个结果

>> from pylab import *
>> imshow( img ) 
>> show()

>> img_r = reduce_img( img, bin_fac = 7 )
>> imshow( img_r )
>> show()

>> %timeit( reduce_img( img, bin_fac=7)  )
1000 loops, best of 3: 655 µs per loop

【问题讨论】:

  • 您是否在寻找scipy.misc.imresize
  • 我无法获取您的功能。甚至工作。重塑我们的一般定义numpy.reshape(a, newshape, order='C'),你称它为好像你已经完成了img.reshape。您的示例图像的形状是(600, 400, 4),即使用np.imread 读入。你尝试reshapeing 它,因为它只是一个元组(x,y)。你的新形状应该是(x, y, 4)。你用np. 调用了一个reshape,而不是第二个。另外我不认为你的生成器 expr。正在做你认为他们正在做的事情。如果您认为他们会占用一小块像素的np.average,那么他们不会。 (至少不是这样)。
  • @ljetibo,你说得对,应该是 img = img.reshape( shape ) 等。我在将它复制到堆栈溢出时一定搞砸了。感谢您指出这一点!
  • 另外,这是用于二维图像阵列,而不是更高维图像,例如 RGB。

标签: python performance image-processing numpy


【解决方案1】:

我首先要提一下,您的分箱方式似乎相当不寻常,我想这就是 @ljetibo 在 cmets 中所指的。我会在“优化”讨论之后回到这个话题。

首先,您可以通过摆脱对np.copy 的多余调用来稍微改进您的代码,因为您只是重新绑定 img 到传入的img 的视图.然后ravel 操作将返回一个副本,除非图像形状是合并因子bin_fac 的倍数。

现在,虽然列表解析速度很快,但您正在从一个可能不连续的列表中重新创建一个 numpy 数组,这意味着您正在将内存从一个地方再次复制到另一个地方。这些都是消耗效率的操作。

您可能感兴趣的只是在原始图像上生成一个高度节省内存的视图。这就是as_strided 的用武之地:

from numpy.lib.stride_tricks import as_strided

def strided_rescale(g, bin_fac):
    strided = as_strided(g,
        shape=(g.shape[0]//bin_fac, g.shape[1]//bin_fac, bin_fac, bin_fac),
        strides=((g.strides[0]*bin_fac, g.strides[1]*bin_fac)+g.strides))
    return strided.mean(axis=-1).mean(axis=-1)  # order is NOT important! See notes..

时间考虑表明,这通常比原始方法快一点,随着分箱因子的增加,性能会有所提高:

In [263]: stp = 'from __main__ import img, strided_rescale, reduce_img'

In [264]: for n in range(1,21):
    a = timeit.timeit(stmt='strided_rescale(img, {})'.format(n), setup=stp, number=100)
    b = timeit.timeit(stmt='reduce_img(img, {})'.format(n), setup=stp, number=100)
    c = b*1./a
    d = np.ptp(strided_rescale(img, n) - reduce_img(img,n))
    print('{a:7f} | {b:7f} | {c:1.4f} | {d:1.4g}'.format(**locals()))
   .....:     
0.124911 | 0.277254 | 2.2196 | 0
0.303813 | 0.171833 | 0.5656 | 0
0.217745 | 0.188637 | 0.8663 | 0
0.162199 | 0.139770 | 0.8617 | 0
0.132355 | 0.138402 | 1.0457 | 0
0.121542 | 0.160275 | 1.3187 | 0
0.102930 | 0.162041 | 1.5743 | 0
0.090694 | 0.138881 | 1.5313 | 2.384e-07
0.097320 | 0.174690 | 1.7950 | 1.788e-07
0.082376 | 0.155261 | 1.8848 | 2.384e-07
0.084228 | 0.178397 | 2.1180 | 2.98e-07
0.070411 | 0.181175 | 2.5731 | 2.98e-07
0.075443 | 0.175605 | 2.3277 | 5.96e-08
0.068964 | 0.182602 | 2.6478 | 5.96e-08
0.067155 | 0.168532 | 2.5096 | 1.192e-07
0.056193 | 0.195684 | 3.4824 | 2.98e-07
0.063575 | 0.206987 | 3.2558 | 2.98e-07
0.078850 | 0.187697 | 2.3804 | 2.384e-07
0.053072 | 0.168763 | 3.1799 | 2.384e-07
0.047512 | 0.151598 | 3.1907 | 1.788e-07
# time a | time b   |  b/a   | peak-to-peak: check if both arrays are the same

我相信观察到的数组相等性的微小差异是由于复制操作导致您从 numpy 数据类型返回到普通 Python 浮点数,反之亦然。不过,我对此不是 100% 确定的。

现在优化讨论已经结束,让我们回到你的分箱方法。在您当前的实现中,您已将图像分割成正方形、不重叠的区域。对于本文的其余部分,这些子矩阵不必是正方形的,它们可以是矩形的(如果可以更改图像的纵横比)并且结论仍然有效。因此,在每个子矩阵中,您都取行均值,然后取结果列向量的均值。可以很容易地在数学上证明这与对整个子矩阵取平均值相同。这是个好消息,因为这意味着在上面显示的 strided_rescale 函数中,您可以简单地将 return 语句替换为:

return gstr.mean(axis=(-2,-1))

这会给你另一个(小)速度提升。

我认为使用 scipy.misc.imresize 的建议非常好,直到我在 ndarrays 上尝试使用 dtype != np.uint8。即使这样,mode 参数也必须正确选择,而且它似乎只占用了子矩阵的左上角:

In [39]: a = np.arange(16, dtype=np.uint8).reshape(4,4)

In [40]: a
Out[40]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]], dtype=uint8)

In [41]: imresize(a, (2,2), mode='P')
Out[41]: 
array([[ 0,  2],
       [ 8, 10]], dtype=uint8)

In [42]: imresize(a, (2,2), mode='L')
Out[42]: 
array([[0, 1],
       [6, 7]], dtype=uint8)

这可能不是您所追求的。所以 stride_tricks 非常适合实际的分箱。如果您希望 平滑 调整大小行为(例如,通过使用样条插值),您将寻找 Python 图像库和所有在后台使用它的函数,例如OpenCV,它还提供summarized in this post 的调整大小行为。

【讨论】:

  • 非常感谢 - 这些都是我很好奇的技巧!
猜你喜欢
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多