OpenGL.Shader:Zhigeは、ライブフィルタークライアントの作成方法を教えています(10)ガウスフィルター/ガウスブラー原理の実現
1.「中程度」のないフィルタリング
前章の原理は、平均フィルタリングを実現しました。画像フィルタリングの紹介で述べたように、平均フィルタリングは最も単純な種類の画像ローパスフィルタであり、均一なノイズとガウスノイズを除去できますが、画像にある程度のぼやけが生じます。これは、画像の指定された領域のピクセルを平均化する方法です。平均化フィルターの欠点は、すべてのポイントを平均化するため、画像がぼやけることです。実際、ほとんどの場合、ノイズの割合は小さく、すべてのポイントが同じ重みで処理されるため、必然的に画像がぼやけます。さらに、このフィルターの幅が広いほど、フィルター処理された画像がぼやけます。つまり、画像の詳細が失われ、画像がより「適度」になります。
では、どうすれば「中程度」の画像フィルタリングを実現できるでしょうか。フィルタの重みが変更されている限り、このフィルタのパラメータはガウス分布に従って変更されます。このフィルタはガウスフィルタと呼ばれ、ガウスブラーとも呼ばれます。
2.正規分布、ガウス関数
ここでは、ガウス正規分布の推定アルゴリズムとそのコードの進化について簡単に紹介します。
正規分布とは、正規分布を指し、中心点に近いほど値が大きくなり、中心から離れるほど値が小さくなります。
平均値を計算するときは、「中心点」を原点とし、法線上の位置に応じて他の点に重みを付けて加重平均を求めます。正規分布は明らかに望ましい重量分布モデルです。
正規分布とは何かを理解し、次のクラスは数学的変換プロセスを実現するためにガウス関数を使用する必要があります。
上記の正規分布は1次元ですが、画像の場合は2次元であるため、2次元の正規分布が必要です。
正規分布の密度関数は「ガウス関数」と呼ばれます。その2次元形式は次のとおりです。
ウェイトマトリックスを取得する
中心点の座標を(0,0)とすると、最も近い8点の座標は次のようになります。
範囲が大きいほど、ポイントが遠くなります。
重み行列を計算するには、σの値を設定する必要があります。σ= 1.5と仮定すると、ぼかし半径が1の重み行列は次のようになります:(座標値をガウス式に変換します)
これらの9ポイントの重みの合計は、0.4787147に等しくなります。これらの9ポイントの重み付き平均のみを計算する場合、それらの重みの合計は1に等しくなければなりません。したがって、最終的な重みマトリックスを取得するには、上記の9つの値を0.4787147で割る必要があります。
合計値で割るプロセスは、「正規化問題」とも呼ばれます。目標は、フィルターの総重量を1に等しくすることです。それ以外の場合、合計値が1より大きいフィルターを使用すると画像が明るくなり、フィルターが1より小さいと画像が暗くなります。これまでに、3次のガウス畳み込みカーネルを取得しました。
3.GLシェーダーマルチインデックス転送
次のステップは、このガウスブラーフィルターをGL.Shaderに実装することです。実際、実装の考え方は前の章の平均ブラーと同じですが、今回はシェーダーのインデックス配列転送を紹介します。シェーダーコードを単純化します。
1つ目は固定点シェーダーです。参照配列の記述に注意してください。言うまでもなく、マトリックスの位置と配列のインデックスの位置に注意してください。
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
uniform float widthFactor;
uniform float heightFactor;
uniform float offset; // 采样点半径
const int GAUSSIAN_SAMPLES = 9;
varying vec2 textureCoordinate[GAUSSIAN_SAMPLES];
void main()
{
gl_Position = position;
vec2 widthStep = vec2(offset*widthFactor, 0.0);
vec2 heightStep = vec2(0.0, offset*heightFactor);
textureCoordinate[0] = inputTextureCoordinate.xy - heightStep - widthStep; // 左上
textureCoordinate[1] = inputTextureCoordinate.xy - heightStep; // 上
textureCoordinate[2] = inputTextureCoordinate.xy - heightStep + widthStep; // 右上
textureCoordinate[3] = inputTextureCoordinate.xy - widthStep; // 左中
textureCoordinate[4] = inputTextureCoordinate.xy; // 中
textureCoordinate[5] = inputTextureCoordinate.xy + widthStep; // 右中
textureCoordinate[6] = inputTextureCoordinate.xy + heightStep - widthStep; // 左下
textureCoordinate[7] = inputTextureCoordinate.xy + heightStep; // 下
textureCoordinate[8] = inputTextureCoordinate.xy + heightStep + widthStep; // 右下
}
次にフラグメントシェーダーがあります。外部からコンボリューションカーネルconvolutionMatrixを参照し、外部C ++レイヤーコードから計算されたガウスフィルターのコンボリューションカーネルを渡します。実際、yuv2rgbの変換マトリックスのようにシェーダーに静的に書き込むことができます。外部コード転送の読みやすさをデバッグして強化することだけが便利です。
precision highp float;
uniform sampler2D SamplerY;
uniform sampler2D SamplerU;
uniform sampler2D SamplerV;
mat3 colorConversionMatrix = mat3(
1.0, 1.0, 1.0,
0.0, -0.39465, 2.03211,
1.13983, -0.58060, 0.0);
vec3 yuv2rgb(vec2 pos)
{
vec3 yuv;
yuv.x = texture2D(SamplerY, pos).r;
yuv.y = texture2D(SamplerU, pos).r - 0.5;
yuv.z = texture2D(SamplerV, pos).r - 0.5;
return colorConversionMatrix * yuv;
}
uniform mediump mat3 convolutionMatrix;
const int GAUSSIAN_SAMPLES = 9;
varying vec2 textureCoordinate[GAUSSIAN_SAMPLES];
void main()
{
//mediump vec3 topLeftColor = yuv2rgb(textureCoordinate[0]);
//mediump vec3 topColor = yuv2rgb(textureCoordinate[1]);
//mediump vec3 topRightColor = yuv2rgb(textureCoordinate[2]);
//mediump vec3 leftColor = yuv2rgb(textureCoordinate[3]);
//mediump vec3 centerColor = yuv2rgb(textureCoordinate[4]);
//mediump vec3 rightColor = yuv2rgb(textureCoordinate[5]);
//mediump vec3 bottomLeftColor = yuv2rgb(textureCoordinate[6]);
//mediump vec3 bottomColor = yuv2rgb(textureCoordinate[7]);
//mediump vec3 bottomRightColor = yuv2rgb(textureCoordinate[8]);
vec3 fragmentColor = (yuv2rgb(textureCoordinate[0]) * convolutionMatrix[0][0]);
fragmentColor += (yuv2rgb(textureCoordinate[1]) * convolutionMatrix[0][1]);
fragmentColor += (yuv2rgb(textureCoordinate[2]) * convolutionMatrix[0][2]);
fragmentColor += (yuv2rgb(textureCoordinate[3]) * convolutionMatrix[1][0]);
fragmentColor += (yuv2rgb(textureCoordinate[4]) * convolutionMatrix[1][1]);
fragmentColor += (yuv2rgb(textureCoordinate[5]) * convolutionMatrix[1][2]);
fragmentColor += (yuv2rgb(textureCoordinate[6]) * convolutionMatrix[2][0]);
fragmentColor += (yuv2rgb(textureCoordinate[7]) * convolutionMatrix[2][1]);
fragmentColor += (yuv2rgb(textureCoordinate[8]) * convolutionMatrix[2][2]);
gl_FragColor = vec4(fragmentColor, 1.0);
}
C ++コードの抜粋の一部:
#ifndef GPU_GAUSSIANBLUR_FILTER_HPP
#define GPU_GAUSSIANBLUR_FILTER_HPP
#include "GpuBaseFilter.hpp"
class GpuGaussianBlurFilter : public GpuBaseFilter {
public:
virtual int getTypeId() { return FILTER_TYPE_GAUSSIANBLUR; }
GpuGaussianBlurFilter()
{
GAUSSIAN_BLUR_VERTEX_SHADER ="...";
GAUSSIAN_BLUR_FRAGMENT_SHADER ="...";
}
void init() {
GpuBaseFilter::init(GAUSSIAN_BLUR_VERTEX_SHADER.c_str(), GAUSSIAN_BLUR_FRAGMENT_SHADER.c_str());
mWidthFactorLocation = glGetUniformLocation(getProgram(), "widthFactor");
mHeightFactorLocation = glGetUniformLocation(getProgram(), "heightFactor");
mSampleOffsetLocation = glGetUniformLocation(getProgram(), "offset");
mUniformConvolutionMatrix = glGetUniformLocation(getProgram(), "convolutionMatrix");
mSampleOffset = 0.0f;
// 高斯滤波的卷积核
convolutionKernel = new GLfloat[9]{
0.0947416f, 0.118318f, 0.0947416f,
0.118318f, 0.147761f, 0.118318f,
0.0947416f, 0.118318f, 0.0947416f,
};
}
void onOutputSizeChanged(int width, int height) {
GpuBaseFilter::onOutputSizeChanged(width, height);
glUniform1f(mWidthFactorLocation, 1.0f / width);
glUniform1f(mHeightFactorLocation, 1.0f / height);
}
void setAdjustEffect(float percent) {
mSampleOffset = range(percent * 100.0f, 0.0f, 3.0f);
// 动态修正采样半径
}
void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
void* positionCords, void* textureCords)
{
if (!mIsInitialized)
return;
glUseProgram(mGLProgId);
// 传递高斯滤波的卷积核
glUniformMatrix3fv(mUniformConvolutionMatrix, 1, GL_FALSE, convolutionKernel);
glUniform1f(mSampleOffsetLocation, mSampleOffset);
glUniform1f(mWidthFactorLocation, 1.0f / mOutputWidth);
glUniform1f(mHeightFactorLocation, 1.0f / mOutputHeight);
glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords);
glEnableVertexAttribArray(mGLAttribPosition);
glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);
glEnableVertexAttribArray(mGLAttribTextureCoordinate);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, SamplerY_texId);
glUniform1i(mGLUniformSampleY, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, SamplerU_texId);
glUniform1i(mGLUniformSampleU, 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, SamplerV_texId);
glUniform1i(mGLUniformSampleV, 2);
// onDrawArraysPre
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(mGLAttribPosition);
glDisableVertexAttribArray(mGLAttribTextureCoordinate);
glBindTexture(GL_TEXTURE_2D, 0);
}
};
#endif // GPU_GAUSSIANBLUR_FILTER_HPP
上記はガウスフィルタリングの実現です。ビデオ練習効果は認識度の低いgifに変換されるため、ここでは説明しません。興味がある場合は、デモプロジェクトを実行して効果を確認できます。
プロジェクトアドレス:https://github.com/MrZhaozhirong/NativeCppApp Mean Blur Filter cpp / gpufilter / filter / GpuGaussianBlurFilter.hpp
それがすべてです。
インタレストディスカッショングループ:703531738。コード:Zhige 13567
引用リファレンス
ガウス分布アルゴリズム https://www.cnblogs.com/invisible2/p/9177018.html