前言
最近看了一个磨皮算法祛斑感觉效果不错,效果图看文末就行,个人觉得效果非常不错滴。
国际惯例,参考博客:
磨皮算法的源码:YUCIHighPassSkinSmoothing
How To Smooth And Soften Skin With Photoshop
图像算法—磨皮算法研究汇总
妹纸们的最爱 - 美颜算法,美颜SDK
.Net里面的coreImage
IOS里面的coreImage
Core Image Kernel Language Reference
Core Image Filter Reference
如何分析ps中的曲线?曲线都能做哪些方面的调整?原理是什么?
Core Image框架详细解析(十三) —— 在写一个自定义滤波器之前你需要知道什么?
Python Pillow – Adjust Image Sharpness
先看看它惊艳的结果
源码剖析
本博文重点是解析源码结构和简单的python复现,不会深究其原理,因为作者也说了,是按照PhotoShop的一个磨皮祛斑步骤(参考博客二)实现的。
源码的核心实现在YUCIHighPassSkinSmoothing.m中,最后一个output函数记录了整个算法的流程。
以下图为例

整体步骤
首先是通过类似于高反差保留的代码,制作一个mask,这个可以看参考博客四的解释
YUCIHighPassSkinSmoothingMaskGenerator *maskGenerator = [[YUCIHighPassSkinSmoothingMaskGenerator alloc] init]; maskGenerator.inputRadius = self.inputRadius; maskGenerator.inputImage = self.inputImage;
讯享网
然后通过Photoshop里面的曲线操作,调整图像亮度
讯享网YUCIRGBToneCurve *skinToneCurveFilter = self.skinToneCurveFilter; skinToneCurveFilter.inputImage = self.inputImage; skinToneCurveFilter.inputIntensity = self.inputAmount;
亮度调整完毕以后,就将原图和亮度图按照mask定义的混合系数进行混合
double sharpnessValue = self.inputSharpnessFactor.doubleValue * self.inputAmount.doubleValue; if (sharpnessValue > 0) { CIFilter *shapenFilter = [CIFilter filterWithName:@"CISharpenLuminance"]; [shapenFilter setValue:@(sharpnessValue) forKey:@"inputSharpness"]; [shapenFilter setValue:blendWithMaskFilter.outputImage forKey:kCIInputImageKey]; return shapenFilter.outputImage;
高反差保留
进入到YUCIHighPassSkinSmoothingMaskGenerator的实现中,发现有四个步骤:
- 先进行曝光度调整
CIExposureAdjust,系数为 − 1.0 -1.0 −1.0
- 然后进行绿色和蓝色通道的混合叠加
YUCIGreenBlueChannelOverlayBlend,代码也很简单讯享网
vec4 base = vec4(image.g,image.g,image.g,1.0); vec4 overlay = vec4(image.b,image.b,image.b,1.0); float ba = 2.0 * overlay.b * base.b + overlay.b * (1.0 - base.a) + base.b * (1.0 - overlay.a);使用的时候直接 2 × G c h a n n e l × B c h a n n e l 2\times G_{channel} \times B_{channel} 2×Gchannel×Bchannel
- 接下来进入高通滤波
YUCIHighPass的环节,非常简单,先高斯模糊一下子,然后跟原图做个混合,别人说这应该是就是高反差保留的计算方法:CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; [blurFilter setValue:self.inputImage.imageByClampingToExtent forKey:kCIInputImageKey];讯享网
image.rgb - blurredImage.rgb + vec3(0.5,0.5,0.5) - 最后要做三次增强
YUCIHighPassSkinSmoothingMaskBoost,也是一个公式
p ′ = { p ∗ p ∗ 2 p ≤ 0.5 1 − ( 1 − p ) ( 1 − p ) ∗ 2 p > 0.5 p'=\begin{cases} p*p*2&p\leq 0.5\\ 1-(1-p)(1-p)*2&p >0.5 \end{cases} p′={ p∗p∗21−(1−p)(1−p)∗2p≤0.5p>0.5
对上面的公式循环三次float hardLightColor = image.b; for (int i = 0; i < 3; ++i) { if (hardLightColor < 0.5) { hardLightColor = hardLightColor * hardLightColor * 2.; } else { hardLightColor = 1. - (1. - hardLightColor) * (1. - hardLightColor) * 2.; } }但是源码实现的时候,又加入了进一步的操作
讯享网
const float k = 255.0 / (164.0 - 75.0); hardLightColor = (hardLightColor - 75.0 / 255.0) * k;
至此,计算mask的流程结束
曲线调亮
这个源码部分实现很复杂,但是从这个issue可以发现,源码的实现其实就是和PhotoShop里面的曲线调整一模一样,去搜索PS的曲线调整原理(参考博客有),就会发现很简单,直接用三次样条曲线,将旧的像素值映射到新的像素值就行了,源码里面计算三次样条的锚点有三个 ( 0 , 0 ) , ( 120 / 255.0 , 146 / 255.0 ) , ( 1 , 1 ) (0,0),(120/255.0,146/255.0),(1,1) (0,0),(120/255.0,146/255.0),(1,1),在python里面有很多库都有这个函数。
简单的融合与锐化
最后计算按照高反差保留计算得到的mask,将曲线调亮后的图与原图进行融合,关于CIBlendWithMask这个API的解释看这里,系数为0时候得到背景,系数为1的时候得到前景。
因为上面图像有点模糊,所以就加了锐化,其实还有很多超高清算法或者人脸mask划分的方法去优化最终成图。
python实现的核心
因为代码贴的有点多,这里只介绍复现时候遇到的坑,完整python源码文末获取。
第一个坑就是计算mask中有一步要高斯模糊,但是opencv的高斯模糊函数除了半径外,还有一个sigma系数,怎么调都和源码不一样,后来看了PIL库,貌似没有这个多余的系数,和源码效果一模一样,所以这一步不要用opencv的函数,而是用PIL去处理
# YUCIHighPass # 先进行高斯模糊 # PIL的方法 pil_img = np2pil(ba_img) pil_blur = pil_img.filter(ImageFilter.GaussianBlur(radius)) blur_img = np.asarray(pil_blur,np.float32)/255.0 plt.figure(figsize=(8,8)) plt.imshow(blur_img) plt.axis('off')
第二个坑是,我们的理解中像素值以0-1为标准,但是在进行三次增强时候,作者加入了其它的一个操作,导致值超出了这个范围,所以需要对结果加个clip 规整到0-1
讯享网# YUCIHighPassSkinSmoothingMaskBoost hardLightColor = hp_img[...,2] [x1,y1] = np.where(hardLightColor<0.5) [x2,y2] = np.where(hardLightColor>=0.5) for i in range(3): hardLightColor[x1,y1] = hardLightColor[x1,y1]*hardLightColor[x1,y1]*2.0 hardLightColor[x2,y2] = 1.0 - (1.0 - hardLightColor[x2,y2]) * (1.0 - hardLightColor[x2,y2]) * 2.0 k = 255.0/(164.0-75.0); hardLightColor = (hardLightColor - 75.0/255.0) * k hpss_img = np.zeros((hardLightColor.shape[0],hardLightColor.shape[1],3)) hpss_img[...,0] = hardLightColor hpss_img[...,1] = hardLightColor hpss_img[...,2] = hardLightColor hpss_img = np.clip(hpss_img,0,1)
最终得到的mask类似于这样

第四个坑,就是做RGB tone curve的时候,一直不知道是啥操作,最后去看PS的方法,实现了一个粗略的版本,从结果来看,和源码跑出来的结果差不多
from scipy.interpolate import CubicSpline x = [0,120.0/255.0,1] y = [0,146.0/255.0,1]#146 cs = CubicSpline(x,y) tc_img = cs(input_img)

最后进行加和就行了,结果对比如下:
自己的python脚本结果

源码结果:

效果差不多,只不过我的图片貌似糊了一点,无伤大雅,斑点没了就行。
后记
博客只是做了最基本的理论实现,里面有很多优化方向,涉及商业机密就不放出来了,最基本理论实现就是按照源码一步步走。
完整的python脚本实现放在微信公众号的简介中描述的github中,有兴趣可以去找找,同时文章也同步到微信公众号中,有疑问或者兴趣欢迎公众号私信。



版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/119758.html