7

私はCUDAで画像処理に取り組んでいますが、ピクセル処理に疑問があります。

m x m畳み込みフィルターを適用するときに、画像の境界ピクセルでよく行われることは何ですか?

3 x 3畳み込みカーネルで1は、特にコードが共有メモリで改善されている場合、画像のピクセル境界を無視する方が扱いやすくなります。実際、この場合、特定のピクセルに利用可能なすべての近傍があるかどうかを確認する必要はありません(つまり、座標のピクセルが(0, 0)左、左上、上に隣接していない)。1ただし、元の画像のピクセル境界を削除すると、部分的な結果が生成される可能性があります。

それとは反対に、共有メモリの改善を使用する場合、つまり、たとえばピクセルをロードするが、内部を計算する場合にも、画像内のすべてのピクセルを処理したいと思います。この場合も、境界ピクセルを無視すると、より明確なコードが生成されます。16 x 1614 x 14

この場合、通常何が行われますか?

誰かが通常、境界ピクセルを無視して私のアプローチを使用しますか?

もちろん、答えは問題の種類によって異なることは承知しています。つまり、ピクセル単位で2つの画像を追加しても、この問題は発生しません。

前もって感謝します。

4

3 に答える 3

12

境界線効果を処理するための一般的なアプローチは、フィルターサイズに基づいて、元の画像に追加の行と列を埋め込むことです。埋め込み値の一般的な選択肢は次のとおりです。

  • 定数(例:ゼロ)
  • 最初と最後の行/列を必要な回数だけ複製します
  • 境界で画像を反映します(例:column [-1] = column [1]、column [-2] = column [2])
  • 画像値をラップします(例:column [-1] = column [width-1]、column [-2] = column [width-2])
于 2011-04-19T11:45:19.013 に答える
5

tl; dr:解決しようとしている問題によって異なります。すべての問題に適用できる解決策はありません。実際、数学的に言えば、それはあなたが対処しなければならない不適切な問題であると私は信じているので、「解決策」はまったくないかもしれないと私は思う。

(数学の無謀な乱用について事前に謝罪します)

実例を示すために、すべてのピクセルコンポーネントとカーネル値が正であると想定される状況を考えてみましょう。これらの回答のいくつかがどのように私たちを迷わせる可能性があるかを理解するために、単純な平均化(「ボックス」)フィルターについてさらに考えてみましょう。画像の境界の外側の値をゼロに設定すると、境界のceil(n / 2)(マンハッタン距離)内のすべてのピクセルの平均が明らかに下にドラッグされます。したがって、フィルタリングされた画像に「暗い」境界線が表示されます(単一の強度コンポーネントまたはRGB色空間を想定すると、結果は色空間によって異なります)。境界の外側の値を任意の定数に設定すると、同様の議論ができることに注意してください。平均はその定数に向かう傾向があります。とにかく典型的な画像のエッジが0に向かう傾向がある場合は、ゼロの定数が適切な場合があります。ガウス関数のようなカーネルですが、カーネル値は中心からの距離とともに急速に減少する傾向があるため、問題はそれほど顕著ではありません。

ここで、定数を使用する代わりに、エッジ値を繰り返すことを選択したとします。これは、画像の周囲に境界線を作成し、行、列、またはコーナーを十分な回数コピーして、フィルターが新しい画像の「内側」にとどまるようにすることと同じです。また、サンプル座標をクランプ/飽和させることと考えることもできます。これは、エッジピクセルの値を強調しすぎるため、単純なボックスフィルターに問題があります。エッジピクセルのセットは複数回表示されますが、それらはすべて同じ重みを受け取りますw=(1/(n*n))。値Kのエッジピクセルを3回サンプリングするとします。つまり、平均への貢献は次のとおりです。

K*w + K*w + K*w  = K*3*w

非常に効果的であるため、平均して1つのピクセルの重みが高くなります。これは平均的なフィルターであるため、重みはカーネル全体で一定であることに注意してください。ただし、この引数は、位置によっても重みが異なるカーネルに適用されます(ここでも、ガウスカーネルについて考えてみてください)。

画像の境界内の値を引き続き使用するように、サンプリング座標をラップまたは反映するとします。これには、定数を使用するよりもいくつかの貴重な利点がありますが、必ずしも「正しい」とは限りません。たとえば、上の境界線のオブジェクトが下のオブジェクトと似ている場所で、何枚の写真を撮りますか?鏡のように滑らかな湖の写真を撮っていない限り、これが真実だとは思えません。岩の写真を撮ってゲームのテクスチャとして使用する場合は、ラッピングまたはリフレクティングが適切な場合があります。ここでは、ラッピングとリフレクトによって、フーリエ変換を使用した結果として生じるアーティファクトがどのように減少するかについて、重要なポイントがあると確信しています。ただし、これは同じ考えに戻ります。

では、青い空の下にある真っ赤な岩の写真をフィルタリングする場合は、どうすればよいでしょうか。明らかに、青い空にオレンジがかった霞を、赤い岩に青がかったファズを追加したくないでしょう。サンプル座標の反映は、反映された座標で見つかったピクセルと同様の色を期待するため機能します...議論のために、フィルターカーネルが非常に大きいため、反映された座標が地平線を超えて広がると想像しない限り。

ボックスフィルターの例に戻りましょう。このフィルターの代替手段は、静的カーネルの使用について考えるのをやめ、このカーネルが何をするつもりだったかを振り返ることです。平均化/ボックスフィルターは、ピクセルコンポーネントを合計し、合計されたピクセル数で除算するように設計されています。アイデアは、これがノイズを滑らかにするということです。境界付近のノイズを抑制する効果の低下をトレードする場合は、単純に合計するピクセル数を減らし、それに応じて小さい数で除算することができます。これは、同様の「正規化」用語(フィルターの面積または体積に関連する用語)を持つフィルターに拡張できます。「面積」の用語については、境界内にあるカーネルの重みの数を数え、そうでない重みを無視します。次に、このカウントを「面積」として使用します (これには余分な乗算が含まれる場合があります)。ボリュームの場合(ここでも、正の重みを想定しています!)、カーネルの重みを単純に合計します。ノイズの多いピクセルと競合するピクセルが少なく、微分がノイズに敏感であることが知られているため、このアイデアは微分フィルターにはおそらくひどいものです。また、一部のフィルターは、ab-initio / analyticメソッドからではなく、数値最適化および/または経験的データによって導出されているため、すぐにわかる「正規化」係数がない場合があります。

于 2014-10-07T21:24:25.577 に答える
1

あなたの質問はやや広範で、2つの問題が混在していると思います。

  1. 境界条件の処理;
  2. ハロー領域を扱う。

最初の問題(境界条件)は、たとえば、とイメージと3 x 3カーネルの間の畳み込みを計算するときに発生します。畳み込みウィンドウが境界を越えると、画像を境界の外側に拡張するという問題が発生します。

2番目の問題(ハロー領域)は、たとえば、16 x 16共有メモリ内にタイルをロードするときに発生し、 14 x 142次導関数を計算するために内部タイルを処理する必要があります。

2番目の問題については、次のような有用な質問があると思います。CUDAカーネルのメモリアクセス合体の分析

境界外の信号の拡張に関しては、この場合、提供されるさまざまなアドレッシングモードのおかげで、テクスチャメモリによって便利なツールが提供されます。CUDAテクスチャのさまざまなアドレッシングモードを参照してください。

以下に、テクスチャメモリを使用して周期境界条件でメディアンフィルターを実装する方法の例を示します。

#include <stdio.h>

#include "TimingGPU.cuh"
#include "Utilities.cuh"

texture<float, 1, cudaReadModeElementType> signal_texture;

#define BLOCKSIZE 32

/*************************************************/
/* KERNEL FUNCTION FOR MEDIAN FILTER CALCULATION */
/*************************************************/
__global__ void median_filter_periodic_boundary(float * __restrict__ d_vec, const unsigned int N){

    unsigned int tid = threadIdx.x + blockIdx.x * blockDim.x;

    if (tid < N) {

        float signal_center = tex1D(signal_texture, tid - 0);
        float signal_before = tex1D(signal_texture, tid - 1);
        float signal_after  = tex1D(signal_texture, tid + 1);

        printf("%i %f %f %f\n", tid, signal_before, signal_center, signal_after);

        d_vec[tid] = (signal_center + signal_before + signal_after) / 3.f;

    }
}


/********/
/* MAIN */
/********/
int main() {

    const int N = 10;

    // --- Input host array declaration and initialization
    float *h_arr = (float *)malloc(N * sizeof(float));
    for (int i = 0; i < N; i++) h_arr[i] = (float)i;

    // --- Output host and device array vectors
    float *h_vec = (float *)malloc(N * sizeof(float));
    float *d_vec;   gpuErrchk(cudaMalloc(&d_vec, N * sizeof(float)));

    // --- CUDA array declaration and texture memory binding; CUDA array initialization
    cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<float>();
    //Alternatively
    //cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(32, 0, 0, 0, cudaChannelFormatKindFloat);

    cudaArray *d_arr;   gpuErrchk(cudaMallocArray(&d_arr, &channelDesc, N, 1));
    gpuErrchk(cudaMemcpyToArray(d_arr, 0, 0, h_arr, N * sizeof(float), cudaMemcpyHostToDevice));

    cudaBindTextureToArray(signal_texture, d_arr); 
    signal_texture.normalized = false; 
    signal_texture.addressMode[0] = cudaAddressModeWrap;

    // --- Kernel execution
    median_filter_periodic_boundary<<<iDivUp(N, BLOCKSIZE), BLOCKSIZE>>>(d_vec, N);
    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaDeviceSynchronize());

    gpuErrchk(cudaMemcpy(h_vec, d_vec, N * sizeof(float), cudaMemcpyDeviceToHost));

    for (int i=0; i<N; i++) printf("h_vec[%i] = %f\n", i, h_vec[i]);

    printf("Test finished\n");

    return 0;
}
于 2015-09-02T08:46:01.067 に答える