一个简单好用的磨皮祛斑算法理论和python实现

算法搜集 专栏收录该内容
26 篇文章 4 订阅

前言

最近看了一个磨皮算法祛斑感觉效果不错,效果图看文末就行,个人觉得效果非常不错滴。

国际惯例,参考博客:

磨皮算法的源码: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={pp21(1p)(1p)2p0.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中,有兴趣可以去找找,同时文章也同步到微信公众号中,有疑问或者兴趣欢迎公众号私信。

在这里插入图片描述

  • 3
    点赞
  • 0
    评论
  • 8
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值