内容简介:边缘模糊是图像中经常出现的质量问题,由此造成的轮廓不清晰,线条不鲜明,使图像特征提取、识别和理解难以进行。增强图像边缘和线条,使图像边缘变得清晰的处理就是我们所说的图像锐化。在移动设备上使用GPU做图像锐化,一般就是利用空域滤波器对图像做模板卷积处理,主要步骤如下: 1)、用模板遍历图像,使模板中心分别与图中像素重合 2)、将模板上各点位的系数分别与图像中对应像素相乘 3)、将所有乘积相加 4)、将和赋值给对应中心像素图像锐化中常用的方法主要有梯度运算、拉普拉斯算子等。
边缘模糊是图像中经常出现的质量问题,由此造成的轮廓不清晰,线条不鲜明,使图像特征提取、识别和理解难以进行。增强图像边缘和线条,使图像边缘变得清晰的处理就是我们所说的图像锐化。
在移动设备上使用GPU做图像锐化,一般就是利用空域滤波器对图像做模板卷积处理,主要步骤如下: 1)、用模板遍历图像,使模板中心分别与图中像素重合 2)、将模板上各点位的系数分别与图像中对应像素相乘 3)、将所有乘积相加 4)、将和赋值给对应中心像素
图像锐化中常用的方法主要有梯度运算、拉普拉斯算子等。
梯度算法
梯度算法的结果值与相邻像素的灰度差值成正比,图像经过梯度运算后,留下灰度值急剧变化的边沿处的点。GPUImage中可以找到Sobel算子和Prewitt算子的具体实现,Sobel和Prewitt都是3x3模板的梯度运算,其模板表示如下:
GPUImageSobelEdgeDetectionFilter是GPUImage利用Sobel算子实现的一种边缘检测器,其运行效果如下:
接下来我们深入源码,看一下这样一个滤镜在GPUImage中具体是怎样实现的。
@interface GPUImageSobelEdgeDetectionFilter : GPUImageTwoPassFilter
首先GPUImageSobelEdgeDetectionFilter是继承自GPUImageTwoPassFilter的。GPUImageTwoPassFilter内部会管理2个GLProgram,每个GLProgram代表一個GPU处理过程。这里之所以需要2个GLProgram是因为梯度运算基于灰度计算灰度变化,因此第一个GLProgram用于将图像转换成灰度(具体使用的是GPUImageGrayscaleFilter.h中的shader),第二个GLProgram才是真正用于处理sobel算法的shader。
if (!(self = [super initWithFirstStageVertexShaderFromString:kGPUImageVertexShaderString firstStageFragmentShaderFromString:kGPUImageLuminanceFragmentShaderString secondStageVertexShaderFromString:kGPUImageNearbyTexelSamplingVertexShaderString secondStageFragmentShaderFromString:fragmentShaderString])) { return nil; } ...
初始化过程非常简单,直接调用父类的初始化接口,分别传入2个GLProgram需要的shader即可。灰度变换的shader就不在这里细看了,我们重点看一下sobel算法的vertex shader和fragment shader。
NSString *const kGPUImageNearbyTexelSamplingVertexShaderString = SHADER_STRING ( attribute vec4 position; attribute vec4 inputTextureCoordinate; uniform float texelWidth; uniform float texelHeight; varying vec2 textureCoordinate; varying vec2 leftTextureCoordinate; varying vec2 rightTextureCoordinate; varying vec2 topTextureCoordinate; varying vec2 topLeftTextureCoordinate; varying vec2 topRightTextureCoordinate; varying vec2 bottomTextureCoordinate; varying vec2 bottomLeftTextureCoordinate; varying vec2 bottomRightTextureCoordinate; void main() { gl_Position = position; vec2 widthStep = vec2(texelWidth, 0.0); vec2 heightStep = vec2(0.0, texelHeight); vec2 widthHeightStep = vec2(texelWidth, texelHeight); vec2 widthNegativeHeightStep = vec2(texelWidth, -texelHeight); textureCoordinate = inputTextureCoordinate.xy; leftTextureCoordinate = inputTextureCoordinate.xy - widthStep; rightTextureCoordinate = inputTextureCoordinate.xy + widthStep; topTextureCoordinate = inputTextureCoordinate.xy - heightStep; topLeftTextureCoordinate = inputTextureCoordinate.xy - widthHeightStep; topRightTextureCoordinate = inputTextureCoordinate.xy + widthNegativeHeightStep; bottomTextureCoordinate = inputTextureCoordinate.xy + heightStep; bottomLeftTextureCoordinate = inputTextureCoordinate.xy - widthNegativeHeightStep; bottomRightTextureCoordinate = inputTextureCoordinate.xy + widthHeightStep; } );
fragment shader的计算资源比较珍贵,可以看到这里有个比较取巧的做法,把每个像素点四周的坐标点计算放在了vertex shader,然后利用varying变量传给fragment shader。这样在fragment shader中要取四周的像素值时只要直接把穿过来的坐标点拿来用就可以了。
NSString *const kGPUImageSobelEdgeDetectionFragmentShaderString = SHADER_STRING ( precision mediump float; varying vec2 textureCoordinate; varying vec2 leftTextureCoordinate; varying vec2 rightTextureCoordinate; varying vec2 topTextureCoordinate; varying vec2 topLeftTextureCoordinate; varying vec2 topRightTextureCoordinate; varying vec2 bottomTextureCoordinate; varying vec2 bottomLeftTextureCoordinate; varying vec2 bottomRightTextureCoordinate; uniform sampler2D inputImageTexture; uniform float edgeStrength; void main() { float bottomLeftIntensity = texture2D(inputImageTexture, bottomLeftTextureCoordinate).r; float topRightIntensity = texture2D(inputImageTexture, topRightTextureCoordinate).r; float topLeftIntensity = texture2D(inputImageTexture, topLeftTextureCoordinate).r; float bottomRightIntensity = texture2D(inputImageTexture, bottomRightTextureCoordinate).r; float leftIntensity = texture2D(inputImageTexture, leftTextureCoordinate).r; float rightIntensity = texture2D(inputImageTexture, rightTextureCoordinate).r; float bottomIntensity = texture2D(inputImageTexture, bottomTextureCoordinate).r; float topIntensity = texture2D(inputImageTexture, topTextureCoordinate).r; float h = -topLeftIntensity - 2.0 * topIntensity - topRightIntensity + bottomLeftIntensity + 2.0 * bottomIntensity + bottomRightIntensity; float v = -bottomLeftIntensity - 2.0 * leftIntensity - topLeftIntensity + bottomRightIntensity + 2.0 * rightIntensity + topRightIntensity; float mag = length(vec2(h, v)) * edgeStrength; gl_FragColor = vec4(vec3(mag), 1.0); } );
在fragment shader中,获取四周的像素值,再分别按照sobel算子计算横向和纵向的乘积。最后以横向和纵向差值为坐标计算向量长度,并且把最终的长度值作为结果。
filter中的其它代码主要是一些属性设置,几乎不需要额外的GL代码调用,GPU调用的部分基本都被GPUImage封裝好了,扩展起来还是非常方便的。filter的使用也非常简单:
GPUImagePicture *gpuPic = [[GPUImagePicture alloc] initWithImage:sampleImage]; GPUImageSobelEdgeDetectionFilter *sobelFilter = [[GPUImageSobelEdgeDetectionFilter alloc] init]; [gpuPic addTarget:sobelFilter]; [gpuPic processImageUpToFilter:sobelFilter withCompletionHandler:^(UIImage *processedImage) { // do something }];
拉普拉斯算子
拉普拉斯算法比较适合用于改善图像模糊,是比较常用的边缘增强处理算子,其模板表示有如下几种:
GPUImage中同样可以找到关于拉普拉斯算法的具体实现GPUImageLaplacianFilter、GPUImageSharpenFilter。GPUImageSharpenFilter是基于拉普拉斯算子的一种拉氏锐化,其运行效果如下:
深入源码,看一下GPUImageSharpenFilter的具体实现
@interface GPUImageSharpenFilter : GPUImageFilter
GPUImageSharpenFilter比较简单,直接继承自GPUImageFilter,只有一个GLProgram。
if (!(self = [super initWithVertexShaderFromString:kGPUImageSharpenVertexShaderString fragmentShaderFromString:kGPUImageSharpenFragmentShaderString])) { return nil; } ...
初始化过程同样是调用父类的初始化接口,传入shader即可。
NSString *const kGPUImageSharpenVertexShaderString = SHADER_STRING ( 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; varying float edgeMultiplier; 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; } );
同样的技巧,相邻坐标的计算放在了vertex shader。
NSString *const kGPUImageSharpenFragmentShaderString = SHADER_STRING ( 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); } );
fragment shader分别获取上下左右的像素值,按照模板系数计算乘积。可以注意到,这里的模板系数其实不是固定的,而是根据外部设置的sharpness动态调节的,这样可以针对不同的图片,不同的需要设置不同的锐化强度。上面的运行效果图设置的sharpness值是1.0,不同的强度值,甚至不同的锐化模板,大家都可以自己动手尝试哦。
作者简介:Bill, 天天P图 iOS 工程师
文章后记天天P图是由腾讯公司开发的业内领先的图像处理,相机美拍的APP。欢迎扫码或搜索关注我们的微信公众号:“天天P图攻城狮”,那上面将陆续公开分享我们的技术实践,期待一起交流学习!
加入我们天天P图技术团队长期招聘: (1) 深度学习(图像处理)研发工程师(上海) 工作职责
- 开展图像/视频的深度学习相关领域研究和开发工作;
- 负责图像/视频深度学习算法方案的设计与实现;
- 支持社交平台部产品前沿深度学习相关研究。
工作要求
- 计算机等相关专业硕士及以上学历,计算机视觉等方向优先;
- 掌握主流计算机视觉和机器学习/深度学习等相关知识,有相关的研究经历或开发经验;
- 具有较强的编程能力,熟悉C/C++、python;
- 在人脸识别,背景分割,体态跟踪等技术方向上有研究经历者优先,熟悉主流和前沿的技术方案优先;
- 宽泛的技术视野,创造性思维,富有想象力;
- 思维活跃,能快速学习新知识,对技术研发富有激情。
(2) AND / iOS 开发工程师 (3) 图像处理算法工程师 期待对我们感兴趣或者有推荐的技术牛人加入我们(base 上海)!联系方式:ttpic_dev@qq.com
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Phoenix解读 | Phoenix源码解读之索引
- Phoenix解读 | Phoenix源码解读之SQL
- Redux 源码解读 —— 从源码开始学 Redux
- AQS源码详细解读
- SDWebImage源码解读《一》
- MJExtension源码解读
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Graph Algorithms
Shimon Even / Cambridge University Press / 2011-9-19 / USD 32.99
Shimon Even's Graph Algorithms, published in 1979, was a seminal introductory book on algorithms read by everyone engaged in the field. This thoroughly revised second edition, with a foreword by Richa......一起来看看 《Graph Algorithms》 这本书的介绍吧!