11

大学のプロジェクトとして、ルーカスカナデ法を使ったオプティカルフロースクリプトに取り組んでいました。それはうまく機能しますが、私には理解できないことがあります。開始時に数MBのメモリを使用しますが、その量は毎秒急速に増加します。480pムービーの1フレームのOFを計算するまでに、約1GBを使用します。1.9GBに達すると、突然停止し、数時間放置してもそこに留まります。

別のPCでスクリプトを実行してみましたが、1GBしか使用していません。

私の計算によれば、これは本当に奇妙な動作です。使用するのは100MBよりはるかに少ないはずです。

私にとって最も驚いたのは、スクリプトが1フレームを計算した後、ガベージコレクターが監視しているオブジェクトの量を印刷し、それが約200万であり、コレクションを強制した後に再度印刷したことです。これはまったく同じでした。2番目のフレームが計算されるのを待って(その間にメモリ使用量が約1GB増加しました)、スクリプトはGCによって監視されているオブジェクトの数を出力しました。これは200万に近い数とまったく同じです。それで、それはどういう意味ですか?そのnumpyはCで書かれていて、メモリリークがありますか?

私はこの振る舞いを本当に理解したいと思います。

コードは次のとおりです:http://pastebin.com/WSi7akY4

4

1 に答える 1

21

それはあなたのメモリの問題を説明していませんが、あなたの実装は、控えめに言って、最適ではありません。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

2つのネストされた呼び出しnp.cumsumと除外包含原理を使用して、ウィンドウ化されたパラメーターを計算します。各点で解く連立方程式は2x2しかないため、クラメルの公式を使用して解きをベクトル化します。

比較のために、最後のステートメントを1回変更したlucas_kanade場合と同じように関数の名前lucas_kanade_opを変更して、numpy配列を返すようにしました。

def lucas_kanade_op(im1, im2, win=2) :
    ...
    return np.array(opfl)

私は両方のアプローチの時間を計り(そして両方が同じように出力することを確認しました)、驚くことではありませんでした。

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の画像の場合、これはx2000の速度向上です。フルサイズの480p画像に対するアプローチをあえてテストしませんでしたが、上記の関数は、ランダムな854x480配列で1秒あたり約5回の計算を処理でき、問題はありません。

numpyを最大限に活用して、上記で提案したのと同様の方法でコードを書き直すことをお勧めします。コードレビューに完全なコードを投稿することは、良い出発点になります。しかし、コードが最初から非常に非効率的である場合、オブジェクトへの漂遊参照を探す意味はありません。

于 2013-01-14T20:06:29.607 に答える