2017-05-19 13 views
6

ディレクトリからnumpyの配列に画像を読み込むための最も速いアプローチを見つけようとしています。私の最終目標は、これらのすべての画像からのピクセルの最大、最小、およびn番目のパーセンタイルなどの統計を計算することです。 .max.minなどの組み込み配列メソッドとnp.percentile関数を使用できるので、すべての画像のピクセルが1つの大きな数値配列にあるときは、これは簡単で高速です。何千枚もの画像を1つの大きなnumpyの配列に読み込む最も速いアプローチ

以下は、25枚のTIFF画像(512x512ピクセル)のタイミング例です。これらのベンチマークは、ジュピターノートで%%timitを使用したものです。その違いはあまりにも小さいので、わずか25枚の画像には実用上の意味がありませんが、私は将来何千枚もの画像を読むつもりです。上記のリストと辞書アプローチ辞書

%%timeit  
imgs = {}  
img_path = '/path/to/imgs/'  
for img in os.listdir(img_path):  
    imgs[num] = io.imread(os.path.join(img_path, img))  
## 33.3 ms ± 402 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 

を使用してリスト

%%timeit 
imgs = []  
img_path = '/path/to/imgs/' 
for img in os.listdir(img_path):  
    imgs.append(io.imread(os.path.join(img_path, img)))  
## 32.2 ms ± 355 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 
  • への追加

    # Imports 
    import os 
    import skimage.io as io 
    import numpy as np 
    
    1. は、私はループを交換しようとしましたsimによるそれぞれの理解結果は時間的に似ています。また、辞書キーを事前に割り当ててみましたが、時間に大きな違いはありませんでした。リストから大きな配列に画像を取得するには、np.concatenate(imgs)を使用します。これには〜1 msしかかかりません。最初の次元Iが最初に考え

      %%timeit  
      imgs = np.ndarray((512*25,512), dtype='uint16')  
      img_path = '/path/to/imgs/'  
      for num, img in enumerate(os.listdir(img_path)):  
          imgs[num*512:(num+1)*512, :] = io.imread(os.path.join(img_path, img))  
      ## 33.5 ms ± 804 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 
      
    2. 第三の次元に沿ってnumpyのを事前割当て

      %%timeit  
      imgs = np.ndarray((512,512,25), dtype='uint16')  
      img_path = '/path/to/imgs/'  
      for num, img in enumerate(os.listdir(img_path)):  
          imgs[:, :, num] = io.imread(os.path.join(img_path, img))  
      ## 71.2 ms ± 2.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 
      

    沿ってnumpyの配列を事前割当て

  • ループ内に動的変数展開がないので、numpyの事前割り振り手法は高速になりますが、そうではありません。最も直感的なアプローチは最後のものです。各イメージは配列の3番目の軸に沿って別々の次元を占めますが、これも最も遅いです。取られた追加の時間は、事前割り振り自体に起因するものではなく、約1msしかかかりません。

    I持ってこれに関する3つの質問:

    1. なぜnumpyの事前割り当ては、辞書とリストのソリューションよりも速くない近づいているのですか?
    2. 何千もの画像を1つの大量の配列に読み込む最速の方法はどれですか?
    3. numpyやscikit-imageの外側を見ると、イメージを読み込むためのさらに高速なモジュールが得られるでしょうか?私はplt.imread()を試しましたが、scikit-image.ioモジュールは高速です。
  • +1

    '(25、512、512)'の配列を初期化しようとしましたか?最初の次元は外側の次元です。最初のリストアプローチからの 'np.array(imgs)'はこの形を作り出します。その33msの大部分は、ストレージではなく負荷です。それをテストするには、配列を累積することなくロードを試みてください。 – hpaulj

    +0

    ありがとう@hpaulj!あなたの外の寸法についてのヒントと、私の費やした時間のほとんどが頭上からのものであることが役立っていました。私は300の高解像度のTIFF(1024x1024ピクセル)を試して、numpyの外側の次元のアプローチ([300、1024、1024]または[1024,300,1024]のいずれか)が今や最速(〜1秒)です。 2番目はリストと辞書の解(〜1.7s)で、numpyの内部(?)の次元[1024,1024,300]は最後に死んでいます(〜4.6s)。回答を追加すると、これらのベンチマークを追加して受け入れることができます。 –

    +0

    @hpauljリンクを追加して、最初の(そして2番目の)次元が外側の次元であることをもう少し詳しく説明すると、本当にありがたいです。私は検索に関連するものは見つけられません。 –

    答えて

    1

    パートA:最後の軸に沿って、これらの要素を格納する際の要素はnumpyのアレイの行優先順に格納されている方法で行くnumpyのアレイ

    へのアクセスと割り当て、あなたが正しいことをやっています反復ごとに。これらは連続したメモリ位置を占有するため、値へのアクセスと割り当てに最も効率的です。したがって、np.ndarray((512*25,512), dtype='uint16')またはnp.ndarray((25,512,512), dtype='uint16')のような初期化は、コメントにも記載されているように最高の動作をします。しかし、これらのタイミングが開始時に提案された性能理論を確認

    In [45]: %timeit app1() 
        ...: %timeit app2() 
        ...: %timeit app3() 
        ...: %timeit app4() 
        ...: 
    10 loops, best of 3: 28.2 ms per loop 
    100 loops, best of 3: 2.04 ms per loop 
    100 loops, best of 3: 2.02 ms per loop 
    100 loops, best of 3: 2.36 ms per loop 
    

    -

    N = 512 
    n = 25 
    a = np.random.randint(0,255,(N,N)) 
    
    def app1(): 
        imgs = np.empty((N,N,n), dtype='uint16') 
        for i in range(n): 
         imgs[:,:,i] = a 
         # Storing along the first two axes 
        return imgs 
    
    def app2(): 
        imgs = np.empty((N*n,N), dtype='uint16') 
        for num in range(n):  
         imgs[num*N:(num+1)*N, :] = a 
         # Storing along the last axis 
        return imgs 
    
    def app3(): 
        imgs = np.empty((n,N,N), dtype='uint16') 
        for num in range(n):  
         imgs[num,:,:] = a 
         # Storing along the last two axes 
        return imgs 
    
    def app4(): 
        imgs = np.empty((N,n,N), dtype='uint16') 
        for num in range(n):  
         imgs[:,num,:] = a 
         # Storing along the first and last axes 
        return imgs 
    

    タイミング -

    タイミングでテスト用のfuncsとしてそれらをコンパイルし、代わりに画像のランダムアレイに供給した後私は最後のセットアップのタイミングがapp3app1のタイミングの間にあることを期待しましたが、アクセスと割り当てのために最後から最初の軸に向かう影響は線形ではないかもしれません。これに関するより多くの調査は興味深い(follow up question here)かもしれません。概略的claify我々はx(画像1)とo(画像2)で表される画像アレイを、格納された、我々が持っているであろうことを考慮することが

    App1の:

    [[[x 0] 
        [x 0] 
        [x 0] 
        [x 0] 
        [x 0]] 
    
    [[x 0] 
        [x 0] 
        [x 0] 
        [x 0] 
        [x 0]] 
    
    [[x 0] 
        [x 0] 
        [x 0] 
        [x 0] 
        [x 0]]] 
    

    したがって、内メモリ空間では、行優先順位の後に[x,o,x,o,x,o..]となります。

    App2が:

    [[x x x x x] 
    [x x x x x] 
    [x x x x x] 
    [o o o o o] 
    [o o o o o] 
    [o o o o o]] 
    

    したがって、メモリ空間に、それは次のようになります。[x,x,x,x,x,x...o,o,o,o,o..]

    App3起動:

    [[[x x x x x] 
        [x x x x x] 
        [x x x x x]] 
    
    [[o o o o o] 
        [o o o o o] 
        [o o o o o]]] 
    

    したがって、メモリ空間に、それは以前のものと同じになります。


    パートB:アレイを今すぐ

    としてディスクから画像を読み込み、画像を読み取る上の部分は、私はOpenCVののimreadは、はるかに高速であることを見てきました。試験として

    が、私はwikiページからモナリザのイメージをダウンロードし、画像読み取りのパフォーマンスをテストした - この場合

    import cv2 # OpenCV 
    
    In [521]: %timeit io.imread('monalisa.jpg') 
    100 loops, best of 3: 3.24 ms per loop 
    
    In [522]: %timeit cv2.imread('monalisa.jpg') 
    100 loops, best of 3: 2.54 ms per loop 
    
    +0

    返信いただきありがとうございます!あなたは '[x、o、x、o、x、o ...]を読むよりも、なぜ[x、x、x、... o、o、o、...] ] '? 'x'と' o'は同じ種類のオブジェクトで、ピクセルの強さの整数、右でしょうか? –

    +0

    また、 "繰り返しごとに最後の軸に沿ってそれらの要素を保存するときに正しいことをしています"あなたの例では、最も速いのは、最初の軸/次元、すなわち 'np.ndarray([x、y、z])'の 'x'に沿って要素を格納することです。あるいは、「要素」は別個の画像を参照していないのですか? –

    +0

    @JoelOstblom最初の質問に - 私はポストで指摘したように、また繰り返します: 'これらは連続したメモリ位置を占有し、値にアクセスして値を割り当てるためには最も効率的です。 '' x、x、... o、o、o、...] 'のためにimage1、すなわち 'x's'を最初に、次に' image2'、すなわち 'o's'を次に格納しているからです。 2番目のクエリに - コメント付きのコードを編集しました。 – Divakar

    3

    、ほとんどの時間は、ディスクからファイルを読み込む費やされる、と私はwouldnリストを作成する時間についてあまり心配しないでください。

    いずれの場合でも、ディスクから実際のイメージを読み込むオーバーヘッドなしに、メモリからオブジェクトを読み取るだけで、4つの方法を比較するスクリプトがあります。

    import numpy as np 
    import time 
    from functools import wraps 
    
    
    x, y = 512, 512 
    img = np.random.randn(x, y) 
    n = 1000 
    
    
    def timethis(func): 
        @wraps(func) 
        def wrapper(*args, **kwargs): 
         start = time.perf_counter() 
         r = func(*args, **kwargs) 
         end = time.perf_counter() 
         print('{}.{} : {} milliseconds'.format(func.__module__, func.__name__, (end - start)*1e3)) 
         return r 
        return wrapper 
    
    
    @timethis 
    def static_list(n): 
        imgs = [None]*n 
        for i in range(n): 
         imgs[i] = img 
        return imgs 
    
    
    @timethis 
    def dynamic_list(n): 
        imgs = [] 
        for i in range(n): 
         imgs.append(img) 
        return imgs 
    
    
    @timethis 
    def list_comprehension(n): 
        return [img for i in range(n)] 
    
    
    @timethis 
    def numpy_flat(n): 
        imgs = np.ndarray((x*n, y)) 
        for i in range(n): 
         imgs[x*i:(i+1)*x, :] = img 
    
    static_list(n) 
    dynamic_list(n) 
    list_comprehension(n) 
    numpy_flat(n) 
    

    結果は:

    __main__.static_list : 0.07004200006122119 milliseconds 
    __main__.dynamic_list : 0.10294799994881032 milliseconds 
    __main__.list_comprehension : 0.05021800006943522 milliseconds 
    __main__.numpy_flat : 309.80870099983804 milliseconds 
    

    は明らかにあなたの最善の策は、さらに(メモリから)1000枚の画像を読み取るためのnumpyの配列、そのわずか310ミリ秒を移入ししかし、リスト内包です。つまり、オーバーヘッドはディスクの読み込みになります。

    なぜnumpyが遅いのですか?

    numpyがメモリに配列を格納する方法です。リストをnumpy配列に変換するためにpythonリスト関数を変更すると、時間は似ています。

    修飾関数は値を返す:

    @timethis 
    def static_list(n): 
        imgs = [None]*n 
        for i in range(n): 
         imgs[i] = img 
        return np.array(imgs) 
    
    
    @timethis 
    def dynamic_list(n): 
        imgs = [] 
        for i in range(n): 
         imgs.append(img) 
        return np.array(imgs) 
    
    
    @timethis 
    def list_comprehension(n): 
        return np.array([img for i in range(n)]) 
    

    とタイミング結果:

    __main__.static_list : 303.32892100022946 milliseconds 
    __main__.dynamic_list : 301.86925499992867 milliseconds 
    __main__.list_comprehension : 300.76925699995627 milliseconds 
    __main__.numpy_flat : 305.9309459999895 milliseconds 
    

    だから、それはより多くの時間がかかることだけnumpyのものであり、それはアレイに一定の相対値でありますサイズ...

    関連する問題