何人かのユーザーが、numpyまたはscipyでの画像畳み込みの速度またはメモリ消費について質問しています[ 1 、 2 、、 4 ]。回答とNumpyの使用経験から、これはMatlabやIDLと比較してnumpyの大きな欠点である可能性があると思います。
これまでのところ、全体的な質問に対応している回答はないため、「Pythonで2D畳み込みを計算するための最速の方法は何ですか?」です。一般的なpythonモジュールは公正なゲームです:numpy、scipy、およびPIL(その他?)。比較を難しくするために、次のルールを提案したいと思います。
それは本当にあなたが何をしたいかに依存します...多くの場合、あなたは完全に一般的な(読む:遅い)2D畳み込みを必要としません...(すなわち、フィルターが分離可能である場合、代わりに2つの1D畳み込みを使用します...これがさまざまなscipy.ndimage.gaussian
、scipy.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を使用して畳み込みを行う方がかなり高速になる可能性があります(現時点では、良い例を思い付くことができないようですが...)。
私のマシンでは、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))
これは巡回畳み込みであるため、エッジに近い領域を他の方法とは異なる方法で処理する可能性があることに注意してください。
私もこれでいくつかの実験をしました。私の推測では、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);
}
}
}
アプリケーションで畳み込み速度を改善しようとしていますが、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
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()