本文主要是对美颜相关的一些内容的学习做一个总结,一个基本的美颜相机大概包含了美颜和美型两个方面,美颜主要是磨皮美白等效果,美型则是对眼睛、鼻子、脸型等做一些微调;大多美型相机还会提供各式各样的滤镜、提供好看的2D贴纸,更有苹果相机提供了动漫滤镜以及3DEmoji效果可用于AR等高端场景;
由于自己是一个初学者,对以上所有内容都只能做一个简单的介绍,而不能非常深入,所提供的效果也只是达到一个参考而非商用效果,还请见谅;
这里的算法实现主要是基于unity的shaderlab来完成的;
美白部分所采用的是log曲线来提升美白效果;
要注意一点,美白是让皮肤变白而不是简简单单让屏幕变得更亮,所以如何让周围环境不做出太多改变,而是只让人脸变得更白则是美白所要追求的效果。这里,也许会想到使用皮肤检测,对满足条件的像素值进行提亮,其它不变,这个方法当然也考虑过,但是使用颜色检测的方法检测皮肤的话有两大缺点,第一,增加了判断条件,在GPU里面加入太多判断会严重影响GPU的效率,第二,数字图像一般都会有很强的椒盐噪声,这个在手机相机得到的屏幕里是很强的,当然肉眼看不太出来,当使用颜色检测的时候就会把噪声效果放大,所以不可取。这里采用的是基于灰度值的转换,也就是转换之后,要在像素值和新像素值之间以原像素的灰度作为参考因素进行插值,这样对于颜色过暗的部分尤其像头发部位就不会再有什么亮度上的改变。
fixed Beauty_Log(fixedc, float beta){
fixed a = (log(c*(beta-1)+1))/log(beta);
return a;
}
fixed4 SkinBeauty(fixed4 srcCol){
fixed beta = 1.0001 + 8.0 * _Whiten;
beta = beta * (srcCol.r + srcCol.g + srcCol.b) / 3.0;
srcCol.r = Beauty_Log(srcColr.r, beta);
srcCol.g = Beauty_Log(srcColr.g, beta);
srcCol.b = Beauty_Log(srcColr.b, beta);
return srcCol;
}
首先我们是对像素的三个通道进行分别处理的,我们要做的工作就是第一个函数,对通道的值进行提升,注意这个函数的特点,它是一个凸函数,在(0,1)区间内,它的值也是(0,1),但是结果是大于等于原值的,这个函数一个重要的参数是beta,beta越大,美白程度越高。在第二个函数里面,对三个通道进行了相同的提升,但是beta的值是基于灰度值进行修改的,这样做的好处是消除了美白对于头发和眉毛眼睛等部位的影响,使其主要对皮肤等部位感兴趣。
磨皮应该是一个相机里最重要的一个功能了,好的磨皮效果对用户的吸引力总是惊人的;
先假设读者对图像处理有简单的了解,如高斯滤波、双边滤波、均值滤波等;应用于磨皮效果最简单的滤波就是双边滤波了,为什么不是高斯滤波呢,因为高斯滤波它只考虑了空间域,处理之后整个图片会非常模糊,包括边界部分;而磨皮的目标则是不能失去边缘轮廓的细节,否则就会给人一种特别“假”的感觉,而双边滤波则考虑了空间域和像素域,能够满足保留边缘的需要;
在实现磨皮效果前,需要先分析一下一个好的磨皮效果需要哪些东西
所以,基于以上分析,可以开始由简入繁,先实现双边滤波,再思考如何更好的保留磨皮细节,然后再思考如何变得更加通透;
实现美型效果需要人脸的关键点检测,这个人脸识别有关,所以这里只能大概讲述一下美型的部分,而人脸关键点相关则不在此范畴;
美型效果就是五官的调整,随便一个美颜相机都可以看到,有眼睛、额头、鼻头、鼻翼、嘴型、下巴、脸型等等;
美型在图像处理上的核心做法就是液化效果,无论是眼睛还是其它部位的调整,其思路都是液化;
使用Lut,这也是比较常用的后处理技术;
fixed3 ApplyLut2d(sampler2D tex, fixed3 uvw, fixed3 scaleOffset){
uvw.z = scaleOffset.z;
fixed shift = floor(uvw.z);
uvw.xy = uvw.xy * scaleOffset.z * scaleOffset.xy + scaleOffset.xy * 0.5;
uvw.x += shift * scaleOffset.y;
uvw.xyz = lerp(tex2D(tex, uvw.xy).rgb, tex2D(tex, uvw.xy + fixed2(scaleOffset.y, 0)).rgb, uvw.z - shift);
return uvw;
}
使用如下lut表来做滤镜效果,类似于后处理中的Color Grading;
这里就是根据时间来计算当前使用哪一帧;
fixed4 frag(v2f i) : SV_Target{
float time = floor(_Time.y * _FramePlaySpeed);
float row = floor(time / _HorizontalAmount);
float column = time - row * _HorizontalAmount;
row = row % _VerticalAmount;
column = column % _HorizontalAmount;
half2 uv = i.uv + half2(column, -row);
uv.x /= _HorizontalAmount;
uv.y /= _VerticalAmount;
return tex2D(_StickerTex, uv);
}