Lucas Kanade的Python NumPy实现使用了大量内存。

11

我正在使用Lucas-Kanade方法编写光流脚本,作为大学项目。虽然它可以很好地工作,但是有些事情我无法理解。它在开始时使用少量内存,但这个数量每秒钟迅速增加。当它计算一部480p电影的一个帧的光流时,它使用约1GB的内存。当它达到1.9GB时,它突然停止并保持在那里,即使留了几个小时也是如此。

我尝试在另一台PC上运行脚本,结果只使用1GB。

这种行为真的很奇怪,因为根据我的计算,它应该使用远少于100MB。

对我来说最令人惊讶的事情是,在脚本计算一帧后,我打印出垃圾收集器正在监视的对象数量,大约为200万个,然后强制执行回收后再次打印,结果完全相同。我等待第二帧被计算(同时内存使用量增加了约1GB),脚本打印出GC监视的对象数量 - 数字非常接近200万。那么这意味着什么?numpy是用C编写的,有内存泄漏吗?

我真的很想理解这种行为。

这是代码:http://pastebin.com/WSi7akY4


你在64位和32位的电脑上都试过这个程序吗?这可能部分地解释了两台电脑之间0.9G的差异。 - Bakuriu
1个回答

22
虽然它没有解释你的记忆问题,但是可以说你的实现并不太好。你不仅没有充分利用numpy的能力,而且算法的流程也不能很好地避免重复计算。我认为你只是耗尽了系统资源,不是因为python或numpy出了问题,而是因为你创建了太多不必要的列表嵌套列表嵌套列表...
在查看Lucas-Kanade算法的维基百科条目后,我重新编写了你的主函数如下:
def lucas_kanade_np(im1, im2, win=2):
    assert im1.shape == im2.shape
    I_x = np.zeros(im1.shape)
    I_y = np.zeros(im1.shape)
    I_t = np.zeros(im1.shape)
    I_x[1:-1, 1:-1] = (im1[1:-1, 2:] - im1[1:-1, :-2]) / 2
    I_y[1:-1, 1:-1] = (im1[2:, 1:-1] - im1[:-2, 1:-1]) / 2
    I_t[1:-1, 1:-1] = im1[1:-1, 1:-1] - im2[1:-1, 1:-1]
    params = np.zeros(im1.shape + (5,)) #Ix2, Iy2, Ixy, Ixt, Iyt
    params[..., 0] = I_x * I_x # I_x2
    params[..., 1] = I_y * I_y # I_y2
    params[..., 2] = I_x * I_y # I_xy
    params[..., 3] = I_x * I_t # I_xt
    params[..., 4] = I_y * I_t # I_yt
    del I_x, I_y, I_t
    cum_params = np.cumsum(np.cumsum(params, axis=0), axis=1)
    del params
    win_params = (cum_params[2 * win + 1:, 2 * win + 1:] -
                  cum_params[2 * win + 1:, :-1 - 2 * win] -
                  cum_params[:-1 - 2 * win, 2 * win + 1:] +
                  cum_params[:-1 - 2 * win, :-1 - 2 * win])
    del cum_params
    op_flow = np.zeros(im1.shape + (2,))
    det = win_params[...,0] * win_params[..., 1] - win_params[..., 2] **2
    op_flow_x = np.where(det != 0,
                         (win_params[..., 1] * win_params[..., 3] -
                          win_params[..., 2] * win_params[..., 4]) / det,
                         0)
    op_flow_y = np.where(det != 0,
                         (win_params[..., 0] * win_params[..., 4] -
                          win_params[..., 2] * win_params[..., 3]) / det,
                         0)
    op_flow[win + 1: -1 - win, win + 1: -1 - win, 0] = op_flow_x[:-1, :-1]
    op_flow[win + 1: -1 - win, win + 1: -1 - win, 1] = op_flow_y[:-1, :-1]
    return op_flow

它使用两个嵌套调用np.cumsum和排斥-包容原理来计算窗口参数。由于每个点要解决的方程组只有2x2,因此它使用克莱默法则对其进行向量化求解。
为了比较,我将您的lucas_kanade函数重命名为lucas_kanade_op,并对最后一个语句进行了单个更改,以便返回一个numpy数组:
def lucas_kanade_op(im1, im2, win=2) :
    ...
    return np.array(opfl)

我记录了两种方法的时间(并检查它们都输出相同的结果),毫不意外,利用numpy可以大幅提升效率:
rows, cols = 100, 100
im1 = np.random.rand(rows, cols)
im2 = np.random.rand(rows, cols)
ans1 = lucas_kanade_op(im1, im2)
ans2 = lucas_kanade_np(im1, im2)
np.testing.assert_almost_equal(ans1,ans2)

import timeit
print 'op\'s time:', timeit.timeit('lucas_kanade_op(im1, im2)',
                                   'from __main__ import lucas_kanade_op, im1, im2',
                                   number=1)
print 'np\'s time:', timeit.timeit('lucas_kanade_np(im1, im2)',
                                   'from __main__ import lucas_kanade_np, im1, im2',
                                   number=1)

这将打印输出:
op's time: 5.7419579567
np's time: 0.00256002154425

那么对于一个较小的100x100像素的图像,速度提高了2000倍。我没有敢测试您的方法是否适用于全尺寸480p图像,但是上面的函数可以处理大约每秒5个随机854x480数组的计算,而且没有任何问题。
我建议您按照上述方式重写代码,充分利用numpy。将您的完整代码发布到Code Review可能是一个好的起点。但是,如果您的代码本身效率低下,那么寻找杂散对象的引用就没有什么意义了!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接