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

卷积

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

图像卷积

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

假如我们有下面一幅图像
image
这幅图像不太清晰,存在很多噪点。

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

这些噪点,就好像平地耸立的山峰:
image

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

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

图像卷积运算

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

有噪点的原图,我们可以把它转为一个矩阵:
image
然我们我们用下面这个矩阵
image.png
来平均一下上面的矩阵

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

image

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

假设我们有个矩阵image.png与原图进行卷积运算

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

过程如下

image image

卷积应用

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

锐化

拉普拉斯算法比较适合用于改善图像模糊,是比较常用的边缘增强处理算子。
image

顶点着色器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格的像素点。如下图所示理解

因为我选用了快速拉式变换,所以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);
};

上效果

image

小结

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

Github代码


YFan
272 声望923 粉丝

引用和评论

0 条评论