使用numpy快速降低图像分辨率的Python代码

2024-09-30 12:35:09 发布

您现在位置:Python中文网/ 问答频道 /正文

下面是一个代码,它通过将小像素划分为较大的像素来降低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

这是一个结果

^{pr2}$

test image

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

test image reduced

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

Tags: 代码numpyreduceimgbinnporder像素
1条回答
网友
1楼 · 发布于 2024-09-30 12:35:09

我首先要说的是,你的“只装箱”方式似乎很不寻常,我想这正是@ljetibo在评论中所指的。在“优化”讨论之后,我将回到这个话题。在

首先,您可以通过去掉对np.copy的多余调用来稍微改进代码,因为您只需将重新绑定到传入的img的视图。ravel操作将返回一个副本,除非图像形状是binning因子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..

时序考虑表明,这通常比原始方法快一点,随着装箱因子的增加,性能得到改善:

^{2}$

我相信在数组相等性方面观察到的细微差异是由于复制操作,即从numpy数据类型返回到普通Python浮点,反之亦然。但我不能百分之百肯定。在

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

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

这将给你另一个(小)提速。在

我认为使用^{}是一个非常好的建议,直到我用一个dtype在ndarrays上尝试过了!= 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_技巧对实际的binning很有效。如果您希望平滑调整大小的行为(例如,通过使用样条插值),您将看到Python图像库和所有在幕后使用它的函数,例如OpenCV,它也提供了summarized in this post的大小调整行为。在

相关问题 更多 >

    热门问题