Numpy中的内存使用

5 投票
2 回答
1899 浏览
提问于 2025-04-18 09:18

我在我的Mac上写了一个程序,但在我的树莓派上运行时出现了内存不足的问题(MemoryError)。

这个程序主要是处理图像,它会对一个640x480的uint8图像进行卷积,卷积的另一个矩阵大小是它的两倍。

我估计内存的使用情况是:

初始图像:

640 x 480 x 8 bits / 8 bits / 1024 bytes = 300 kb

复杂矩阵:

640 x 480 x 2^2 x 128 bits / 8 bits / 1024^2 = 18.75 MB

假设它需要在内存中保存两到三个这样的矩阵副本,那应该占用的内存不多,可能不到100MB。但不幸的是,它似乎耗尽了330MB的全部可用内存(Python运行时也需要加载到这个空间中)。

  1. 我的分析正确吗?
  2. 有没有什么建议可以帮助我更好地管理Python中的内存?

更新:

根据下面的建议,我做了一些内存分析,确实是fftconvolve导致了内存使用的激增,具体情况如下:

行号 内存使用 增量 行内容

65   86.121 MiB    0.000 MiB     @profile
66                               def iriscode(self):
67   86.121 MiB    0.000 MiB       img = self.polar
68
69   86.379 MiB    0.258 MiB       pupil_curve = find_max(img[0:40])
70   86.379 MiB    0.000 MiB       blur = cv2.GaussianBlur(self.polar, (9, 9), 0)
71   76.137 MiB  -10.242 MiB       iris_fft = fit_iris_fft(radial_diff(blur[50:,:])) + 50
72
73   76.160 MiB    0.023 MiB       img = warp(img, iris_fft, pupil_curve)
74                                 # cv2.imshow("mask",np.uint8(ma.getmaskarray(img))*255)
75
76                                 global GABOR_FILTER
77  262.898 MiB  186.738 MiB       output = signal.fftconvolve(GABOR_FILTER, img, mode="valid")

不过,这个增加的幅度让我感到惊讶。有什么想法可以帮助我减少这个内存使用吗?我尝试用complex64代替complex128,但内存使用情况没有变化。

2 个回答

1

我会按以下顺序尝试这些方法:

  1. 使用普通的卷积方法,而不是fftconvolve。
  2. 使用scipy.weave,自己实现卷积操作(这样可以避免numpy的内存管理问题)。
4

要理解发生了什么,你可以查看 fftconvolve 的源代码,在这里

傅里叶变换卷积的基本概念是,时间域中的卷积实际上在频率域中就是逐元素相乘。但是因为你使用的是快速傅里叶变换(FFT),它会把你的函数当作周期性的来看待,也就是说,卷积核在边缘会“包裹”起来。因此,为了得到正确的结果,数组会用零填充到一个共同的形状,在你的情况下,这个形状是 (1280+640-1, 960+480-1) = (1919, 1439)。为了加快计算,这个形状会进一步扩展到下一个只有 2、3 或 5 作为质因数的更大数字,这在你的情况下变成了 (1920, 1440)。对于一个复杂的数组,这个大小占用的内存是 1920 * 1440 * 16 / 2**20 = 42 MiB

你将会有两个这样的数组,分别对应你的两个输入,再加上计算它们各自的 FFT 时产生的两个数组,还有在它们相乘时产生的一个数组,以及在计算它们的逆 FFT 以得到卷积时产生的另一个数组。

并不清楚所有这些数组是否会同时存在,因为有些可能会在过程中被垃圾回收,但至少在某个时刻会有 3 个,可能会有 4 个。再加上 FFT 计算的额外开销,你的 186 MiB 就解释清楚了。

你可以尝试非 FFT 的卷积,这样就不需要那么多的填充。你也可以尝试稍微优化一下 scipy.signal.fftconvolve 中的代码。把这个 else替换为:

else:
    ret = fftn(in1, fshape)
    ret *= fftn(in2, fshape)
    ret = ifftn(ret)[fslice].copy()

这样可以消除一个中间副本,给你额外的 40 MiB,这可能会解决你的问题。

撰写回答