Android OpenGL ES - 卷积矩阵

PS
上一篇我们说了一些简单的滤镜,明度、对比度、曝光度等等
那么本篇我们来说一下稍微复杂一点的卷积,理解卷积对图片处理还是十分重要的

卷积

从数学上讲,卷积就是一种运算,与减加乘除没有本质的区别的一种运算。
就像我们可以通过A+B的运算来计算A与B的和一样,简单的加减乘除运算符可以看成混合运算符两边元素的信息,我们可以认为卷积运算也是一种混合信息的手段

图像卷积

我们刚才说了卷积可以看成一种混合信息的手段,我们来看一下图像卷积

假如我们有下面一幅图像
Android OpenGL ES - 卷积矩阵_第1张图片
这幅图像不太清晰,存在很多噪点。

PS
噪点又称 图像噪声(image noise)是 图像中一种亮度或颜色信息的随机变化(被拍摄物体本身并没有),通常是电子噪声的表现。它一般是由扫描仪或数码相机的传感器和电路产生的,也可能是受胶片颗粒或者理想光电探测器中不可避免的的 散粒噪声影响产生的。图像噪声是图像拍摄过程中不希望存在的副产品,给图像带来了错误和额外的信息。

这些噪点,就好像平地耸立的山峰:
Android OpenGL ES - 卷积矩阵_第2张图片

我们要做的就是把山峰刨掉一些土,填到山峰周围去。用数学的话来说,就是把山峰周围的高度平均一下。

那我们该如何做到这样呢?

图像卷积运算

在计算机上我们都知道图像可以用一个二维矩阵来存储,其存储的内容(像素)又对应手机/电脑 硬件屏幕上的点。

有噪点的原图,我们可以把它转为一个矩阵:
Android OpenGL ES - 卷积矩阵_第3张图片
来平均一下上面的矩阵

假如我要平均一下a1,1点,运算过程如下

Android OpenGL ES - 卷积矩阵_第4张图片

如下图我们使用下面这个卷积模板进行图像卷积运算,其结果就如动图那样,每个像素值都进行了加权平均运算。
静态的图可能看起来没那么直观,我们来个动图

假设我们有个矩阵Android OpenGL ES - 卷积矩阵_第5张图片与原图进行卷积运算

PS
不同的矩阵会产生不同的卷积效果,因此有些地方把这个矩阵称为卷积核

过程如下

Android OpenGL ES - 卷积矩阵_第6张图片

卷积应用

最经典的卷积应用就是图像锐化和模糊的处理。在移动设备上使用GPU做图像锐化,一般就是利用空域滤波器对图像做模板卷积处理。

锐化

拉普拉斯算法比较适合用于改善图像模糊,是比较常用的边缘增强处理算子。
Android OpenGL ES - 卷积矩阵_第7张图片

顶点着色器Vertex Shader

attribute vec4 position;
attribute vec4 inputTextureCoordinate;

uniform float imageWidthFactor;  //  屏幕宽度步长因子
uniform float imageHeightFactor;  //  屏幕高度步长因子
uniform float sharpness;  // 锐化核心值,由外层用户输入

varying vec2 textureCoordinate; // 当前纹理坐标
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying float centerMultiplier; // Laplacian算子中心值
varying float edgeMultiplier; // Laplacian算子边缘值
void main()
{
    gl_Position = position;
    vec2 widthStep = vec2(imageWidthFactor, 0.0);
    vec2 heightStep = vec2(0.0, imageHeightFactor);
    textureCoordinate = inputTextureCoordinate.xy;
    leftTextureCoordinate = inputTextureCoordinate.xy - widthStep;
    rightTextureCoordinate = inputTextureCoordinate.xy + widthStep;
    topTextureCoordinate = inputTextureCoordinate.xy + heightStep;
    bottomTextureCoordinate = inputTextureCoordinate.xy - heightStep;
    centerMultiplier = 1.0 + 4.0 * sharpness;
    edgeMultiplier = sharpness;
}

这次的GLSL明显和前几次不太一样了,前几次都是使用默认的Vertex Shader,那么这次我们怎么理解那四个 left/top/right/bottom的纹理坐标?还有那两个屏幕步长因子,我们再来看看上层代码的输入:


@Override
public void onInit() {
     super.onInit();
     sharpnessLocation = GLES20.glGetUniformLocation(getProgram(), "sharpness");
     imageWidthFactorLocation = GLES20.glGetUniformLocation(getProgram(), "imageWidthFactor");
     imageHeightFactorLocation = GLES20.glGetUniformLocation(getProgram(), "imageHeightFactor");
}

@Override
public void onOutputSizeChanged(int width, int height) {
     super.onOutputSizeChanged(width, height);
     setFloat(imageWidthFactorLocation, 1.0f / width);
     setFloat(imageHeightFactorLocation, 1.0f / height);
}

屏幕步长是 当前屏幕宽高的倒数,也就是按照当前屏幕的像素个数分割开来。以当前纹理坐标对应laplacian算子的中心核,左右偏移1/width=1个像素的位置,就得出laplacian算子核心的其余外围8格的像素点。如下图所示理解

Android OpenGL ES - 卷积矩阵_第8张图片

因为我选用了快速拉式变换,所以L-T,R-T,L-B,R-B的位置就没算出来,因为其laplacian的因子为0相乘结果也为0,没必要浪费顶点着色器的的输出变量。

片段着色器Fragment Shader

precision highp float;
varying highp vec2 textureCoordinate;
varying highp vec2 leftTextureCoordinate;
varying highp vec2 rightTextureCoordinate;
varying highp vec2 topTextureCoordinate;
varying highp vec2 bottomTextureCoordinate;
varying highp float centerMultiplier;
varying highp float edgeMultiplier;
uniform sampler2D inputImageTexture;
void main()
{
     mediump vec3 textureColor = texture2D(inputImageTexture, textureCoordinate).rgb;
     mediump vec3 leftTextureColor = texture2D(inputImageTexture, leftTextureCoordinate).rgb;
     mediump vec3 rightTextureColor = texture2D(inputImageTexture, rightTextureCoordinate).rgb;
     mediump vec3 topTextureColor = texture2D(inputImageTexture, topTextureCoordinate).rgb;
     mediump vec3 bottomTextureColor = texture2D(inputImageTexture, bottomTextureCoordinate).rgb;
     gl_FragColor = vec4((textureColor * centerMultiplier - (leftTextureColor * edgeMultiplier + rightTextureColor * edgeMultiplier + topTextureColor * edgeMultiplier + bottomTextureColor * edgeMultiplier)), texture2D(inputImageTexture, bottomTextureCoordinate).w);
};

上效果

小结

本篇我们介绍了卷积,作为图片处理的基础知识,卷积在之后的图像处理中还是会经常用到的

Github代码

你可能感兴趣的:(opengl-es)