GPUImage详细解析(十二)Sobel边界检测

前言

卷积运算是一个看似复杂的概念,今天来揭开这个神秘的面纱。
卷积矩阵:卷积矩阵是一个由权重数据组成的矩阵,中心像素周围像素的亮度乘以这些权重然后再相加就能得到中心像素的转化后数值。
本文对GPUImage中的Sobel边界检测滤镜进行解析。

效果

正文

GPUImage的Sobel边界检测滤镜是
GPUImageSobelEdgeDetectionFilter
GPUImageSobelEdgeDetectionFilter继承GPUImageTwoPassFilter,由两个滤镜组成,分别是黑白滤镜和边界检测滤镜。首先是把输入的图像变成亮度图,再由边界检测的滤镜转换成边界图。GPUImageSobelEdgeDetectionFilter对外的属性有三个,分别是:
texelWidth:边界检测时八方向的像素值宽度,默认是1/size.width;(1像素的宽度)
texelHeight:边界检测时八方向的像素值高度,默认是1/size.height;(1像素的高度)
edgeStrength:边界检测后,对边界的亮度增强程度,默认是1.0;(不变)
3x3矩阵中,纹理的距离宽度和高度;

边界检测的原理:一个像素为边界的表现是左右、上下的颜色差异很大,那么通过特定的卷积矩阵来运算,快速得到左右和上下方向的差异值之后,就可以通过这个值的大小来确定边界;
Sobel边界检测的卷积矩阵如下:
水平的卷积矩阵:

-1, 0, +1
-2, 0, +2
-1, 0, +1

垂直的卷积矩阵:

−1, −2, −1
 0,  0,  0
+1, +2, +1

如果左右的颜色差异值很小/大,那么通过水平卷积矩阵得到的值就会很小/大;
如果上下的颜色差异值很小/大,那么通过垂直卷积矩阵得到的值就会很小/大;
于是通过两个卷积矩阵分别得到水平差异值h和垂直差异值v后,再用sqrt(h2+v2)就可以得到边界值。

绘制的流程

  • 1、摄像机采集图像,得到YUV颜色空间的图像;
  • 2、把YUV的图像分成亮度纹理和色度纹理,并用YUV到RGBA的转 换矩阵,把两个纹理合成RGBA的图像;
  • 3、根据RGB不同的权值,把RGB颜色空间的图像,转换为亮度图;
  • 4、对每一个像素,根据八方向的像素值,用水平卷积矩阵算出水平方向的差异值h;用垂直卷积矩阵算出垂直方向的差异值v;根据sqrt(h2+v2)求出明亮程度,得到边界图;
  • 5、调整边界图的大小,显示到屏幕上。
整个流程用图来表示

流程图中的Texture#10,表示的是纹理对象10;绑定在纹理单元5是通过GPU Frame Capture查看。如下图,我们可以看到纹理单元2、3、4、5中分别绑定着纹理对象3、4、9、10,并且当前渲染的目标是纹理对象3。


遇到的问题

1、GPU Frame Debugger 无法正常工作

这个也是别人遇到过的问题点击http://www.openradar.me/28262556 可以查看。

2、无法消除的warning
System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles Reading from public effective user settings

这两个问题,其实是同一个问题:GPU Frame Capture在默认情况下会调用Metal的API,在正常的设备调试,会出现以下两行代码

Metal GPU Frame Capture Enabled
Metal API Validation Enabled

在调试demo过程中,我用的是Xcode8.1 + iOS 10.2.1;
iOS的版本高于Xcode版本
解决方案:升级Xcode或者换用低版本的iOS系统。

总结

在处理每个像素时,根据八方向求出边界值的过程与像素处理顺序无关;
这个处理的过程可以用片段着色器进行并行计算,极大地提高处理效率。

本身摄像头得到的YUV颜色空间里面就包括了亮度图,但是在本文的demo中却是经历了YUV到RGBA的转换,再由RGBA到亮度图的转换,这里是一个可优化的地方。

代码不复杂,见github

引用:《objc期刊》

推荐阅读更多精彩内容