网站怎么做预约小程序,洛阳seo外包公司费用,东道设计一个logo多少钱,四川百度推广和seo优化这里是 第三篇#xff1a;用 OpenGL 实现高斯模糊。我们分别在 iOS 和 Android 平台实现了用 OpenGL 对图像进行高斯模糊处理并渲染出来。效果图如下#xff1a; 本文福利#xff0c; 免费领取C音视频学习资料包、技术视频/代码#xff0c;内容包括#xff08;音视频开发用 OpenGL 实现高斯模糊。我们分别在 iOS 和 Android 平台实现了用 OpenGL 对图像进行高斯模糊处理并渲染出来。效果图如下 本文福利 免费领取C音视频学习资料包、技术视频/代码内容包括音视频开发面试题FFmpeg webRTC rtmp hls rtsp ffplay 编解码推拉流srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓ 高斯模糊是一种柔和模糊的图像效果模糊后的图像可以被更复杂的算法用来产生例如炫光、景深、热浪或者毛玻璃的效果。本文将会给大家介绍高斯模糊的数学原理以及用 OpenGL 完成高斯模糊的代码实现。
1、高斯模糊基础知识
高斯模糊Gaussian Blur也叫高斯平滑是在图像处理中广泛使用的处理效果通常用它来减少图像噪声以及降低细节层次。因为其视觉效果就像是经过一个半透明屏幕在观察图像所以常用于生成毛玻璃效果。
从数学的角度来看图像的高斯模糊过程就是图像与正态分布做卷积由于正态分布又叫作高斯分布所以这项技术就叫作高斯模糊。
1.1、基本原理
让我们先看一个直观的例子来理解模糊这个概念。 上图中中间点是 2周边点都是 1。
假如现在我们想让中间点和周围的点数值上更加接近来达成我们模糊中间点和周围点边界的目的。我们可以让中间点取周围点的平均值那么中间点就会从 2 变成 1中间点就会靠近周围的值这就是数值上的平滑也就是模糊。 我们将这个想法应用到图像上对图像中的每一个像素点取周围像素的平均值自然而然就会让这幅图产生模糊效果。
当我们取周围点的时候所参考的范围呈现一个圆形圆形半径越大模糊效果就会越强烈。 如果使用简单平均显然不是很合理因为图像都是连续的越靠近的点关系越密切越远离的点关系越疏远。因此加权平均更合理距离越近的点权重越大距离越远的点权重越小。
高斯模糊就是一种加权平均的模糊效果。 1.2、高斯函数的数学表达
正态分布的密度函数叫做高斯函数Gaussian function。
在图形上正态分布是一种钟形曲线越接近中心取值越大越远离中心取值越小。计算平均值的时候我们只需要将中心点作为原点其他点按照其在正态曲线上的位置分配权重就可以得到一个加权平均值。 因为我们处理的是图像而图像可以表示为二维矩阵其中每个元素为 ARGB 像素值因此我们在这里需要延伸到二维高斯函数。 高斯函数的一维形式是 高斯函数的二维形式是 假如目前有一张宽高为 1024x1024 的图像我们使用上述所说的方法对这个图像上的每个点计算二维正态分布的加权参考当前坐标附近距离半径为 33 的所有像素。那么可以得知我们需要进行的计算次数为 1024 * 1024 * 33 * 33 ≈ 11.4 亿次这显然是一个不可接受的算法我们需要对算法的效率进行优化。
因为二维高斯函数具有分离性所以二维高斯函数可以拆分为两个一维高斯函数来计算证明过程参考二维高斯卷积核拆分成两个一维的高斯卷积核[1]所以我们可以将算法优化为先计算水平方向的加权函数再计算垂直方向的加权函数。这样我们的算法计算量就从之前的 1024 * 1024 * 33 * 33 ≈ 11.4 亿次 下降为 1024 * 1024 * 33 * 2 ≈ 6900 万次。 所以我们的算法优化为水平方向运行一次着色器后再在垂直方向运行一次着色器。
2、iOS Demo
2.1、渲染模块
渲染模块与 OpenGL 渲染视频 中讲到的一致最终是封装出一个渲染视图 KFOpenGLView 用于展示最后的渲染结果。这里就不再细讲只贴一下主要的类和类具体的功能
KFOpenGLView使用 OpenGL 实现的渲染 View提供了设置画面填充模式的接口和渲染一帧纹理的接口。KFGLFilter实现 shader 的加载、编译和着色器程序链接以及 FBO 的管理。同时作为渲染处理节点提供给了接口支持多级渲染。KFGLProgram封装了使用 GL 程序的部分 API。KFGLFrameBuffer封装了使用 FBO 的 API。KFTextureFrame表示一帧纹理对象。KFFrame表示一帧类型可以是数据缓冲或纹理。KFGLTextureAttributes对纹理 Texture 属性的封装。KFGLBase定义了默认的 VertexShader 和 FragmentShader。
2.2、高斯模糊 Shader 实现
我们使用 KFGLFilter 为它设置高斯模糊的 Shader 来实现我们高斯模糊效果对应的顶点着色器和片段着色器的代码如下
KFGLGaussianBlur.h
#import Foundation/Foundation.hNS_ASSUME_NONNULL_BEGIN#define STRINGIZE(x) #x
#define STRINGIZE2(x) STRINGIZE(x)
#define SHADER_STRING(text) STRINGIZE2(text)extern NSString *const KFGLGaussianBlurVertexShader;
extern NSString *const KFGLGaussianBlurFragmentShader;NS_ASSUME_NONNULL_END
KFGLGaussianBlur.m
#import KFGLGaussianBlur.hNSString *const KFGLGaussianBlurVertexShader SHADER_STRING
(attribute vec4 position; // 通过 attribute 通道获取顶点信息。4 维向量。attribute vec4 inputTextureCoordinate; // 通过 attribute 通道获取纹理坐标信息。4 维向量。varying vec2 textureCoordinate; // 用于 vertex shader 和 fragment shader 间传递纹理坐标。2 维向量。const int GAUSSIAN_SAMPLES 9; // 被参考的点数目。uniform float wOffset; // 水平方向单位偏移。Offset 越大结果越模糊。uniform float hOffset; // 垂直方向单位偏移。Offset 越大结果越模糊。varying vec2 blurCoordinates[GAUSSIAN_SAMPLES]; // 被参考点的纹理坐标数组将在 vertex shader 和 fragment shader 间传递。2 维向量数组。void main(){gl_Position position;textureCoordinate inputTextureCoordinate.xy; // 将通过 attribute 通道获取的纹理坐标数据中的 2 维分量传给 fragment shader。int multiplier 0;vec2 blurStep;vec2 singleStepOffset vec2(hOffset, wOffset);for (int i 0; i GAUSSIAN_SAMPLES; i){multiplier (i - ((GAUSSIAN_SAMPLES - 1) / 2)); // 每一个被参考点距离当前纹理坐标的偏移乘数blurStep float(multiplier) * singleStepOffset; // 每一个被参考点距离当前纹理坐标的偏移blurCoordinates[i] inputTextureCoordinate.xy blurStep; // 每一个被参考点的纹理坐标}}
);NSString *const KFGLGaussianBlurFragmentShader SHADER_STRING
(varying highp vec2 textureCoordinate; // 从 vertex shader 传递来的纹理坐标。uniform sampler2D inputImageTexture; // 通过 uniform 通道获取纹理信息。2D 纹理。const lowp int GAUSSIAN_SAMPLES 9; // 被参考的点数目。varying highp vec2 blurCoordinates[GAUSSIAN_SAMPLES]; // 从 vertex shader 传递来的被参考点的纹理坐标数组。void main(){lowp vec4 sum vec4(0.0);// 根据距离当前点距离远近分配权重。分配原则越近权重越大。sum texture2D(inputImageTexture, blurCoordinates[0]) * 0.05;sum texture2D(inputImageTexture, blurCoordinates[1]) * 0.09;sum texture2D(inputImageTexture, blurCoordinates[2]) * 0.12;sum texture2D(inputImageTexture, blurCoordinates[3]) * 0.15;sum texture2D(inputImageTexture, blurCoordinates[4]) * 0.18;sum texture2D(inputImageTexture, blurCoordinates[5]) * 0.15;sum texture2D(inputImageTexture, blurCoordinates[6]) * 0.12;sum texture2D(inputImageTexture, blurCoordinates[7]) * 0.09;sum texture2D(inputImageTexture, blurCoordinates[8]) * 0.05;// 加权。gl_FragColor sum;}
);
2.3、图像转纹理
我们还需要实现一个 KFUIImageConvertTexture 类用于实现图片转纹理之后再对纹理使用 OpenGL 进行处理。代码如下
KFUIImageConvertTexture.h
#import Foundation/Foundation.h
#import OpenGLES/EAGL.h
#import UIKit/UIKit.h
#import KFTextureFrame.hinterface KFUIImageConvertTexture : NSObject (KFTextureFrame *)renderImage:(UIImage *)image;end
KFUIImageConvertTexture.m
#import KFUIImageConvertTexture.himplementation KFUIImageConvertTexture (KFTextureFrame *)renderImage:(UIImage *)image {CGImageRef cgImageRef [image CGImage];GLuint width (GLuint)CGImageGetWidth(cgImageRef);GLuint height (GLuint)CGImageGetHeight(cgImageRef);CGRect rect CGRectMake(0, 0, width, height);CGColorSpaceRef colorSpace CGColorSpaceCreateDeviceRGB();void *imageData malloc(width * height * 4);CGContextRef context CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);CGColorSpaceRelease(colorSpace);CGContextClearRect(context, rect);CGContextDrawImage(context, rect, cgImageRef);glEnable(GL_TEXTURE_2D);GLuint textureID;glGenTextures(1, textureID);glBindTexture(GL_TEXTURE_2D, textureID);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);// 结束后要做清理glBindTexture(GL_TEXTURE_2D, 0); // 解绑CGContextRelease(context);free(imageData);KFTextureFrame *inputFrame [[KFTextureFrame alloc] initWithTextureId:textureID textureSize:CGSizeMake(width, height) time:kCMTimeZero];return inputFrame;
}
2.4、展示高斯模糊渲染结果
我们在一个 ViewController 串联对图片进行高斯模糊处理的逻辑并展示最后的效果。代码如下
#import KFGaussianBlurViewController.h
#import KFUIImageConvertTexture.h
#import KFOpenGLView.h
#import KFGLFilter.h
#import KFGLGaussianBlur.hinterface KFGaussianBlurViewController ()
property (nonatomic, strong) KFOpenGLView *glView;
property (nonatomic, strong) KFUIImageConvertTexture *imageConvertTexture;
property (nonatomic, strong) EAGLContext *context;
property (nonatomic, strong) KFGLFilter *verticalGaussianBlurFilter;
property (nonatomic, strong) KFGLFilter *horizonalGaussianBlurFilter;
endimplementation KFGaussianBlurViewController#pragma mark - Property
- (EAGLContext *)context {if (!_context) {_context [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];}return _context;
}- (KFUIImageConvertTexture *)imageConvertTexture {if (!_imageConvertTexture) {_imageConvertTexture [[KFUIImageConvertTexture alloc] init];}return _imageConvertTexture;
}- (KFGLFilter *)verticalGaussianBlurFilter {if (!_verticalGaussianBlurFilter) {_verticalGaussianBlurFilter [[KFGLFilter alloc] initWithCustomFBO:NO vertexShader:KFGLGaussianBlurVertexShader fragmentShader:KFGLGaussianBlurFragmentShader];[_verticalGaussianBlurFilter setFloatUniformValue:hOffset floatValue:0.00390625f];}return _verticalGaussianBlurFilter;
}- (KFGLFilter *)horizonalGaussianBlurFilter {if (!_horizonalGaussianBlurFilter) {_horizonalGaussianBlurFilter [[KFGLFilter alloc] initWithCustomFBO:NO vertexShader:KFGLGaussianBlurVertexShader fragmentShader:KFGLGaussianBlurFragmentShader];[_horizonalGaussianBlurFilter setFloatUniformValue:wOffset floatValue:0.00390625f];}return _horizonalGaussianBlurFilter;
}#pragma mark - Lifecycle
- (void)viewDidLoad {[super viewDidLoad];[self setupUI];[self applyGaussianBlurEffect];
}- (void)viewWillLayoutSubviews {[super viewWillLayoutSubviews];self.glView.frame self.view.bounds;
}- (void)setupUI {self.edgesForExtendedLayout UIRectEdgeAll;self.extendedLayoutIncludesOpaqueBars YES;self.title Gaussian Blur;self.view.backgroundColor [UIColor whiteColor];// 渲染 view。_glView [[KFOpenGLView alloc] initWithFrame:self.view.bounds context:self.context];_glView.fillMode KFGLViewContentModeFit;[self.view addSubview:self.glView];
}- (void)applyGaussianBlurEffect {[EAGLContext setCurrentContext:self.context];UIImage *baseImage [UIImage imageNamed:KeyframeLogo];KFTextureFrame *textureFrame [KFUIImageConvertTexture renderImage:baseImage];// 垂直方向做一次高斯模糊。KFTextureFrame *verticalTexture [self.verticalGaussianBlurFilter render:textureFrame];// 水平方向做一次高斯模糊。KFTextureFrame *horizonalTexture [self.horizonalGaussianBlurFilter render:verticalTexture];[self.glView displayFrame:horizonalTexture];[EAGLContext setCurrentContext:nil];
}end
通过上面的代码可以看到我们是用 KFGLFilter 来封装一次 OpenGL 的处理节点它可以接收一个 KFTextureFrame 对象加载 Shader 对其进行渲染处理处理完后输出处理后的 KFTextureFrame然后可以接着交给下一个 KFGLFilter 来处理就像一条渲染链。
这里我们把高斯模糊用到的二维高斯卷积核拆成两个一维的高斯卷积核所以是分别做了一次垂直方向和一次水平风向的处理。
3、Android Demo
Android 实现高斯模糊的 Demo 我们是在 OpenGL 渲染视频 Demo 的基础上在相机返回的视频帧被渲染前增加了高斯模糊的处理。对应视频采集模糊和视频渲染模块这里就不再细讲只贴一下主要的类和类具体的功能
KFGLContext负责创建 OpenGL 环境负责管理和组装 EGLDisplay、EGLSurface、EGLContext。KFGLFilter实现 shader 的加载、编译和着色器程序链接以及 FBO 的管理。同时作为渲染处理节点提供给了接口支持多级渲染。KFGLProgram负责加载和编译着色器创建着色器程序容器。KFGLBase定义了默认的 VertexShader 和 FragmentShader。KFSurfaceViewKFSurfaceView 继承自 SurfaceView 来实现渲染。KFTextureViewKFTextureView 继承自 TextureView 来实现渲染。KFFrame表示一帧类型可以是数据缓冲或纹理。KFRenderViewKFRenderView 是一个容器可以选择使用 KFSurfaceView 或 KFTextureView 作为实际的渲染视图。
实现高斯模糊的顶点着色器代码和片段着色器代码如下
public static String defaultGaussianVertexShader attribute vec4 position; \n attribute vec4 inputTextureCoordinate; \n varying vec2 textureCoordinate; \n const int GAUSSIAN_SAMPLES 9; \n uniform float wOffset; \n uniform float hOffset; \n varying vec2 blurCoordinates[GAUSSIAN_SAMPLES]; \n void main() \n { \n gl_Position position; \n textureCoordinate inputTextureCoordinate.xy; \n int multiplier 0; \n vec2 blurStep; \n vec2 singleStepOffset vec2(hOffset, wOffset); \n for (int i 0; i GAUSSIAN_SAMPLES; i) \n { \n multiplier (i - ((GAUSSIAN_SAMPLES - 1) / 2)); \n blurStep float(multiplier) * singleStepOffset; \n blurCoordinates[i] inputTextureCoordinate.xy blurStep; \n } \n } \n ;
public static String defaultGaussianFragmentShader varying highp vec2 textureCoordinate; \n uniform sampler2D inputImageTexture; \n const lowp int GAUSSIAN_SAMPLES 9; \n varying highp vec2 blurCoordinates[GAUSSIAN_SAMPLES]; \n void main() \n {\n lowp vec4 sum vec4(0.0); \n sum texture2D(inputImageTexture, blurCoordinates[0]) * 0.05; \n sum texture2D(inputImageTexture, blurCoordinates[1]) * 0.09; \n sum texture2D(inputImageTexture, blurCoordinates[2]) * 0.12; \n sum texture2D(inputImageTexture, blurCoordinates[3]) * 0.15; \n sum texture2D(inputImageTexture, blurCoordinates[4]) * 0.18; \n sum texture2D(inputImageTexture, blurCoordinates[5]) * 0.15; \n sum texture2D(inputImageTexture, blurCoordinates[6]) * 0.12; \n sum texture2D(inputImageTexture, blurCoordinates[7]) * 0.09; \n sum texture2D(inputImageTexture, blurCoordinates[8]) * 0.05; \n gl_FragColor sum; \n } \n ;
对 MainActivity 的改动则主要是在 KFVideoCaptureListener 的 onFrameAvailable 回调中增加对图像帧做高斯模糊的处理逻辑再进行渲染即可。代码如下
public void onFrameAvailable(KFFrame frame) {mGLContext.bind();if (mVerticalGLFilter null) {mVerticalGLFilter new KFGLFilter(false, defaultGaussianVertexShader,defaultGaussianFragmentShader);mVerticalGLFilter.setFloatUniformValue(hOffset,0.00390625f);}if (mHoritizalGLFilter null) {mHoritizalGLFilter new KFGLFilter(false, defaultGaussianVertexShader,defaultGaussianFragmentShader);mHoritizalGLFilter.setFloatUniformValue(wOffset,0.00390625f);}KFFrame filterFrame mVerticalGLFilter.render((KFTextureFrame)frame);KFFrame hFilterFrame mHoritizalGLFilter.render((KFTextureFrame)filterFrame);mRenderView.render((KFTextureFrame) hFilterFrame);mGLContext.unbind();
}
可见当我们用 KFGLFilter 将 OpenGL 渲染能力封装起来并可以像增加渲染处理节点一样往现有渲染链中增加新的图像处理功能时相关改动就变得很方便了。 本文福利 免费领取C音视频学习资料包、技术视频/代码内容包括音视频开发面试题FFmpeg webRTC rtmp hls rtsp ffplay 编解码推拉流srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓