web-dev-qa-db-ja.com

Matlabのblkproc(blockproc)関数と同様に、ブロック内のnumpy配列を効率的に処理するにはどうすればよいですか?

画像を効率的に小さな領域に分割し、各領域を個別に処理してから、各プロセスの結果を単一の処理済み画像に再アセンブルするための優れたアプローチを探しています。 Matlabには、 blkproc (新しいバージョンのMatlabでは blockproc に置き換えられました)というツールがありました。

理想的な世界では、関数またはクラスは入力行列の分割間のオーバーラップもサポートします。 Matlabヘルプでは、blkprocは次のように定義されています。

B = blkproc(A、[m n]、[mborder nborder]、fun、...)

  • Aは入力行列です。
  • [mn]はブロックサイズです
  • [mborder、nborder]は、境界領域のサイズです(オプション)
  • funは各ブロックに適用する機能です

私はアプローチをまとめましたが、それは不器用だと思います。もっと良い方法があるに違いありません。私自身の恥ずかしさの危険を冒して、ここに私のコードがあります:


import numpy as np

def segmented_process(M, blk_size=(16,16), overlap=(0,0), fun=None):
    rows = []
    for i in range(0, M.shape[0], blk_size[0]):
        cols = []
        for j in range(0, M.shape[1], blk_size[1]):
            cols.append(fun(M[i:i+blk_size[0], j:j+blk_size[1]]))
        rows.append(np.concatenate(cols, axis=1))
    return np.concatenate(rows, axis=0)

R = np.random.Rand(128,128)
passthrough = lambda(x):x
Rprime = segmented_process(R, blk_size=(16,16), 
                           overlap=(0,0), 
                           fun=passthrough)

np.all(R==Rprime)
27
Carl F.

ブロックを操作する別の(ループのない)方法の例を次に示します。

import numpy as np
from numpy.lib.stride_tricks import as_strided as ast

A= np.arange(36).reshape(6, 6)
print A
#[[ 0  1  2  3  4  5]
# [ 6  7  8  9 10 11]
# ...
# [30 31 32 33 34 35]]

# 2x2 block view
B= ast(A, shape= (3, 3, 2, 2), strides= (48, 8, 24, 4))
print B[1, 1]
#[[14 15]
# [20 21]]

# for preserving original shape
B[:, :]= np.dot(B[:, :], np.array([[0, 1], [1, 0]]))
print A
#[[ 1  0  3  2  5  4]
# [ 7  6  9  8 11 10]
# ...
# [31 30 33 32 35 34]]
print B[1, 1]
#[[15 14]
# [21 20]]

# for reducing shape, processing in 3D is enough
C= B.reshape(3, 3, -1)
print C.sum(-1)
#[[ 14  22  30]
# [ 62  70  78]
# [110 118 126]]

したがって、単にmatlab機能をnumpyにコピーしようとするだけでは、すべての方法で続行するのが最善の方法ではありません。時々、「オフザハット」の考え方が必要になります。

警告
一般に、ストライドトリックに基づく実装may(ただし、必ずしもそうする必要はありません)、パフォーマンスが低下します。したがって、あらゆる方法でパフォーマンスを測定する準備をしてください。いずれにせよ、最初に必要な機能(または簡単に適応するために十分に類似したもの)がすべてnumpyまたはscipyに実装されているかどうかを確認することをお勧めします。

更新
ここではmagicに実際のstridesが含まれていないことに注意してください。そのため、適切な2Dnumpy- arrayのblock_viewを取得する簡単な関数を提供します。だからここに行きます:

from numpy.lib.stride_tricks import as_strided as ast

def block_view(A, block= (3, 3)):
    """Provide a 2D block view to 2D array. No error checking made.
    Therefore meaningful (as implemented) only for blocks strictly
    compatible with the shape of A."""
    # simple shape and strides computations may seem at first strange
    # unless one is able to recognize the 'Tuple additions' involved ;-)
    shape= (A.shape[0]/ block[0], A.shape[1]/ block[1])+ block
    strides= (block[0]* A.strides[0], block[1]* A.strides[1])+ A.strides
    return ast(A, shape= shape, strides= strides)

if __name__ == '__main__':
    from numpy import arange
    A= arange(144).reshape(12, 12)
    print block_view(A)[0, 0]
    #[[ 0  1  2]
    # [12 13 14]
    # [24 25 26]]
    print block_view(A, (2, 6))[0, 0]
    #[[ 0  1  2  3  4  5]
    # [12 13 14 15 16 17]]
    print block_view(A, (3, 12))[0, 0]
    #[[ 0  1  2  3  4  5  6  7  8  9 10 11]
    # [12 13 14 15 16 17 18 19 20 21 22 23]
    # [24 25 26 27 28 29 30 31 32 33 34 35]]
21
eat

スライス/ビューで処理します。連結は非常に高価です。

for x in xrange(0, 160, 16):
    for y in xrange(0, 160, 16):
        view = A[x:x+16, y:y+16]
        view[:,:] = fun(view)
11
Paul

私は両方の入力と元のアプローチを取り、結果を比較しました。 @eatが正しく指摘しているように、結果は入力データの性質によって異なります。驚くべきことに、いくつかの例では、連結はビュー処理を打ち負かします。それぞれの方法にはスイートスポットがあります。これが私のベンチマークコードです:

import numpy as np
from itertools import product

def segment_and_concatenate(M, fun=None, blk_size=(16,16), overlap=(0,0)):
    # truncate M to a multiple of blk_size
    M = M[:M.shape[0]-M.shape[0]%blk_size[0], 
          :M.shape[1]-M.shape[1]%blk_size[1]]
    rows = []
    for i in range(0, M.shape[0], blk_size[0]):
        cols = []
        for j in range(0, M.shape[1], blk_size[1]):
            max_ndx = (min(i+blk_size[0], M.shape[0]),
                       min(j+blk_size[1], M.shape[1]))
            cols.append(fun(M[i:max_ndx[0], j:max_ndx[1]]))
        rows.append(np.concatenate(cols, axis=1))
    return np.concatenate(rows, axis=0)


from numpy.lib.stride_tricks import as_strided
def block_view(A, block= (3, 3)):
    """Provide a 2D block view to 2D array. No error checking made.
    Therefore meaningful (as implemented) only for blocks strictly
    compatible with the shape of A."""
    # simple shape and strides computations may seem at first strange
    # unless one is able to recognize the 'Tuple additions' involved ;-)
    shape= (A.shape[0]/ block[0], A.shape[1]/ block[1])+ block
    strides= (block[0]* A.strides[0], block[1]* A.strides[1])+ A.strides
    return as_strided(A, shape= shape, strides= strides)

def segmented_stride(M, fun, blk_size=(3,3), overlap=(0,0)):
    # This is some complex function of blk_size and M.shape
    stride = blk_size
    output = np.zeros(M.shape)

    B = block_view(M, block=blk_size)
    O = block_view(output, block=blk_size)

    for b,o in Zip(B, O):
        o[:,:] = fun(b);

    return output

def view_process(M, fun=None, blk_size=(16,16), overlap=None):
    # truncate M to a multiple of blk_size
    from itertools import product
    output = np.zeros(M.shape)

    dz = np.asarray(blk_size)
    shape = M.shape - (np.mod(np.asarray(M.shape), 
                          blk_size))
    for indices in product(*[range(0, stop, step) 
                        for stop,step in Zip(shape, blk_size)]):
        # Don't overrun the end of the array.
        #max_ndx = np.min((np.asarray(indices) + dz, M.shape), axis=0)
        #slices = [slice(s, s + f, None) for s,f in Zip(indices, dz)]
        output[indices[0]:indices[0]+dz[0], 
               indices[1]:indices[1]+dz[1]][:,:] = fun(M[indices[0]:indices[0]+dz[0], 
               indices[1]:indices[1]+dz[1]])

    return output

if __name__ == "__main__":
    R = np.random.Rand(128,128)
    squareit = lambda(x):x*2

    from timeit import timeit
    t ={}
    kn = np.array(list(product((8,16,64,128), 
                               (128, 512, 2048, 4096))  ) )

    methods = ("segment_and_concatenate", 
               "view_process", 
               "segmented_stride")    
    t = np.zeros((kn.shape[0], len(methods)))

    for i, (k, N) in enumerate(kn):
        for j, method in enumerate(methods):
            t[i,j] = timeit("""Rprime = %s(R, blk_size=(%d,%d), 
                          overlap = (0,0), 
                          fun = squareit)""" % (method, k, k),
                   setup="""
from segmented_processing import %s
import numpy as np
R = np.random.Rand(%d,%d)
squareit = lambda(x):x**2""" % (method, N, N),
number=5
)
        print "k =", k, "N =", N #, "time:", t[i]
        print ("    Speed up (view vs. concat, stride vs. concat): %0.4f, %0.4f" % (
                       t[i][0]/t[i][1], 
                       t[i][0]/t[i][2]))

そしてここに結果があります:

A comparison of three methods for processing large matrices in a block-wise fashion in numpy セグメント化されたストライド方式は、ブロックサイズが小さい場合に3〜4倍勝つことに注意してください。大きなブロックサイズ(128 x 128)と非常に大きなマトリックス(2048 x 2048以上)でのみ、ビュー処理アプローチが勝ちますが、その割合はごくわずかです。ベイクオフに基づくと、@ eatにチェックマークが付いているようです。良い例をありがとう!

7
Carl F.

ゲームに少し遅れますが、これは重複するブロックを行います。ここではまだ行っていませんが、ウィンドウをシフトするためのステップサイズに簡単に適応できると思います。

from numpy.lib.stride_tricks import as_strided
def rolling_block(A, block=(3, 3)):
    shape = (A.shape[0] - block[0] + 1, A.shape[1] - block[1] + 1) + block
    strides = (A.strides[0], A.strides[1]) + A.strides
    return as_strided(A, shape=shape, strides=strides)
3
hgcrpd

ゲームの後半でも。ボブと呼ばれるスイスの画像処理パッケージがあります: https://www.idiap.ch/software/bob/docs/releases/last/sphinx/html/index.html それは=を持っていますpythonコマンドbob.ip.blockの説明: https://www.idiap.ch/software/bob/docs/releases/last/sphinx/html/ip/generated/ bob.ip.block.html#bob.ip.block これは、Matlabコマンド 'blockproc'が実行するすべてのことを実行しているように見えます。テストしていません。

画像のDCT係数を抽出または変更するための「ブロック」機能を組み込んだ興味深いコマンドbob.ip.DCTFeaturesもあります。

3
RobBW

私はこのチュートリアルを見つけました-最終的なソースコードはまさに望ましい機能を提供します!それはどんな次元でも機能するはずです(私はそれをテストしませんでした) http://www.johnvinyard.com/blog/?p=268

ソースコードの最後にある「フラット化」オプションは少しバグがあるようですが。それにもかかわらず、非常に素晴らしいソフトウェアです!

0
muuh