それらのマトリックスはどのように機能しますか?ピクセルごとに複数にする必要がありますか?周囲のピクセルがない左上、右上、左下、左下のピクセルはどうですか?そして、マトリックスは最初に左から右へ、上から下へ、または上から下へ、次に左から右へと機能しますか?
このカーネル(エッジエンハンス)がなぜ このイメージに変わるのか:http: //i.stack.imgur.com/d755G.png :http: //i.stack.imgur.com/NRdkK.jpg
それらのマトリックスはどのように機能しますか?ピクセルごとに複数にする必要がありますか?周囲のピクセルがない左上、右上、左下、左下のピクセルはどうですか?そして、マトリックスは最初に左から右へ、上から下へ、または上から下へ、次に左から右へと機能しますか?
このカーネル(エッジエンハンス)がなぜ このイメージに変わるのか:http: //i.stack.imgur.com/d755G.png :http: //i.stack.imgur.com/NRdkK.jpg
Convolution フィルタは、すべての単一ピクセルに適用されます。
端では、できることがいくつかあります (すべて、一種の境界線を残すか、画像を縮小します)。
畳み込みを適用する順序は関係ありません (右上から左下が最も一般的です)。順序に関係なく同じ結果が得られます。
ただし、畳み込み行列を適用するときによくある間違いは、調べている現在のピクセルを新しい値で上書きすることです。これは、現在のピクセルの隣にあるピクセルの値に影響します。より良い方法は、計算された値を保持するためのバッファーを作成することです。これにより、畳み込みフィルターの以前のアプリケーションが行列の現在のアプリケーションに影響を与えないようにします。
サンプル画像から、適用されたフィルターが元の画像を見ずに白黒バージョンを作成する理由を判断するのは困難です。
以下は、畳み込みカーネルを画像に適用する段階的な例です (簡単にするために 1D)。
投稿のエッジ拡張カーネルについては、-1 の隣に +1 があることに注意してください。それが何をするかを考えてください。領域が一定の場合、+/-1 の下の 2 つのピクセルが加算されてゼロ (黒) になります。2 つのピクセルが異なる場合、ゼロ以外の値になります。つまり、隣り合った異なるピクセルが強調表示され、同じピクセルが黒に設定されていることがわかります。差が大きいほど、フィルタリングされた画像のピクセルは明るく (より白く) なります。
はい、すべてのピクセルにその行列を掛けます。従来の方法では、畳み込み対象のピクセルに関連する関連ピクセルを見つけ、係数を掛けて平均化します。したがって、次の 3x3 ぼかし:
1, 1, 1,
1, 1, 1,
1, 1, 1
このマトリックスは、さまざまなコンポーネントの関連する値を取得して乗算することを意味します。次に要素数で割ります。したがって、3 x 3 のボックスを取得し、すべての赤の値を合計してから 9 で割ります。3 x 3 のボックスを取得し、すべての緑の値を加算してから 9 で割ります。3 x 3 を取得します。ボックスで、すべての青色の値を合計してから 9 で割ります。
これはいくつかのことを意味します。まず、この操作を実行するには、2 つ目の巨大なメモリ チャンクが必要です。そして、できるすべてのピクセルを実行します。
ただし、これは従来の方法の場合に限られ、従来の方法は実際には不必要に複雑です (わかりますか?)。コーナーで結果を返せば。実際に追加のメモリを必要とすることはなく、開始時のメモリ フットプリント内で常に操作全体を実行します。
public static void convolve(int[] pixels, int offset, int stride, int x, int y, int width, int height, int[][] matrix, int parts) {
int index = offset + x + (y*stride);
for (int j = 0; j < height; j++, index += stride) {
for (int k = 0; k < width; k++) {
int pos = index + k;
pixels[pos] = convolve(pixels,stride,pos, matrix, parts);
}
}
}
private static int crimp(int color) {
return (color >= 0xFF) ? 0xFF : (color < 0) ? 0 : color;
}
private static int convolve(int[] pixels, int stride, int index, int[][] matrix, int parts) {
int redSum = 0;
int greenSum = 0;
int blueSum = 0;
int pixel, factor;
for (int j = 0, m = matrix.length; j < m; j++, index+=stride) {
for (int k = 0, n = matrix[j].length; k < n; k++) {
pixel = pixels[index + k];
factor = matrix[j][k];
redSum += factor * ((pixel >> 16) & 0xFF);
greenSum += factor * ((pixel >> 8) & 0xFF);
blueSum += factor * ((pixel) & 0xFF);
}
}
return 0xFF000000 | ((crimp(redSum / parts) << 16) | (crimp(greenSum / parts) << 8) | (crimp(blueSum / parts)));
}
カーネルでは、伝統的に値を中央の最もピクセルに返します。これにより、画像のエッジの周りがぼやけますが、元の位置に多かれ少なかれ残ります。これは良いアイデアのように思えましたが、実際には問題があります。これを行う正しい方法は、結果のピクセルを左上隅に配置することです。次に、余分なメモリを使用せずに、イメージ全体をスキャンラインで単純に反復し、一度に 1 ピクセルずつ移動して値を返すことができます。エラーは発生しません。色の重みの大部分は、1 ピクセル分上および左にシフトされます。しかし、これは 1 ピクセルであり、結果のピクセルが右下にある状態で逆方向に反復すると、下方向と左方向に戻すことができます。これはキャッシュヒットの問題かもしれませんが。
ただし、最新のアーキテクチャの多くには GPU が搭載されているため、イメージ全体を同時に処理できます。それを一種の論点にする。しかし、グラフィックスで最も重要なアルゴリズムの 1 つがこれを要求するのは奇妙です。これは、操作を実行する最も簡単な方法を不可能にし、メモリを浪費するためです。
そのため、この質問についてマットのような人は、「ただし、畳み込み行列を適用するときによくある間違いは、調べている現在のピクセルを新しい値で上書きすることです。」-- 本当にこれは正しい方法です。エラーは結果ピクセルを左上隅ではなく中央に書き込んでいます。左上隅とは異なり、中央のピクセルが再び必要になるためです。左上隅が再び必要になることはありません (左から右へ、上から下へと反復していると仮定すると) ため、そこに値を保存しても安全です。
「これは、現在のピクセルの隣にあるピクセルの値に影響します。」-- スキャンとして処理したときに左上隅に書き込んでしまうと、必要のないデータを上書きしてしまいます。追加のメモリを大量に使用することは、より良い解決策ではありません。
そのため、おそらくこれまでに見た中で最速の Java ブラーです。
private static void applyBlur(int[] pixels, int stride) {
int v0, v1, v2, r, g, b;
int pos;
pos = 0;
try {
while (true) {
v0 = pixels[pos];
v1 = pixels[pos+1];
v2 = pixels[pos+2];
r = ((v0 >> 16) & 0xFF) + ((v1 >> 16) & 0xFF) + ((v2 >> 16) & 0xFF);
g = ((v0 >> 8 ) & 0xFF) + ((v1 >> 8) & 0xFF) + ((v2 >> 8) & 0xFF);
b = ((v0 ) & 0xFF) + ((v1 ) & 0xFF) + ((v2 ) & 0xFF);
r/=3;
g/=3;
b/=3;
pixels[pos++] = r << 16 | g << 8 | b;
}
}
catch (ArrayIndexOutOfBoundsException e) { }
pos = 0;
try {
while (true) {
v0 = pixels[pos];
v1 = pixels[pos+stride];
v2 = pixels[pos+stride+stride];
r = ((v0 >> 16) & 0xFF) + ((v1 >> 16) & 0xFF) + ((v2 >> 16) & 0xFF);
g = ((v0 >> 8 ) & 0xFF) + ((v1 >> 8) & 0xFF) + ((v2 >> 8) & 0xFF);
b = ((v0 ) & 0xFF) + ((v1 ) & 0xFF) + ((v2 ) & 0xFF);
r/=3;
g/=3;
b/=3;
pixels[pos++] = r << 16 | g << 8 | b;
}
}
catch (ArrayIndexOutOfBoundsException e) { }
}