このトピックの著者はポピックhabrayuzerであり、アストラルの理由でこのトピック自体を投稿することはできません。
はじめに

おそらく、大部分のhabro-communityは、ぼかし、シャープ、エッジの検出、エンボスなどの画像処理フィルターについて直接知っています。 ある者は彼らとより密接に連携し、ある者はそれらを当たり前のように使用しました。 しかし、誰もが画像がどのようにフィルタリングされるのか、そしてリストされたフィルターの共通点は何ですか? このトピックでは、これをすべて実行するアルゴリズムを一般的な用語で説明し、その実装も示します。
ちょっとした理論。
そのため、リストされたフィルターや他の多くのフィルターは
畳み込みに基づいています。 畳み込みとは何ですか? 畳み込み(英語の畳み込み)は、ある関数の「類似性」を、別の関数の反映されシフトされたコピーと示す操作です。 畳み込みの概念は、グループとメジャーで定義された関数に一般化されます。 少し複雑な定義ではありませんか? 数学的理論の部分に興味がある人は、
ウィキペディアへのリンクを見て、おそらく、自分自身に役立つ何かを学ぶことができます。
畳み込みの定義-説明を「指で」説明するのは、画像処理の場合のみです。これは科学文献ほどスマートで正確ではないかもしれませんが、このプロセスの本質を理解できるように思えます。
したがって、畳み込みは、選択したピクセルの新しい値を計算し、その周囲のピクセルの値を考慮に入れる操作です。 値を計算するために
、畳み込みカーネルと呼ばれる行列
が使用されます。 通常、畳み込みカーネルは正方行列n * nで、nは奇数ですが、行列を長方形にすることを妨げるものはありません。 選択したピクセルの新しい値の計算中、畳み込みカーネルは、このピクセルの中心(ここではマトリックスサイズの奇数が重要です)によって「適用」されているようです。 周囲のピクセルもコアで覆われています。 次に、合計が計算されます。ここで、項はピクセル値と、指定されたピクセルをカバーするカーネルセルの値の積です。 合計は、畳み込みカーネルのすべての要素の合計で除算されます。 結果の値は、選択したピクセルの新しい値です。 画像の各ピクセルに畳み込みを適用すると、選択した畳み込みカーネルに応じて、特定の効果が得られます。
これで理論で終わり、このアルゴリズムの例と実装に移ります。 脳はまだ生きていますか? :)
いくつかの例。




アルゴリズムの実装。
C#のコードの主要部分のみを提供します。畳み込みの完全なソースコードと視覚テスト用の小さなプログラムは、記事の最後にあるリンクからダウンロードできます。 コードが非常に最適なものとして書かれておらず、最適化のためのフィールドがあることを事前に予約します。 たとえば、事前に計算されたピクセル値を使用して速度を上げることができますが、それはポイントではありません。
public static class Convolution
{
public static Bitmap Apply( Bitmap input, double [,] kernel)
{
//
byte [] inputBytes = BitmapBytes.GetBytes(input);
byte [] outputBytes = new byte [inputBytes.Length];
int width = input.Width;
int height = input.Height;
int kernelWidth = kernel.GetLength(0);
int kernelHeight = kernel.GetLength(1);
//
for ( int x = 0; x < width; x++)
{
for ( int y = 0; y < height; y++)
{
double rSum = 0, gSum = 0, bSum = 0, kSum = 0;
for ( int i = 0; i < kernelWidth; i++)
{
for ( int j = 0; j < kernelHeight; j++)
{
int pixelPosX = x + (i - (kernelWidth / 2));
int pixelPosY = y + (j - (kernelHeight / 2));
if ((pixelPosX < 0) ||
(pixelPosX >= width) ||
(pixelPosY < 0) ||
(pixelPosY >= height)) continue ;
byte r = inputBytes[3 * (width * pixelPosY + pixelPosX) + 0];
byte g = inputBytes[3 * (width * pixelPosY + pixelPosX) + 1];
byte b = inputBytes[3 * (width * pixelPosY + pixelPosX) + 2];
double kernelVal = kernel[i, j];
rSum += r * kernelVal;
gSum += g * kernelVal;
bSum += b * kernelVal;
kSum += kernelVal;
}
}
if (kSum <= 0) kSum = 1;
//
rSum /= kSum;
if (rSum < 0) rSum = 0;
if (rSum > 255) rSum = 255;
gSum /= kSum;
if (gSum < 0) gSum = 0;
if (gSum > 255) gSum = 255;
bSum /= kSum;
if (bSum < 0) bSum = 0;
if (bSum > 255) bSum = 255;
//
outputBytes[3 * (width * y + x) + 0] = ( byte )rSum;
outputBytes[3 * (width * y + x) + 1] = ( byte )gSum;
outputBytes[3 * (width * y + x) + 2] = ( byte )bSum;
}
}
//
return BitmapBytes.GetBitmap(outputBytes, width, height);
}
}
* This source code was highlighted with Source Code Highlighter .
プログラム例のダウンロード以上です。 この資料が有用であり、新しいものを提供してくれたことを願っています。 このトピックに興味がある場合は、畳み込みカーネルを形成する基本的な方法と、このアルゴリズムを最適化する方法について詳しく説明します。 ご清聴ありがとうございました!