实战分享:iOS实时美颜滤镜是怎样炼成的

MistyStatto 8年前
   <p> </p>    <p>1. 背景</p>    <p>前段时间由于项目需求,做了一个基于GPUImage的实时美颜滤镜。现在各种各样的直播、视频App层出不穷,美颜滤镜的需求也越来越多。为了回馈开源,现在我把它放到了 <a href="/misc/goto?guid=4959673096513639880" rel="nofollow,noindex">GitHub</a> 上面,感兴趣的朋友可以去下载。下面将主要介绍实现美颜滤镜的原理和思路。</p>    <p>2. GPUImage</p>    <p><a href="/misc/goto?guid=4959639738719213616" rel="nofollow,noindex">GPUImage</a> 是一个开源的基于GPU的图片或视频的处理框架,其本身内置了多达120多种常见的滤镜效果。有了它,添加实时的滤镜只需要简单地添加几行代码。下面的例子是以摄像头的数据为源,对其实时地进行反色的操作(类似相片底片的效果):</p>    <pre>  <code class="language-objectivec">self.videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionFront];  self.videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;  self.videoCamera.horizontallyMirrorFrontFacingCamera = YES;  GPUImageColorInvertFilter *invert = [[GPUImageColorInvertFilter alloc] init];  [self.videoCamera addTarget:invert];  self.filterView = [[GPUImageView alloc] initWithFrame:self.view.frame];  self.filterView.center = self.view.center;  [self.view addSubview:self.filterView];  [invert addTarget:self.filterView];  [self.videoCamera startCameraCapture];</code></pre>    <p>其实美颜也是一样,如果有这么一个美颜的滤镜(姑且叫做GPUImageBeautifyFilter),那么只需要把上面代码中的GPUImageColorInvertFilter替换成GPUImageBeautifyFilter即可。我们只需要做一个GPUImageBeautifyFilter就能实现实时美颜了,问题来了,到底什么算是美颜呢?我的理解是,大家对于美颜比较常见的需求就是磨皮、美白。当然提高饱和度、提亮之类的就根据需求而定。本文将着重介绍磨皮的实现(实际上GPUImageBeautifyFilter也实现了美白、提亮等效果)。</p>    <p>3. 磨皮</p>    <p>磨皮的本质实际上是模糊。而在图像处理领域,模糊就是将像素点的取值与周边的像素点取值相关联。而我们常见的高斯模糊 ,它的像素点取值则是由周边像素点求加权平均所得,而权重系数则是像素间的距离的高斯函数,大致关系是距离越小、权重系数越大。下图3.1是高斯模糊效果的示例:</p>    <p>高斯模糊效果图3.1</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0f5b79890a4dc5ce1f1fe33d8a45922d.png"></p>    <p>如果单单使用高斯模糊来磨皮,得到的效果是不尽人意的。原因在于,高斯模糊只考虑了像素间的距离关系,没有考虑到像素值本身之间的差异。举个例子来讲,头发与人脸分界处(颜色差异很大,黑色与人皮肤的颜色),如果采用高斯模糊则这个边缘也会模糊掉,这显然不是我们希望看到的。而 <a href="/misc/goto?guid=4959673096641207604" rel="nofollow,noindex">双边滤波(Bilateral Filter)</a> 则考虑到了颜色的差异,它的像素点取值也是周边像素点的加权平均,而且权重也是高斯函数。不同的是,这个权重不仅与像素间距离有关,还与像素值本身的差异有关,具体讲是,像素值差异越小,权重越大,也是这个特性让它具有了保持边缘的特性,因此它是一个很好的磨皮工具。下图3.2是双边滤波的效果示例:</p>    <p>双边滤波效果图3.2</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/54338d47c2d9054e77c8b83188c99689.png"></p>    <p>对比3.1和3.2,双边滤波效果确实在人脸细节部分保留得更好,因此我采用了双边滤波作为磨皮的基础算法。双边滤波在GPUImage中也有实现,是GPUImageBilateralFilter。根据图3.2,可以看到图中仍有部分人脸的细节保护得不够,还有我们并不希望将人的头发也模糊掉(我们只需要对皮肤进行处理)。由此延伸出来的改进思路是结合双边滤波,边缘检测以及肤色检测。整体逻辑如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b9745f10f2a2b2ebe34b88eeab0e9244.png"></p>    <p>Combination  Filter是我们自己定义的三输入的滤波器。三个输入分别是原图像A(x, y),双边滤波后的图像B(x, y),边缘图像C(x, y)。其中A,B,C可以看成是图像矩阵,(x,y)可以看成其中某一像素的坐标。Combination  Filter的处理逻辑如下图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/46a56b4ed18ec8065673909e2ec69f1b.png"></p>    <p>下面是主要的shader代码:</p>    <pre>  <code class="language-objectivec">NSString *const kGPUImageBeautifyFragmentShaderString = SHADER_STRING  (   varying highp vec2 textureCoordinate;   varying highp vec2 textureCoordinate2;   varying highp vec2 textureCoordinate3;      uniform sampler2D inputImageTexture;   uniform sampler2D inputImageTexture2;   uniform sampler2D inputImageTexture3;   uniform mediump float smoothDegree;      void main()   {       highp vec4 bilateral = texture2D(inputImageTexture, textureCoordinate);       highp vec4 canny = texture2D(inputImageTexture2, textureCoordinate2);       highp vec4 origin = texture2D(inputImageTexture3,textureCoordinate3);       highp vec4 smooth;       lowp float r = origin.r;       lowp float g = origin.g;       lowp float b = origin.b;       if (canny.r < 0.2 && r > 0.3725 && g > 0.1568 && b > 0.0784 && r > b && (max(max(r, g), b) - min(min(r, g), b)) > 0.0588 && abs(r-g) > 0.0588) {           smooth = (1.0 - smoothDegree) * (origin - bilateral) + bilateral;       }       else {           smooth = origin;       }       gl_FragColor = smooth;   }   );</code></pre>    <p>Combination Filter通过肤色检测和边缘检测,只对皮肤和非边缘部分进行处理。下面是采用这种方式进行磨皮之后的效果图:</p>    <p>最终磨皮效果图3.3</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/abb2e7254f493a53eb5d83cdaed4e0cf.png"></p>    <p>对比3.3与3.2,可以看到3.3对人脸细节的保护更好,同时对于面部磨皮效果也很好,给人感觉更加真实。</p>    <p>4.延伸</p>    <p>我所采用的磨皮算法是基于双边滤波的,主要是考虑到它同时结合了像素间空间距离以及像素值本身的差异。当然也不一定要采用双边滤波,也有通过改进高斯模糊(结合像素值差异)来实现磨皮的,甚至能取得更好的效果。另外GPUImageBeautifyFilter不仅仅具有磨皮功能,也实现了log曲线调色,亮度、饱和度的调整,具体详情可以参见 <a href="/misc/goto?guid=4959673096513639880" rel="nofollow,noindex">demo</a> 。</p>    <p>via: http://www.cocoachina.com/ios/20160513/16171.html</p>