下面的代码通过将小像素合并为更大的像素来降低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
答案 0 :(得分:1)
我首先要提到的是,只有的分区方式似乎相当不寻常,这就是@ljetibo在评论中提到的,我猜想。在"优化"之后我会回到这里。交谈。
首先,您可以通过删除对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 float,反之亦然。我对此并不是100%肯定。
现在优化讨论结束了,让我们回到你的分箱方法。使用当前的实现,您可以将图像拆分为方形,非重叠区域。这些子矩阵对于本故事的其余部分不一定是正方形的,它们可以是矩形的(如果图像的纵横比可以改变)并且结论仍然有效。因此,在每个子矩阵中,您采用行方式,然后取得结果列向量的平均值。它可以很容易地在数学上显示,这与在整个子矩阵上取平均值相同。这是个好消息,因为它意味着在上面显示的strided_rescale
函数中,您只需将return
语句替换为:
return gstr.mean(axis=(-2,-1))
这会给你另一个(小)速度提升。
我认为使用scipy.misc.imresize
的建议非常好,直到我在ddarrays上使用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。