OpenGL.Shader: Zhige te enseña a escribir un filtro de cliente en vivo (12) Filtro visual: la realización del principio de filtrado bilateral del blanqueamiento de la piel

OpenGL.Shader: Zhige te enseña a escribir un cliente de filtro en vivo (12)

 

Los arreglos de trabajo y vida están demasiado llenos, vacié mi blog por un tiempo, solo recientemente tengo tiempo para seguir organizando el estudio de filtros. Este artículo trae un aprendizaje simple de dermoabrasión con efecto de filtro más popular del filtrado bilateral.

1. ¿Qué es el filtrado bilateral?

Echemos un vistazo a la explicación más oficial: el filtro bilateral es un método de filtrado no lineal que combina la proximidad espacial de la imagen y la similitud del valor del píxel. También considera la información espacial y la escala de grises. Similitud, para lograr el propósito de preservación de bordes y eliminación de ruido. Es simple, no iterativo y parcial.

Es posible que no comprenda la "no linealidad" a primera vista. De hecho, el filtro medio / filtro gaussiano introducido anteriormente es un filtro lineal, que se entiende simplemente como: para todos los valores de píxeles de una imagen, se usa la misma matriz de filtro y la convolución se realiza de acuerdo con una proporción fija de coeficientes de peso. El filtrado no lineal consiste en agregar un parámetro para determinar si los píxeles adyacentes son similares, de modo que el coeficiente de peso en el proceso de convolución ya no sea una proporción fija, sino que se ajuste dinámicamente a pedido.

Después de combinar la comprensión anterior de la lengua vernácula, la siguiente práctica internacional comienza con la fórmula del filtrado bilateral.

Escriba la descripción de la imagen aquí


g (i, j) representa el resultado de salida;
S (i, j) se refiere a la operación de convolución (2N + 1) (2N + 1) centrada en (i, j); N es el radio de la matriz de convolución La longitud
(k, l) representa el punto de entrada (s) en el rango; f (k, l) es el valor correspondiente al punto (k, l).
w (i, j, k, l) representa el valor calculado por dos funciones gaussianas (nota: este no es el peso final)

Transformamos la fórmula anterior, asumiendo que w (i, j, k, l) en la fórmula es m, entonces
Escriba la descripción de la imagen aquí

Sea m1 + m2 + m3… + mn = M, entonces podemos
Escriba la descripción de la imagen aquí
ver que obviamente se trata de una operación de convolución de la matriz de la imagen y el núcleo. Entre ellos, m1 / M representa el peso del primer punto (o el último punto, ver cómo implementarlo más adelante), y la matriz de imagen y el kernel se ponderan y se suman mediante el operador de convolución para finalmente obtener el valor de salida.

A continuación, discutiremos el w (i, j, k, l) más crítico. De hecho, w (i, j, k, l) = ws * wr.

 

Escriba la descripción de la imagen aquí

Permítanme hablar primero de WS. El espacio está cerca de la función gaussiana, también llamada dominio de definición. Si observa con atención, en realidad es el modelo de distribución normal del filtrado gaussiano . Representa el modelo matemático de la convolución realizada en un espacio específico. El diagrama esquemático es el siguiente. Si realizamos la convolución de este modelo matemático en toda la imagen, es un filtrado gaussiano ordinario.

       Escriba la descripción de la imagen aquí

 

 

 

Escriba la descripción de la imagen aquí

Hablemos de WR, función gaussiana de similitud de valor de píxel, también llamada dominio de valor o dominio de frecuencia. El diagrama lógico es el siguiente, que significa el valor f (k, l) del punto de entrada actual (k, l) y el punto de salida (i, j) La diferencia entre el valor f (i, j). Cuanto mayor es la diferencia, menor wr tiende a 0; cuanto menor es la diferencia, mayor wr tiende a 1. Si f (i, j) = f (k, l), wr = 1.

Comprensión clave: es comparar la diferencia entre el valor f (k, l) del punto actual y el valor f (i, j) del punto de entrada. En el procesamiento de imágenes, es comparar el valor del píxel y las coordenadas de la coordenada (i, j) como (K, l) valor de píxel para determinar si los bordes son similares.

Escriba la descripción de la imagen aquí

 

 2. Filtrado bilateral en GL

La realización del filtrado bilateral en OpenGL.Shader no radica en algoritmos, sino en ideas. El artículo anterior introdujo el cálculo de múltiples FBO para lograr la reducción de la dimensionalidad del filtro gaussiano, que puede ser difícil de entender. Esta vez, la implementación del filtrado bilateral partirá de la simplicidad y pasará de la simplicidad. Esfuércese por que todos comprendan la idea y, después de obtener el código, puede "cambiarlo usted mismo".

El primero es el sombreador de vértices:

atributo vec4 posición; 
atributo vec4 inputTextureCoordinate; 
const int GAUSSIAN_SAMPLES = 9; 
uniform vec2 singleStepOffset; 
variando vec2 textureCoordinate; 
Variando vec2 blurCoordinates [GAUSSIAN_SAMPLES]; 
 
void main () 

    gl_Position = posición; 
    textureCoordinate = inputTextureCoordinate.xy; 
     
    int multiplicador = 0; 
    vec2 blurStep; 
    para (int i = 0; i <GAUSSIAN_SAMPLES; i ++) 
    { 
        multiplicador = (i - ((GAUSSIAN_SAMPLES - 1) / 2)); 
        blurStep = float (multiplicador) * singleStepOffset; 
        blurCoordinates [i] = inputTextureCoordinate.xy + blurStep; 
    } 
}

El filtrado bilateral es básicamente el mismo que el filtrado gaussiano anterior.También toma 9 puntos de muestreo y reclama directamente un paso de desplazamiento singleStepOffset de vec2. Calcule los vértices de los primeros cuatro pasos y los últimos cuatro pasos del punto de entrada. No hay nada que decir, centrémonos en el sombreador de fragmentos.

Sampler2D uniforme SamplerY; 
sampler2D SamplerU uniforme; 
Sampler2D SamplerV uniforme; 
Sampler2D uniforme SamplerRGB; 
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 = textura2D (SamplerY, pos) .r; 
   yuv.y = texture2D (SamplerU, pos) .r - 0.5; 
   yuv.z = texture2D (SamplerV, pos) .r - 0.5; 
   return colorConversionMatrix * yuv; 

// yuv 转 rgb
const lowp int GAUSSIAN_SAMPLES = 9; 
variando highp vec2 textureCoordinate; 
varying highp vec2 blurCoordinates [GAUSSIAN_SAMPLES]; 
uniform mediump float distanceNormalizationFactor; 
 
void main () 

    lowp vec4 centralColor; // Ingrese el valor de píxel del punto central 
    lowp float gaussianWeightTotal; // peso gaussiano establecido
    lowp vec4 sampleSum; // Convolución y
    lowp vec4 sampleColor ; // punto de muestra valor de píxel
    lowp float gaussianWeight; // punto de muestra peso gaussiano
    lowp float distanceFromCentralColor; 
     
    centralColor = vec4 (yuv2rgb (blurCoordinates [4]), 1.0); 
    gaussianWeightTotal = 0.22; 
    sampleSum = centralColor * 0.22; 
     
    sampleColor = vec4 ( yuv2rgb (blurCoordinates [0]), 1.0); 
    distanceFromCentralColor = min (distancia (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    GaussianWeight = 0.03 * (1.0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    muestraSum + = sampleColor * gaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [1]), 1.0); 
    distanceFromCentralColor = min (distancia (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    GaussianWeight = 0.07 * (1.0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    muestraSum + = sampleColor * gaussianWeight; 
    
    sampleColor = vec4 (yuv2rgb (blurCoordinates [2]), 1.0); 
    distanceFromCentralColor = min (distancia (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    GaussianWeight = 0.12 * (1.0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    muestraSum + = sampleColor * gaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [3]), 1.0); 
    distanceFromCentralColor = min (distancia (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    peso gaussiano = 0.17 * (1.0 - distanciaDeColorCentral); 
    gaussianWeightTotal + = gaussianWeight; 
    muestraSum + = sampleColor * gaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [5]), 1.0); 
    distanceFromCentralColor = min (distancia (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    peso gaussiano = 0.17 * (1.0 - distanciaDeColorCentral); 
    gaussianWeightTotal + = gaussianWeight; 
    MuestraSuma + = MuestraColor * GaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [6]), 1.0); 
    distanceFromCentralColor = min (distancia (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    GaussianWeight = 0.12 * (1.0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    MuestraSuma + = MuestraColor * GaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [7]), 1.0); 
    distanceFromCentralColor = min (distancia (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    GaussianWeight = 0.07 * (1.0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    MuestraSuma + = MuestraColor * GaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [8]), 1.0); 
    distanceFromCentralColor = min (distancia (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    GaussianWeight = 0.03 * (1.0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    muestraSum + = sampleColor * gaussianWeight; 
     
    gl_FragColor = sampleSum / gaussianWeightTotal; 
}

Mirando una sección larga de código de sombreado, en realidad es un conjunto de código de plantilla. Primero mire las dos partes del código:

centralColor = vec4(yuv2rgb(blurCoordinates[4]), 1.0); 
gaussianWeightTotal = 0.22; 
sampleSum = centralColor * 0.22; 

// ... ...

gl_FragColor = sampleSum / gaussianWeightTotal 

Si omitimos la convolución de los 8 puntos de muestreo externos, el valor de salida = el valor de píxel del punto central de entrada, manteniendo el efecto de imagen de entrada original. Luego agregamos la convolución del punto de muestreo, tomando blurCoordinates [3] y blurCoordinates [5] de un singleStepOffset fuera del punto central como ejemplo para analizar la lógica del código del punto de muestreo:

sampleColor = vec4(yuv2rgb(blurCoordinates[3]), 1.0); 
distanceFromCentralColor = min(distance(centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
gaussianWeight = 0.17 * (1.0 - distanceFromCentralColor); 
gaussianWeightTotal += gaussianWeight; 
sampleSum += sampleColor * gaussianWeight; 

La primera línea de código , obtenga el valor de píxel del punto de muestreo.
(Punto clave) La segunda línea de código usa la función incorporada GLSL desatancia para calcular la distancia entre dos vec2 / 3/4, que también puede entenderse como la diferencia entre dos variables. La función de distancia se puede usar para calcular la similitud de dos colores. Cuanto mayor sea el resultado , Cuanto mayor sea la diferencia entre los dos colores, menor será el resultado y menor será la diferencia entre los dos colores. Luego, multiplique por el factor de cuantificación de diferencia de DistanceNormalizationFactor personalizado. ¿Cómo entender este factor? De hecho, es un parámetro modificado que puede cambiar dinámicamente su rango de efecto. Si no lo entiende, puede ver la animación a continuación.

Aquí hay una técnica de depuración para GLSL. El valor que debe depurarse se envía directamente a gl_FragColor para su visualización, y el efecto es el más sencillo de observar con los ojos.

gl_FragColor = vec4( min(distance(centralColor, sampleColor)*distanceNormalizationFactor, 1.0),   0.0, 0.0, 1.0);

// debug.gif


(Sub-énfasis) La tercera línea de código , combinada con la segunda línea de comprensión del código, usa la función incorporada GLSL min, de acuerdo con el significado matemático del filtrado bilateral, calcula la diferencia entre el punto de muestreo y el punto de entrada central, y la normaliza a uno La similitud distanceFromCentralColor, pero una cosa a tener en cuenta es que cuanto más cerca está el valor de píxel del punto de muestreo y el punto de entrada central, más cerca está la distanceFromCentralColor de 0, y más cerca está el peso gaussiano del valor original.
Entonces, el peso involucrado en la convolución = el peso gaussiano original * (1.0-distanceFromCentralColor) Las
líneas cuarta y quinta de código fusionan los pesos involucrados en la convolución y realizan la operación de convolución.

El resto es reescribir varias funciones de GpuBaseFilter, consulte https://github.com/MrZhaozhirong/NativeCppApp                       /src/main/cpp/gpufilter/filter/GpuBilateralBlurFilter.hpp para obtener más detalles 

    void setAdjustEffect(float percent) {
        // 动态调整色值阈值参数
        mThreshold_ColorDistanceNormalization = range(percent*100.0f, 10.0f, 1.0f);
    }

    void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
                void* positionCords, void* textureCords)
    {
        if (!mIsInitialized)
            return;
        glUseProgram(getProgram());
        // 把step offset的步伐直接用vec2表示,其值直接输入1/w,1/h
        glUniform2f(mSingleStepOffsetLocation, 1.0f/mOutputWidth, 1.0f/mOutputHeight);
        glUniform1f(mColorDisNormalFactorLocation, mThreshold_ColorDistanceNormalization);
        // 绘制的模板代码
        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);
    }

 

Déjame hablar de otro punto de conocimiento:

En GpuGaussianBlurFilter, el kernel de convolución gaussiano se pasa al Shader desde el código externo, y su valor es

convolutionKernel = new GLfloat [9] {                 0.0947416f, 0.118318f, 0.0947416f,                 0.118318f, 0.147761f, 0.118318f,                 0.0947416f, 0.118318f, 0.0947416f,         };



En GpuGaussianBlurFilter2, el kernel gaussiano no se importa desde el exterior, sino que se escribe directamente en el Shader.Sus valores son:
0.05 , 0.09, 0.12, 0.15, 0.18, 0.15, 0.12, 0.09, 0.05

En este GpuBilateralBlurFilter, no sé que hay un pequeño socio que no prestó atención, que el kernel de Gauss está escrito directamente Shader entre cuyo valor se encuentra:
0.03,0.07,0.12,0.17,0.22,0.17,0.12,0.07,0.03

Paso a paso para aumentar el peso del valor central y reducir el peso del borde, ¿pienso por qué?

Supongo que te gusta

Origin blog.csdn.net/a360940265a/article/details/109171054
Recomendado
Clasificación