web-dev-qa-db-ja.com

Python)で最速の2D畳み込みまたは画像フィルター

何人かのユーザーが、numpyまたはscipyでの画像畳み込みの速度またはメモリ消費について質問しています[ 12 、、 4 ]。回答とNumpyの使用経験から、これはMatlabやIDLと比較してnumpyの大きな欠点である可能性があると思います。

これまでのところ、全体的な質問に対応している回答はないため、「Pythonで2D畳み込みを計算するための最速の方法は何ですか?」です。一般的なpythonモジュールは公正なゲームです:numpy、scipy、およびPIL(その他?)。比較を難しくするために、次のルールを提案したいと思います。

  1. 入力行列は、それぞれ2048x2048と32x32です。
  2. 単精度または倍精度の浮動小数点はどちらも許容されます。
  3. 入力行列を適切な形式に変換するために費やされた時間はカウントされません。畳み込みステップだけです。
  4. 入力行列を出力に置き換えることは許容されます(pythonライブラリはそれをサポートしていますか?)
  5. 直接DLL一般的なCライブラリへの呼び出しは問題ありません--lapackまたはscalapack
  6. PyCUDAはすぐに出ます。カスタムGPUハードウェアを使用することは公平ではありません。
20
Carl F.

それは本当にあなたが何をしたいかに依存します...多くの場合、あなたは完全に一般的な(読む:遅い)2D畳み込みを必要としません...(すなわち、フィルターが分離可能である場合、代わりに2つの1D畳み込みを使用します...これがさまざまなscipy.ndimage.gaussianscipy.ndimage.uniform、一般的なn-D畳み込みとして実装されたものよりもはるかに高速です。)

とにかく、比較のポイントとして:

t = timeit.timeit(stmt='ndimage.convolve(x, y, output=x)', number=1,
setup="""
import numpy as np
from scipy import ndimage
x = np.random.random((2048, 2048)).astype(np.float32)
y = np.random.random((32, 32)).astype(np.float32)
""")
print t

これは私のマシンでは6.9秒かかります...

これをfftconvolveと比較してください

t = timeit.timeit(stmt="signal.fftconvolve(x, y, mode='same')", number=1,
setup="""
import numpy as np
from scipy import signal
x = np.random.random((2048, 2048)).astype(np.float32)
y = np.random.random((32, 32)).astype(np.float32)
""")
print t

これには約10.8秒かかります。ただし、入力サイズが異なると、fftを使用して畳み込みを行う方がかなり高速になる可能性があります(現時点では、良い例を思い付くことができないようですが...)。

9
Joe Kington

私のマシンでは、FFTを使用した手作りの巡回畳み込みが断食されているようです。

import numpy
x = numpy.random.random((2048, 2048)).astype(numpy.float32)
y = numpy.random.random((32, 32)).astype(numpy.float32)
z = numpy.fft.irfft2(numpy.fft.rfft2(x) * numpy.fft.rfft2(y, x.shape))

これは巡回畳み込みであるため、エッジに近い領域を他の方法とは異なる方法で処理する可能性があることに注意してください。

12
Sven Marnach

私もこれでいくつかの実験をしました。私の推測では、SciPyコンボリューションは計算を高速化するためにBLASライブラリを使用していません。 BLASを使用して、MATLABと同等の速度の2D畳み込みをコーディングすることができました。それはもっと手間がかかりますが、最善の策はC++で畳み込みを再コーディングすることです。

これがループのタイトな部分です(奇妙な()ベースの配列参照を許してください。これはMATLAB配列の便利なクラスです)重要な部分は、画像を反復処理せず、フィルターを反復処理してBLASを使用することです。通常、画像はフィルターよりもはるかに大きいため、画像を繰り返し処理します。

for(int n = 0; n < filt.numCols; n++)
  {
    for(int m = 0; m < filt.numRows; m++)
    {
      const double filt_val = filt(filt.numRows-1-m,filt.numCols-1-n);
      for (int i =0; i < diffN; i++)
      {
        double *out_ptr = &outImage(0,i);
        const double *im_ptr = &image(m,i+n);
        cblas_daxpy(diffM,filt_val,im_ptr, 1, out_ptr,1);

      }
   }
 }
4
Marshall

アプリケーションで畳み込み速度を改善しようとしていますが、signal.correlateの約20倍遅いsignal.correlate2dを使用していますが、入力行列は小さくなっています(27x27 and 5x5)。 2018年の時点で、これは実際の質問で指定されたマトリックスについて自分のマシン(Dell Inspiron 13、Core i5)で観察したものです。

OpenCVは最善を尽くしましたが、「モード」オプションが指定されていないという注意点があります。入力と出力は同じサイズです。

>>> img= np.random.Rand(2048,2048)
>>> kernel = np.ones((32,32), dtype=np.float)
>>> t1= time.time();dst1 = cv2.filter2D(img,-1,kernel);print(time.time()-t1)
0.208490133286
>>> t1= time.time();dst2 = signal.correlate(img,kernel,mode='valid',method='fft');print(time.time()-t1)
0.582989931107
>>> t1= time.time();dst3 = signal.convolve2d(img,kernel,mode='valid');print(time.time()-t1)
11.2672450542
>>> t1= time.time();dst4 = signal.correlate2d(img,kernel,mode='valid');print(time.time()-t1)
11.2443971634
>>> t1= time.time();dst5 = signal.fftconvolve(img,kernel,mode='valid');print(time.time()-t1)
0.581533193588
0
Ruthvik Vaila

Scipyには、1Dおよび2D信号に使用できる関数 fftconvolve があります。

from scipy import signal
from scipy import misc
import numpy as np
import matplotlib.pyplot as plt

face = misc.face(gray=True)
kernel = np.outer(signal.gaussian(70, 8), signal.gaussian(70, 8))
blurred = signal.fftconvolve(face, kernel, mode='same')

fig, (ax_orig, ax_kernel, ax_blurred) = plt.subplots(3, 1, figsize=(6, 15))
ax_orig.imshow(face, cmap='gray')
ax_orig.set_title('Original')
ax_orig.set_axis_off()
ax_kernel.imshow(kernel, cmap='gray')
ax_kernel.set_title('Gaussian kernel')
ax_kernel.set_axis_off()
ax_blurred.imshow(blurred, cmap='gray')
ax_blurred.set_title('Blurred')
ax_blurred.set_axis_off()
fig.show()