PS
上一篇我们说了一些简单的滤镜,明度、对比度、曝光度等等
那么本篇我们来说一下稍微复杂一点的卷积,理解卷积对图片处理还是十分重要的
卷积
从数学上讲,卷积就是一种运算,与减加乘除没有本质的区别的一种运算。
就像我们可以通过A+B的运算来计算A与B的和一样,简单的加减乘除运算符可以看成混合运算符两边元素的信息,我们可以认为卷积运算也是一种混合信息的手段
图像卷积
我们刚才说了卷积可以看成一种混合信息的手段,我们来看一下图像卷积
PS
噪点又称 图像噪声(image noise)是 图像中一种亮度或颜色信息的随机变化(被拍摄物体本身并没有),通常是电子噪声的表现。它一般是由扫描仪或数码相机的传感器和电路产生的,也可能是受胶片颗粒或者理想光电探测器中不可避免的的 散粒噪声影响产生的。图像噪声是图像拍摄过程中不希望存在的副产品,给图像带来了错误和额外的信息。
我们要做的就是把山峰刨掉一些土,填到山峰周围去。用数学的话来说,就是把山峰周围的高度平均一下。
那我们该如何做到这样呢?
图像卷积运算
在计算机上我们都知道图像可以用一个二维矩阵来存储,其存储的内容(像素)又对应手机/电脑 硬件屏幕上的点。
有噪点的原图,我们可以把它转为一个矩阵:
来平均一下上面的矩阵
假如我要平均一下a1,1点,运算过程如下
如下图我们使用下面这个卷积模板进行图像卷积运算,其结果就如动图那样,每个像素值都进行了加权平均运算。
静态的图可能看起来没那么直观,我们来个动图
PS
不同的矩阵会产生不同的卷积效果,因此有些地方把这个矩阵称为卷积核
过程如下
卷积应用
最经典的卷积应用就是图像锐化和模糊的处理。在移动设备上使用GPU做图像锐化,一般就是利用空域滤波器对图像做模板卷积处理。
锐化
拉普拉斯算法比较适合用于改善图像模糊,是比较常用的边缘增强处理算子。
顶点着色器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);
};
上效果
小结
本篇我们介绍了卷积,作为图片处理的基础知识,卷积在之后的图像处理中还是会经常用到的