OpenGL.Shader: Zhige учит писать фильтр живого клиента (12) Визуальный фильтр: реализация принципа двусторонней фильтрации отбеливания кожи

OpenGL.Shader: Чжигэ учит писать клиент с живым фильтром (12)

 

Работа и жизнь слишком загружены, и я опустошил свой блог на какое-то время. Только недавно у меня появилось время продолжить изучение фильтров. В этой статье представлен более популярный эффект дермабразии с эффектом фильтрации - простое обучение двусторонней фильтрации.

1. Что такое двусторонняя фильтрация?

Давайте посмотрим на более официальное объяснение: Двусторонний фильтр - это метод нелинейной фильтрации, который сочетает в себе пространственную близость изображения и схожесть значения пикселей. Он также учитывает пространственную информацию и оттенки серого. Сходство для достижения цели сохранения кромок и удаления шума. Он простой, неитеративный и частичный.

С первого взгляда вы можете не понять «нелинейность». Фактически, средний фильтр / фильтр Гаусса, представленный выше, является линейным фильтром, который понимается просто как: для всех значений пикселей изображения используется одна и та же матрица фильтра, а свертка выполняется в соответствии с фиксированным соотношением весовых коэффициентов. Нелинейная фильтрация заключается в добавлении параметра для определения того, похожи ли соседние пиксели, так что весовой коэффициент в процессе свертки больше не является фиксированным соотношением, а динамически регулируется по запросу.

После объединения приведенного выше понимания разговорного языка следующая международная практика начинается с формулы двусторонней фильтрации.

Напишите здесь описание изображения


g (i, j) представляет выходной результат;
S (i, j) относится к (2N + 1) (2N + 1) операции свертки с центром на (i, j); N - радиус матрицы свертки Длина
(k, l) представляет входную точку (точки) в диапазоне; f (k, l) - значение, соответствующее точке (k, l).
w (i, j, k, l) представляет значение, вычисленное двумя функциями Гаусса (примечание: это не окончательный вес)

Преобразуем приведенную выше формулу, предполагая, что w (i, j, k, l) в формуле равно m, тогда
Напишите здесь описание изображения

Пусть m1 + m2 + m3… + mn = M, тогда мы
Напишите здесь описание изображения
видим, что это, очевидно, операция свертки матрицы изображения и ядра. Среди них m1 / M представляет вес первой точки (или последней точки, см., Как это реализовать позже), а матрица изображения и ядро ​​взвешиваются и суммируются с помощью оператора свертки, чтобы окончательно получить выходное значение.

Далее мы обсудим наиболее критический w (i, j, k, l). Фактически, w (i, j, k, l) = ws * wr.

 

Напишите здесь описание изображения

Позвольте мне сначала поговорить о WS, пространство близко к функции Гаусса, также называемой областью определения.Если вы внимательно посмотрите, на самом деле это модель нормального распределения для фильтрации Гаусса . Он представляет собой математическую модель свертки, выполняемой в определенном пространстве.Схема выглядит следующим образом.Если мы выполняем свертку этой математической модели на всем изображении, это будет обычная фильтрация Гаусса.

       Напишите здесь описание изображения

 

 

 

Напишите здесь описание изображения

Давайте поговорим о WR, гауссовой функции сходства значений пикселей, также называемой областью значений или частотной областью. Логическая диаграмма выглядит следующим образом, что означает текущую точку входа (k, l), значение f (k, l) и точку выхода (i, j). Разница между значением f (i, j). Чем больше разница, тем меньше wr стремится к 0, чем меньше разница, тем больше wr стремится к 1. Если f (i, j) = f (k, l), wr = 1.

Ключевое понимание: это сравнение разницы между значением f (k, l) текущей точки и значением f (i, j) входной точки. При обработке изображения это означает сравнение значения пикселя и координаты координаты (i, j) как (K, l) значение пикселя, чтобы определить, похожи ли края.

Напишите здесь описание изображения

 

 2. Двусторонняя фильтрация в GL

Реализация двусторонней фильтрации на OpenGL.Shader заключается не в алгоритмах, а в идеях. В предыдущей статье был представлен расчет нескольких FBO для достижения уменьшения размерности гауссовского фильтра, что может быть трудно понять. На этот раз реализация двусторонней фильтрации начнется с простоты и уйдет с простоты. Стремитесь, чтобы все поняли идею, и после получения кода вы можете «изменить его самостоятельно».

Первый - вершинный шейдер:

позиция атрибута vec4; 
атрибут vec4 inputTextureCoordinate; 
const int GAUSSIAN_SAMPLES = 9; 
uniform vec2 singleStepOffset; 
варьирующаяся vec2 textureCoordinate; 
варьирующиеся vec2 blurCoordinates [GAUSSIAN_SAMPLES]; 
 
void main () 

    gl_Position = position; 
    textureCoordinate = inputTextureCoordinate.xy; 
     
    int multiplier = 0; 
    vec2 blurStep; 
    for (int i = 0; i <GAUSSIAN_SAMPLES; i ++) 
    { 
        multiplier = (i - ((GAUSSIAN_SAMPLES - 1) / 2)); 
        blurStep = float (множитель) * singleStepOffset; 
        blurCoordinates [i] = inputTextureCoordinate.xy + blurStep; 
    } 
}

Двусторонняя фильтрация в основном такая же, как и предыдущая фильтрация по Гауссу. Она также принимает 9 точек выборки и напрямую требует шаг смещения singleStepOffset равный vec2. Вычислите вершины первых четырех шагов и последних четырех шагов входной точки. Сказать нечего, остановимся на фрагментном шейдере.

однородный пробоотборник 2D SamplerY; 
однородный сэмплер2D SamplerU; 
равномерный пробоотборник 2D SamplerV; 
унифицированный сэмплер 2D 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 = texture2D (SamplerY, pos) .r; 
   yuv.y = texture2D (SamplerU, pos) .r - 0,5; 
   yuv.z = texture2D (SamplerV, pos) .r - 0,5; 
   вернуть colorConversionMatrix * yuv; 

// yuv 转 rgb
const lowp int GAUSSIAN_SAMPLES = 9; 
различная текстура highp vec2 textureCoordinate; 
varing highp vec2 blurCoordinates [GAUSSIAN_SAMPLES]; 
uniform mediump float distanceNormalizationFactor; 
 
void main () 

    lowp vec4 centralColor; // Ввод значения в пикселях центральной точки 
    lowp float gaussianWeightTotal; // Гауссовский вес установлен
    lowp vec4 sampleSum; //
    Выборка свертки и lowpolC ; // значение пикселя точки выборки
    lowp float gaussianWeight; //
    Гауссов вес точки выборки lowp float distanceFromCentralColor; 
     
    centralColor = vec4 (yuv2rgb (blurCoordinates [4]), 1.0); 
    gaussianWeightTotal = 0.22; 
    sampleSum = centralColor * 0.22; 
     
    sampleSum = vec4Color yuv2rgb (blurCoordinates [0]), 1.0); 
    distanceFromCentralColor = min (distance (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    gaussianWeight = 0,03 * (1,0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    sampleSum + = sampleColor * gaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [1]), 1.0); 
    distanceFromCentralColor = min (distance (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    gaussianWeight = 0,07 * (1,0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    sampleSum + = sampleColor * gaussianWeight; 
    
    sampleColor = vec4 (yuv2rgb (blurCoordinates [2]), 1.0); 
    distanceFromCentralColor = min (distance (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    gaussianWeight = 0,12 * (1,0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    sampleSum + = sampleColor * gaussianWeight; 
     
    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; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [5]), 1.0); 
    distanceFromCentralColor = min (distance (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    gaussianWeight = 0,17 * (1,0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    sampleSum + = sampleColor * gaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [6]), 1.0); 
    distanceFromCentralColor = min (distance (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    gaussianWeight = 0,12 * (1,0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    sampleSum + = sampleColor * gaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [7]), 1.0); 
    distanceFromCentralColor = min (distance (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    gaussianWeight = 0,07 * (1,0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    sampleSum + = sampleColor * gaussianWeight; 
     
    sampleColor = vec4 (yuv2rgb (blurCoordinates [8]), 1.0); 
    distanceFromCentralColor = min (distance (centralColor, sampleColor) * distanceNormalizationFactor, 1.0); 
    gaussianWeight = 0,03 * (1,0 - distanceFromCentralColor); 
    gaussianWeightTotal + = gaussianWeight; 
    sampleSum + = sampleColor * gaussianWeight; 
     
    gl_FragColor = sampleSum / gaussianWeightTotal; 
}

Если посмотреть на длинный фрагмент кода шейдера, то на самом деле это набор кода шаблона. Сначала посмотрите на две части кода:

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

// ... ...

gl_FragColor = sampleSum / gaussianWeightTotal 

Если мы опускаем свертку внешних 8 точек выборки, выходное значение = значение пикселя входной центральной точки с сохранением эффекта исходного входного изображения. Затем мы добавляем свертку точки выборки, взяв blurCoordinates [3] и blurCoordinates [5] из singleStepOffset вне центральной точки в качестве примера для анализа логики кода точки выборки:

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; 

В первой строке кода получаем значение пикселя точки выборки.
(Ключевой момент) Во второй строке кода используется встроенная функция GLSL disatance для расчета расстояния между двумя vec2 / 3/4, которое также можно понимать как разницу между двумя переменными. Функция расстояния может использоваться для вычисления сходства двух цветов. Чем больше результат , Чем больше разница между двумя цветами, тем меньше результат и меньше разница между двумя цветами. Затем умножьте на настраиваемый коэффициент квантования разности distanceNormalizationFactor. Как понять этот фактор? Фактически, это модифицированный параметр, который может динамически изменять диапазон своего действия. Если вы не понимаете, вы можете увидеть анимацию ниже.

Вот метод отладки для GLSL.Значение, которое необходимо отладить, напрямую выводится в gl_FragColor для отображения, и эффект проще всего наблюдать глазами.

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

// debug.gif


(Дополнительный акцент) Третья строка кода в сочетании со второй строкой понимания кода использует встроенную функцию GLSL min, в соответствии с математическим значением двусторонней фильтрации, вычисляет разницу между точкой выборки и центральной входной точкой и нормализует ее до единицы. Сходство distanceFromCentralColor, но следует отметить, что чем ближе значение пикселя точки выборки и центральной входной точки, тем ближе distanceFromCentralColor к 0 и тем ближе вес по Гауссу к исходному значению.
Следовательно, вес, участвующий в свертке, = исходный гауссов вес * (1.0-distanceFromCentralColor),
четвертая и пятая строки кода объединяют веса, участвующие в свертке, и выполняют операцию свертки.

Остальное - это переписать несколько функций                       GpuBaseFilter, подробности см. На  https://github.com/MrZhaozhirong/NativeCppApp /src/main/cpp/gpufilter/filter/GpuBateralBlurFilter.hpp.

    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);
    }

 

Позвольте мне поговорить о другом моменте знания:

В GpuGaussianBlurFilter ядро ​​гауссовой свертки передается в шейдер из внешнего кода, и его значение равно

convolutionKernel = new GLfloat [9] {                 0,0947416f, 0,118318f, 0,0947416f,                 0,118318f, 0,147761f, 0,118318f,                 0,0947416f, 0,118318f, 0,0947416f,         };



В GpuGaussianBlurFilter2 ядро ​​Гаусса не импортируется извне, а записывается непосредственно в шейдере. Его значения:
0,05 , 0,09, 0,12, 0,15, 0,18, 0,15, 0,12, 0,09, 0,05.

В этом GpuBateralBlurFilter, не знаю, есть небольшой партнер, не обращал внимания, что гауссовское ядро ​​написано непосредственно шейдером, среди которого значение:
0,03,0.07,0.12,0.17,0.22,0.17,0.12,0.07,0.03

Я шаг за шагом увеличиваю вес основной ценности и уменьшаю вес края, подумайте, почему?

рекомендация

отblog.csdn.net/a360940265a/article/details/109171054