可能な限り最速の ガウスぼかし アルゴリズムをどのように実装しますか?
これをJavaで実装するので、 [〜#〜] gpu [〜#〜] ソリューションは除外されます。私のアプリケーション planetGenesis はクロスプラットフォームなので、 [〜#〜] jni [〜#〜] は必要ありません。
ガウスカーネルが分離可能であるという事実を使用する必要があります。 e。 2Dたたみ込みを2つの1Dたたみ込みの組み合わせとして表現できます。
フィルターが大きい場合、空間領域でのたたみ込みが周波数(フーリエ)領域での乗算と同等であるという事実を使用することも意味があります。つまり、画像とフィルターのフーリエ変換を取り、(複雑な)結果を乗算してから、逆フーリエ変換を行うことができます。 FFT(高速フーリエ変換)の複雑さはO(n log n)ですが、畳み込みの複雑さはO(n ^ 2)です。また、同じフィルターで多くの画像をぼかす必要がある場合は、フィルターのFFTを1回実行するだけで済みます。
FFTを使用することにした場合は、 FFTWライブラリ が適切な選択です。
数学のジョークはこれを知っている可能性が高いですが、他の誰にとってもそうです。
ガウスの数学的性質が優れているため、画像の各行で1Dガウスぼかしを実行してから、各列で1Dぼかしを実行すると、2D画像をすばやくぼかすことができます。
Quasimondo:Incubator:Processing:Fast Gaussian Blurが見つかりました。この方法には、浮動小数点数や浮動小数点数の除算ではなく、整数の使用やテーブルの検索など、多くの近似が含まれています。現代のスピードアップがどれほどかわからないJavaコード。
四角形のファストシャドウは B-splines を使用した近似アルゴリズムを備えています。
C#の高速ガウスぼかしアルゴリズムは、いくつかのクールな最適化があると主張しています。
また、高速ガウスぼかし(PDF)by David Everlyは、ガウスぼかし処理のための高速メソッドを備えています。
私はさまざまな方法を試して、それらをベンチマークし、結果をここに投稿します。
私の目的のために、基本的な(X-Y軸を個別に処理する)メソッドとDavid EverlyのFast Gaussian Blurメソッドをインターネットからコピーして実装しました。パラメータが異なるため、直接比較することはできませんでした。ただし、後者は大きなぼかし半径の場合、反復回数がはるかに少なくなります。また、後者は近似アルゴリズムです。
究極のソリューション
多くの情報と実装に非常に混乱し、どれを信頼すればよいかわかりませんでした。それを理解した後、私は自分の記事を書くことにしました。時間の節約になることを願っています。
これにはソースコードが含まれており、(私はそう思います)短く、クリーンで、他のどの言語にも簡単に書き換え可能です。他の人が見られるように投票してください。
おそらく、ボックスのぼかしがはるかに高速です。優れたチュートリアルと Cコードのコピーと貼り付け については このリンク を参照してください。
ぼかしの半径を大きくするには、 ボックスのぼかし を3回適用してみます。これはガウスぼかしを非常によく近似し、真のガウスぼかしよりもはるかに高速になります。
全画像の転置は遅いため、小さなブロックで行うのが最適ですが、小さなブロックの転置は PUNPCKs ( PUNPCKHBW、PUNPCKHDQ、PUNPCKHWD)のチェーンを使用して非常に高速に実行できます。 、PUNPCKLBW、PUNPCKLDQ、PUNPCKLWD )。
私は私の研究のためにこの問題に苦労し、高速ガウスぼかしのための興味深い方法を試しました。まず、前述のように、ぼかしを2つの1Dぼかしに分離するのが最善ですが、実際のピクセル値の計算はハードウェアに応じて、実際にすべての可能な値を事前に計算して、ルックアップテーブルに保存できます。
つまり、Gaussian coefficient
* input pixel value
のすべての組み合わせを事前に計算します。もちろん、係数を離散化する必要がありますが、私はこのソリューションを追加したかっただけです。 [〜#〜] ieee [〜#〜] サブスクリプションをお持ちの場合は、実際のルックアップテーブルを使用した高速画像ぼかし)で詳細を読むことができます時間特徴抽出。
最終的に、私は [〜#〜] cuda [〜#〜] を使用してしまいました:)
Ivan Kuckirによる、線形ボックスブラーを使用した3つのパスを使用する高速ガウスブラーの実装をJavaに変換しました。結果のプロセスはO(n)彼が述べたように 彼自身のブログで です。3つのタイムボックスのぼかしがガウスに近似する理由について詳しく知りたい場合はぼかし(3%) ボックスぼかし と ガウスぼかし をチェックしてください。
Java実装です。
@Override
public BufferedImage ProcessImage(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int[] pixels = image.getRGB(0, 0, width, height, null, 0, width);
int[] changedPixels = new int[pixels.length];
FastGaussianBlur(pixels, changedPixels, width, height, 12);
BufferedImage newImage = new BufferedImage(width, height, image.getType());
newImage.setRGB(0, 0, width, height, changedPixels, 0, width);
return newImage;
}
private void FastGaussianBlur(int[] source, int[] output, int width, int height, int radius) {
ArrayList<Integer> gaussianBoxes = CreateGausianBoxes(radius, 3);
BoxBlur(source, output, width, height, (gaussianBoxes.get(0) - 1) / 2);
BoxBlur(output, source, width, height, (gaussianBoxes.get(1) - 1) / 2);
BoxBlur(source, output, width, height, (gaussianBoxes.get(2) - 1) / 2);
}
private ArrayList<Integer> CreateGausianBoxes(double sigma, int n) {
double idealFilterWidth = Math.sqrt((12 * sigma * sigma / n) + 1);
int filterWidth = (int) Math.floor(idealFilterWidth);
if (filterWidth % 2 == 0) {
filterWidth--;
}
int filterWidthU = filterWidth + 2;
double mIdeal = (12 * sigma * sigma - n * filterWidth * filterWidth - 4 * n * filterWidth - 3 * n) / (-4 * filterWidth - 4);
double m = Math.round(mIdeal);
ArrayList<Integer> result = new ArrayList<>();
for (int i = 0; i < n; i++) {
result.add(i < m ? filterWidth : filterWidthU);
}
return result;
}
private void BoxBlur(int[] source, int[] output, int width, int height, int radius) {
System.arraycopy(source, 0, output, 0, source.length);
BoxBlurHorizantal(output, source, width, height, radius);
BoxBlurVertical(source, output, width, height, radius);
}
private void BoxBlurHorizontal(int[] sourcePixels, int[] outputPixels, int width, int height, int radius) {
int resultingColorPixel;
float iarr = 1f / (radius + radius);
for (int i = 0; i < height; i++) {
int outputIndex = i * width;
int li = outputIndex;
int sourceIndex = outputIndex + radius;
int fv = Byte.toUnsignedInt((byte) sourcePixels[outputIndex]);
int lv = Byte.toUnsignedInt((byte) sourcePixels[outputIndex + width - 1]);
float val = (radius) * fv;
for (int j = 0; j < radius; j++) {
val += Byte.toUnsignedInt((byte) (sourcePixels[outputIndex + j]));
}
for (int j = 0; j < radius; j++) {
val += Byte.toUnsignedInt((byte) sourcePixels[sourceIndex++]) - fv;
resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
outputPixels[outputIndex++] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
}
for (int j = (radius + 1); j < (width - radius); j++) {
val += Byte.toUnsignedInt((byte) sourcePixels[sourceIndex++]) - Byte.toUnsignedInt((byte) sourcePixels[li++]);
resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
outputPixels[outputIndex++] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
}
for (int j = (width - radius); j < width; j++) {
val += lv - Byte.toUnsignedInt((byte) sourcePixels[li++]);
resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
outputPixels[outputIndex++] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
}
}
}
private void BoxBlurVertical(int[] sourcePixels, int[] outputPixels, int width, int height, int radius) {
int resultingColorPixel;
float iarr = 1f / (radius + radius + 1);
for (int i = 0; i < width; i++) {
int outputIndex = i;
int li = outputIndex;
int sourceIndex = outputIndex + radius * width;
int fv = Byte.toUnsignedInt((byte) sourcePixels[outputIndex]);
int lv = Byte.toUnsignedInt((byte) sourcePixels[outputIndex + width * (height - 1)]);
float val = (radius + 1) * fv;
for (int j = 0; j < radius; j++) {
val += Byte.toUnsignedInt((byte) sourcePixels[outputIndex + j * width]);
}
for (int j = 0; j <= radius; j++) {
val += Byte.toUnsignedInt((byte) sourcePixels[sourceIndex]) - fv;
resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
outputPixels[outputIndex] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
sourceIndex += width;
outputIndex += width;
}
for (int j = radius + 1; j < (height - radius); j++) {
val += Byte.toUnsignedInt((byte) sourcePixels[sourceIndex]) - Byte.toUnsignedInt((byte) sourcePixels[li]);
resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
outputPixels[outputIndex] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
li += width;
sourceIndex += width;
outputIndex += width;
}
for (int j = (height - radius); j < height; j++) {
val += lv - Byte.toUnsignedInt((byte) sourcePixels[li]);
resultingColorPixel = Byte.toUnsignedInt(((Integer) Math.round(val * iarr)).byteValue());
outputPixels[outputIndex] = (0xFF << 24) | (resultingColorPixel << 16) | (resultingColorPixel << 8) | (resultingColorPixel);
li += width;
outputIndex += width;
}
}
}
特に大きなカーネルを使用したい場合は、CUDAまたは他のGPUプログラミングツールキットを使用することを検討します。それに失敗すると、常にアセンブリのループを手動で微調整する必要があります。
1D:
ほとんどすべてのカーネルを繰り返し使用してぼかすと、ガウスカーネルになりがちです。これがガウス分布の優れた点であり、統計学者が気に入っている理由です。だからぼかしやすいものを選び、それを数回適用してください。
たとえば、ボックス型のカーネルではぼかしが簡単です。最初に累積合計を計算します。
y(i) = y(i-1) + x(i)
次に:
blurred(i) = y(i+radius) - y(i-radius)
数回繰り返します。
または、いくつかの種類の [〜#〜] iir [〜#〜] フィルターを使用して数回往復する場合もありますが、これらは同様に高速です。
2D以上の場合:
DarenWが言ったように、各次元を次々にぼかします。
私がここで行った方法でボックスぼかしを使用してみてください: 拡張ボックスぼかしを使用した近似ガウスぼかし
これが最良の近似です。
インテグラルイメージを使用すると、さらに高速化できます。
行う場合は、ソリューションを共有してください。
CWPのDave Haleにはminejtkパッケージがあり、これには再帰ガウスフィルター(DericheメソッドとVan Vlietメソッド)が含まれています。 Javaサブルーチンは https://github.com/dhale/jtk/blob/0350c23f91256181d415ea7369dbd62855ac4460/core/src/main/Java/edu/mines/jtk/ dsp/RecursiveGaussianFilter.Java
デリシェの方法は、ガウスぼかし(およびガウスの導関数)に非常に適しているようです。
私はさまざまな場所でいくつかの回答を見て、それらをここに集めています。これで、私の周りを囲み、後で覚えられるようにします。
使用するアプローチに関係なく、単一の正方形フィルターを使用するのではなく、1次元フィルターを使用して 水平方向と垂直方向を別々にフィルター処理 します。
これらすべてを確認した後、単純で貧弱な近似が実際にうまく機能することがよくあることを思い出しました。別の分野では、Alex Krizhevskyは、一見するとシグモイドのひどい近似に見えても、ReLUは革新的なAlexNetの古典的なシグモイド関数よりも高速であることを発見しました。
新しいライブラリでこの古い質問に答える(2016年現在) JavaによるGPUテクノロジーの多くの新しい進歩です。
他のいくつかの回答で示唆されているように、CUDAは代替手段です。しかし、JavaはCUDAをサポートしています。
IBM CUDA4Jライブラリ:Java APIを提供します。これらの新しいAPIを使用して、Java Javaメモリモデル、例外、および自動リソース管理の利便性を備えて、GPUデバイスの特性を管理し、GPUに作業をオフロードするプログラム。
Jcuda:Javaバインディング。JCudaを使用すると、JavaプログラムからCUDAランタイムおよびドライバAPIと対話することができます。
Aparapi:Java開発者がGPUとAPUデバイスに制限されるのではなく、GPUでデータ並列コードフラグメントを実行することでデバイスの計算能力を利用できるようにします。ローカルCPU。
一部のJava OpenCLバインディングライブラリ
https://github.com/ochafik/JavaCL :Java OpenCLのバインディング:自動生成された低レベルのバインディングに基づくオブジェクト指向のOpenCLライブラリ
http://jogamp.org/jocl/www/ :Java OpenCLのバインディング:自動生成された低レベルのバインディングに基づくオブジェクト指向のOpenCLライブラリ
http://www.lwjgl.org/ :Java OpenCLのバインディング:自動生成された低レベルのバインディングとオブジェクト指向の便利なクラス
http://jocl.org/ :Java OpenCLのバインディング:元のOpenCL APIの1:1マッピングである低レベルのバインディング
上記のライブラリはすべて、CPUでのJavaでの実装よりも速くガウスぼかしを実装するのに役立ちます。
2Dデータのガウスぼかしには、いくつかの高速な方法があります。あなたが知っておくべきこと。
選択は、必要な速度、精度、実装の複雑さによって異なります。