最近看了一个磨皮算法祛斑感觉效果不错,效果图看文末就行,个人觉得效果非常不错滴。
国际惯例,参考博客:
磨皮算法的源码: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
源码文末获取。
第一个坑就是计算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中,有兴趣可以去找找,同时文章也同步到微信公众号中,有疑问或者兴趣欢迎公众号私信。