目录

  1. 高斯模糊的原理

  2. GPUImage模糊的实现分析

  3. 高斯模糊优化

一、高斯模糊的原理

这一小节会涉及到一些数学中基本概念,正态分布、高斯函数、卷积、模糊半径等,通过下面的学习实践我们对其进行回顾学习。

"模糊",可以理解成每一个像素都取周边像素的平均值,模糊分类有很多种,我们来看下均值模糊和高斯模糊。

均值模糊是每个像素的值都取周边元素的平均值,并且周边没有点不管距离当前点的距离远近,权重相同

均值模糊可以实现模糊效果,但是如果模糊后的效果看起来和原图效果更相近,就要考虑权重的问题,即距离越近的点权重越大,距离越远的点权重越小。

正态分布是一种权重分配模式,越接近中心,取值越大,越远离中心,取值越小。

图片是二维的,对应的是二维正态分布,正态分布的密度函数叫做"高斯函数"(Gaussian function)

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

有了高斯函数,我们就可以计算每个点的权重。
假设模糊半径是1,构建一个3x3的矩阵,假设高斯函数的σ为1.5,根据xy的坐标值计算每一个点的权重值,然后所有点权重值相加应该为1,所以对上述计算后的值进行归一化处理。

有了归一化的权重矩阵,把其作为卷积核,与原有图片进行卷积运算,得出模糊后的值。

高斯模糊 是一个低通滤波,过滤掉高频信号,剩下低频信号,图像内容的边界去掉 ,实现blur

二、GPUImage高斯模糊的实现分析

了解了高斯模糊的原理,这一小节我们看下如何实现高斯模糊,GPUImage是一个非常强大和丰富的OpenGL图像处理开源库,其中带了部分滤镜的实现 ,对应的高斯模糊滤镜 为GPUImageGaussianBlurFilter,我们分析下它是如何实现的。

//顶点着色器attribute vec4 position;
attribute vec4 inputTextureCoordinate;const int GAUSSIAN_SAMPLES = 9;uniform float texelWidthOffset;
uniform float texelHeightOffset;varying vec2 textureCoordinate;
varying vec2 blurCoordinates[GAUSSIAN_SAMPLES];void main()
{gl_Position = position;textureCoordinate = inputTextureCoordinate.xy;// Calculate the positions for the blurint multiplier = 0;vec2 blurStep;vec2 singleStepOffset = vec2(texelHeightOffset, texelWidthOffset);for (int i = 0; i < GAUSSIAN_SAMPLES; i++){multiplier = (i - ((GAUSSIAN_SAMPLES - 1) / 2));// Blur in x (horizontal)blurStep = float(multiplier) * singleStepOffset;blurCoordinates[i] = inputTextureCoordinate.xy + blurStep;}
}
//片源着色器uniform sampler2D inputImageTexture;const lowp int GAUSSIAN_SAMPLES = 9;varying highp vec2 textureCoordinate;
varying highp vec2 blurCoordinates[GAUSSIAN_SAMPLES];void main()
{lowp vec3 sum = vec3(0.0);lowp vec4 fragColor=texture2D(inputImageTexture,textureCoordinate);sum += texture2D(inputImageTexture, blurCoordinates[0]).rgb * 0.05;sum += texture2D(inputImageTexture, blurCoordinates[1]).rgb * 0.09;sum += texture2D(inputImageTexture, blurCoordinates[2]).rgb * 0.12;sum += texture2D(inputImageTexture, blurCoordinates[3]).rgb * 0.15;sum += texture2D(inputImageTexture, blurCoordinates[4]).rgb * 0.18;sum += texture2D(inputImageTexture, blurCoordinates[5]).rgb * 0.15;sum += texture2D(inputImageTexture, blurCoordinates[6]).rgb * 0.12;sum += texture2D(inputImageTexture, blurCoordinates[7]).rgb * 0.09;sum += texture2D(inputImageTexture, blurCoordinates[8]).rgb * 0.05;gl_FragColor = vec4(sum,fragColor.a);
}

通过着色器代码我们看到GAUSSIAN_SAMPLES = 9;左右个4个采样,加中心点1个采样点,即 2x4+1=9,是一个9x9的矩阵。
blurCoordinates存储计算后的纹理的坐标值。然后在片源着色器中进行卷积运算。

GPUImage采用了分别对X轴和Y轴的高斯模糊,这样降低了算法的复杂度。

高斯滤波器的卷积核是二维的(mn),则算法复杂度为O(mnMN),复杂度较高,算法复杂度变为O(2mM*N)

Render如下

public class GPUImageRender implements GLSurfaceView.Renderer {private Context context;private int inputTextureId;private GPUImageGaussianBlurFilter blurFilter;private  FloatBuffer glCubeBuffer;private  FloatBuffer glTextureBuffer;public static final float CUBE[] = {-1.0f, -1.0f,1.0f, -1.0f,-1.0f, 1.0f,1.0f, 1.0f,};public static final float TEXTURE_NO_ROTATION[] = {0.0f, 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f, 0.0f,};public GPUImageRender(Context context) {this.context = context;}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {String vertexStr = ShaderHelper.loadAsset(context.getResources(), "blur_vertex_gpuimage.glsl");String fragStr = ShaderHelper.loadAsset(context.getResources(), "blur_frag_gpuimage.glsl");blurFilter = new GPUImageGaussianBlurFilter(vertexStr,fragStr);blurFilter.ifNeedInit();inputTextureId = TextureHelper.loadTexture(context, R.drawable.bg);glCubeBuffer = ByteBuffer.allocateDirect(CUBE.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();glCubeBuffer.put(CUBE).position(0);glTextureBuffer = ByteBuffer.allocateDirect(TEXTURE_NO_ROTATION.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();glTextureBuffer.put(TEXTURE_NO_ROTATION).position(0);}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {GLES20.glViewport(0, 0, width, height);blurFilter.onOutputSizeChanged(width,height);}@Overridepublic void onDrawFrame(GL10 gl) {GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);GLES20.glClearColor(0f,0f,0f,1f);blurFilter.onDraw(inputTextureId,glCubeBuffer,glTextureBuffer);}
}public class GPUImageTwoPassFilter extends GPUImageFilterGroup {public GPUImageTwoPassFilter(String firstVertexShader, String firstFragmentShader,String secondVertexShader, String secondFragmentShader) {super(null);addFilter(new GPUImageFilter(firstVertexShader, firstFragmentShader));addFilter(new GPUImageFilter(secondVertexShader, secondFragmentShader));}
}public GPUImageGaussianBlurFilter(float blurSize,String vertexStr,String fragStr) {super(vertexStr, fragStr, vertexStr, fragStr);this.blurSize = blurSize;}

其中用到上一篇谈到的FBO技术

//com.av.mediajourney.opengl.gpuimage.GPUImageFilterGroup#onDrawpublic void onDraw(final int textureId, final FloatBuffer cubeBuffer,final FloatBuffer textureBuffer) {runPendingOnDrawTasks();if (!isInitialized() || frameBuffers == null || frameBufferTextures == null) {return;}if (mergedFilters != null) {int size = mergedFilters.size();int previousTextureId = textureId;for (int i = 0; i < size; i++) {GPUImageFilter filter = mergedFilters.get(i);boolean isNotLast = i < size - 1;//如果不是最后一个,则采用FBO方式,进行离屏渲染;否则不挂载到FBO,直接渲染到屏幕if (isNotLast) {GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[i]);GLES20.glClearColor(0, 0, 0, 0);}//第一个filter,采用输入的纹理id、顶点buffer、纹理bufferif (i == 0) {filter.onDraw(previousTextureId, cubeBuffer, textureBuffer);} else if (i == size - 1) {filter.onDraw(previousTextureId, glCubeBuffer, (size % 2 == 0) ? glTextureFlipBuffer : glTextureBuffer);} else {filter.onDraw(previousTextureId, glCubeBuffer, glTextureBuffer);}//如果不是最后一个filter,则解绑FBO,并且把当前的输出作为下一个filter的纹理输入if (isNotLast) {GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);previousTextureId = frameBufferTextures[i];}}}}

高斯模糊后的效果如下:

三、高斯模糊优化

在保证模糊效果的前提下,怎么样可以提升模糊的效率,即减少耗时,直接的影响因素就是运算量的大小,可以从下面几个方向进行优化:

  1. 减少偏移大小(模糊半径)

  2. 优化算法实现

  3. 先缩放图片,再进行高斯模糊,减少需要处理的数据量

  4. 了解GPU运行方式,减少分支语句,使用opengl3.0等

** 减少偏移大小(模糊半径)和优化算法实现见glsl

//顶点着色器
attribute vec4 position;
attribute vec4 inputTextureCoordinate;//const int GAUSSIAN_SAMPLES = 9;//优化点:高斯算子的左右偏移,对应的高斯算子为(SHIFT_SIZE*2+1)
const int SHIFT_SIZE =2;uniform float texelWidthOffset;
uniform float texelHeightOffset;varying vec2 textureCoordinate;
varying vec4 blurCoordinates[SHIFT_SIZE];void main()
{gl_Position = position;textureCoordinate = inputTextureCoordinate.xy;//偏移步距vec2 singleStepOffset = vec2(texelHeightOffset, texelWidthOffset);//  int multiplier = 0;//  vec2 blurStep;////  for (int i = 0; i < GAUSSIAN_SAMPLES; i++)//  {//      multiplier = (i - ((GAUSSIAN_SAMPLES - 1) / 2));//      // Blur in x (horizontal)//      blurStep = float(multiplier) * singleStepOffset;//      blurCoordinates[i] = inputTextureCoordinate.xy + blurStep;//  }// 优化点:减少循环运算次数for (int i=0; i< SHIFT_SIZE; i++){blurCoordinates[i] = vec4(textureCoordinate.xy - float(i+1)*singleStepOffset,textureCoordinate.xy + float(i+1)*singleStepOffset);}}
//片源着色器
uniform sampler2D inputImageTexture;//const int GAUSSIAN_SAMPLES = 9;//优化点:高斯算子的左右偏移,对应的高斯算子为(SHIFT_SIZE*2+1)
const int SHIFT_SIZE =2;varying highp vec2 textureCoordinate;
varying vec4 blurCoordinates[SHIFT_SIZE];void main()
{/*lowp vec3 sum = vec3(0.0);lowp vec4 fragColor=texture2D(inputImageTexture,textureCoordinate);mediump vec3 sum = fragColor.rgb*0.18;sum += texture2D(inputImageTexture, blurCoordinates[0]).rgb * 0.05;sum += texture2D(inputImageTexture, blurCoordinates[1]).rgb * 0.09;sum += texture2D(inputImageTexture, blurCoordinates[2]).rgb * 0.12;sum += texture2D(inputImageTexture, blurCoordinates[3]).rgb * 0.15;sum += texture2D(inputImageTexture, blurCoordinates[4]).rgb * 0.18;sum += texture2D(inputImageTexture, blurCoordinates[5]).rgb * 0.15;sum += texture2D(inputImageTexture, blurCoordinates[6]).rgb * 0.12;sum += texture2D(inputImageTexture, blurCoordinates[7]).rgb * 0.09;sum += texture2D(inputImageTexture, blurCoordinates[8]).rgb * 0.05;gl_FragColor = vec4(sum,fragColor.a);*/// 计算当前坐标的颜色值vec4 currentColor = texture2D(inputTexture, textureCoordinate);mediump vec3 sum = currentColor.rgb;// 计算偏移坐标的颜色值总和for (int i = 0; i < SHIFT_SIZE; i++) {sum += texture2D(inputTexture, blurShiftCoordinates[i].xy).rgb;sum += texture2D(inputTexture, blurShiftCoordinates[i].zw).rgb;}// 求出平均值gl_FragColor = vec4(sum * 1.0 / float(2 * SHIFT_SIZE + 1), currentColor.a);}

** 先缩放图片,再进行高斯模糊,减少需要处理的数据量**

    private static Bitmap getBitmap(Context context, int resourceId) {final BitmapFactory.Options options = new BitmapFactory.Options();options.inScaled = false;// Read in the resourceBitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);//优化点:对原图进行缩放,1/16的数据量 ,缩放大小根据具体场景而定bitmap = Bitmap.createScaledBitmap(bitmap,bitmap.getWidth() / 4,bitmap.getHeight() / 4,true);return bitmap;}

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

音视频开发(三十二):GPUImage高斯模糊的实现与优化相关推荐

  1. 即时通讯音视频开发(十八):详解音频编解码的原理、演进和应用选型

    1.引言 大家好,我是刘华平,从毕业到现在我一直在从事音视频领域相关工作,也有一些自己的创业项目,曾为早期Google Android SDK多媒体架构的构建作出贡献. 就音频而言,无论是算法多样性, ...

  2. 即时通讯音视频开发(十):实时语音通讯的回音消除技术详解

    前言 即时通讯应用中的实时音视频技术,几乎是IM开发中的最后一道高墙.原因在于:实时音视频技术 = 音视频处理技术 + 网络传输技术 的横向技术应用集合体,而公共互联网不是为了实时通信设计的.有关实时 ...

  3. 即时通讯音视频开发(十四):实时音视频数据传输协议介绍

    概述 随着移动互联网的快速发展以及智能终端性能的逐步提高,智能终端间进行实时音视频通讯成为移动互联网发展的一个重要方向.那么如何保证智能终端之间实时音视频数据通讯成为一个很现实的问题. 实际上,实时音 ...

  4. Android 音视频开发(三) -- Camera2 实现预览、拍照功能

    音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...

  5. 音视频开发总结之二Android平台相关

    一. 音视频采集流程 串联整个音视频录制流程,完成音视频的采集.编码.封包成 mp4 输出. 通过摄像头和麦克风获得实时的音视频数据: 播放流程: 获取流->解码->播放. 录制播放路程: ...

  6. 音视频开发(十九):运算符重载、继承、多态、模版

    目录 类和对象的重要知识点 运算符重载 继承 多态 模版 一.类和对象的重要知识点 1.1 深拷贝与浅拷贝 浅拷贝:简单的赋值拷贝操作,拷贝构造 深拷贝:在堆区重新申请空间,进行拷贝操作 1.2 th ...

  7. 音视频开发(十四):OpenGL 与 OpenGL ES2区别

    什么是OpenGL ES? OpenGL(全写Open Graphics Library)是指定义了一个跨编程语言.跨平台的编程接口规格的专业的图形程序接口.它用于三维图像(二维的亦可),是一个功能强 ...

  8. 音视频开发基础(二)常用的直播协议

    如今网络的发展已经从1G,2G,3G,4G逐渐过渡到5G,5G网络的出现,音视频的使用就变得越来越广.从以前的泛娱乐过渡到了更深次的领域,比如在线教育,在线会议-然而,要想有完美的视屏体验,还是要从很 ...

  9. 音视频开发三:FFmpeg安装与常用命令

    前言:在不同平台下安装FFmpeg 1.mac系统下安装ffmpeg. 打开terminal,运行:brew install ffmpeg 安装完后,terminal中输入:ffmpeg,出现下面提示 ...

  10. STM32CubeIDE开发(三十二), stm32人工智能开发应用实践(Cube.AI).篇二

    一.事有蹊跷 接篇一,前面提到在使用cube.AI生成的c语言神经网络模型API调用时,输入数据数量是24,输出数据数量是4,但上文设想采集了三轴加速度传感器的x/y/z三个各数据,按Jogging( ...

最新文章

  1. 在PHP中使用全局变量的几种方法
  2. 关于研发效能提升的思考,每个P8以及以上都应该懂!
  3. 沈向洋官宣离职微软!他是微软级别最高的中国人、微软AI领导者,21年前参与创办MSRA...
  4. mysql半备份_MySQL半同步复制与增强半同步复制详解及安装
  5. 人脸识别数据集bin解压
  6. 风险评估资产重要性识别_如何有效的进行风险评估?
  7. 使用Lucene的新FreeTextSuggester查找长尾建议
  8. 解决 --- Docker 启动时报错:iptables:No chain/target/match by the name
  9. python映射的主要特点_Python入门 4——字典及其映射
  10. ipython和python是不是同一个软件_为什么SciPy在IPython和Python中的行为有很大不同?...
  11. 【Python数据挖掘】回归模型与应用
  12. mapreduce优化总结
  13. Java中的HashCode 1 之hash算法基本原理
  14. 自动控制原理6.3---串联校正
  15. linux装sql2008数据库,Ubuntu 16.04下安装SQL Server for Linux
  16. matlab做误差棒图,matlab绘制误差棒
  17. android java char_Android句子迷客户端
  18. vueX的mutation/action个人学习总结
  19. 8瓶药水3只小白鼠问题
  20. USB转TTL、USB转RS232的实现

热门文章

  1. 代理记账公司是如何进行缴税的?
  2. 点云旋转平移(一)—基础知识介绍
  3. EXCEL VBA案例教程-李立宗-专题视频课程
  4. bcd转ascii码 流程图_bcd码转ascii码代码
  5. 数据挖掘的十大经典算法
  6. 高通打开内核阶段串口log
  7. 普通话计算机辅助测试作品5号,普通话测试朗读作品范文《作品5号》
  8. F7飞控项目调试过程的记录 系列文章第一部分
  9. QtCreator影子构建与默认编译目录
  10. python变量名必须以字母或下划线开头不区分字母大小写_Python变量名必须以字符或下划线开头,并且区分字母大小写。...