写点什么

恢复青春气息,就靠这套人像美肤算法了

发布于: 2020 年 08 月 19 日
恢复青春气息,就靠这套人像美肤算法了



在人像美颜中,美肤是一个非常重要的组成部分,健康的肤色,可以凸显一个人整体的气质。以“美颜相机”为例,“美颜相机”备受年轻女孩的青睐,很大一部分原因是因为“美颜相机”对肤色处理得很好,可以将不同的肤色变换为一种粉嫩的具有青春气息的肤色,让人感觉到自己很年轻。



人像美肤的简单定义就是对人像中的皮肤区域进行调色,它的一般流程如下图所示。



人像美肤流程示意图



在上图中,上半部分跟通用磨皮流程一致,都需要计算得出皮肤区域或者肤色概率图MASK,可见 MASK 对于美颜是至关重要的。在美肤中,我们先对全图进行美肤调色或者美白调色,然后结合 MASK 和原图做混合,即可得到美肤效果图。



本节介绍的人像美肤算法包括两个部分:

  • 皮肤美白算法

  • 皮肤调色算法



皮肤美白算法实际上也是一种特殊的皮肤调色,这里之所以单独讲解,是因为在美颜算法开发中,可以没有皮肤调色,但是不能没有皮肤美白。而对于美白算法,又可以有多种方式实现,这一点与皮肤调色不同。学完本节的内容之后,大家将学会在磨皮的基础上进行肤色调节,进一步改善磨皮效果。



皮肤美白算法

在市面上流行的人像美颜 App 中,一般有两种皮肤美白方式,一种是不考虑皮肤区域,全图美白,另一种是结合皮肤区域的局部美白。个人认为,第二种比较科学严谨,在效果上也比较自然,而第一种实际上就是一种简化,没有太大意义。



皮肤美白算法有多种,归纳起来有两大类:LUT 调色法和图层混合法。

LUT调色法

该方法是指通过类似 PS 等软件中调节亮度/对比度、曲线、色彩平衡等方式,或者通过某种亮度调节曲线的方式,来生成对应的 LUT,以 LUT 滤镜方式实现皮肤美白。它的优点是使用颜色滤镜 LUT,速度快,便于实时处理。



这里以曲线调节为例,给出一组 PS 曲线调节参数以及对应效果,如下图所示。



PS 中的曲线调节

在上图中,我们通过曲线调节的方式,将原图(a)进行了皮肤提亮,得到了对应的效果图(c)。下面我们使用这组参数生成一张经典的 LUT,如下图所示。

皮肤美白 LUT



上面的 LUT 是针对全图的调色,我们根据图中所述的流程,结合肤色概率检测,得到最后的美白效果以及磨皮美白效果如下图所示



从图中可以看出,与原图相比,效果图中人像的皮肤更加光滑,肤色更加白皙通透,这就是磨皮美白的效果。

上述是一种使用 PS 工具的 LUT 调色美白方法。在实际使用中,不单单局限于 PS,可以使用任何图像编辑软件来调节皮肤颜色,直到满意为止,最后生成经典 LUT 即可。

LUT 调色美白效果图



除此之外,还可以通过算法来生成这样的曲线 LUT,比如亮度/对比度增强曲线:



其中,w(x, y) 是原图像素 (x, y) 的亮度,v(x, y) 是增强之后的亮度, β 是调节系数,值越大,增强程度越强。



我们使用上述亮度/对比度增强曲线来测试磨皮美白效果,如下图所示,对比原图,效果图中人物皮肤区域光滑白皙,美颜效果跃然纸上。

曲线美白效果图



图层混合法

所谓图层混合法美白,是指通过使用 PS 中的图层混合模式,来达到美白效果的方法。该方法比较简单,是使用 PS 修图时常用的一种人像美白方法。该算法的思想是:将原图中的皮肤区域的像素与纯白色像素进行“柔光”图层混合,然后调节不透明度,以达到皮肤美白的目的。



PS 中“柔光”图层混合算法的计算公式如下:

我们使用图层混合法对测试图进行皮肤美白,效果如下图所示。



图层混合法美白效果图



本节讲述了几种皮肤美白的方法,效果上大同小异。在实际应用中,美白的算法千变万化,从传统图像算法到人工智能算法,层出不穷,但关键在于举一反三和灵活运用,这样才能创造出更好的算法,得到更惊艳的效果。



下面我们给出 C 语言实现上述三种美白算法的核心代码,相关代码的引用可参见前文:

#include <string.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include"Commen.h"
#include"f_LUTFilter.h"
#include"f_SkinPDF.h"
#include"f_GaussFilter.h"
#include"f_SkinWhite.h"
/*************************************************************************
*函数: 肤色美白
*参数:
*srcData:32BGRA 位图像数据
*width: 图像宽度
*height: 图像高度
*stride: 图像 Stride
*skinMask: 皮肤蒙版
*lutData: 32BGRA LUT 图像数据
*ratio: 美白程度,范围[0,100]
*返回值: 0-成功,其他失败
**************************************************************************/
int f_SkinWhite(unsigned char* srcData, int width, int height, int stride,
unsigned char* lutData, int ratio)
{
int ret = 0;
int length = height * stride;
unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) *
length);
memcpy(tempData, srcData, sizeof(unsigned char) * length);
unsigned char* skinPDF = (unsigned char*)malloc(sizeof(unsigned char) *
length);
memcpy(skinPDF, srcData, sizeof(unsigned char) * length);
ret = f_SkinPDF(skinPDF, width, height, stride);
int maskSmoothRadius = 3;
ret = f_FastGaussFilter(skinPDF, width, height, stride,
maskSmoothRadius);
ret = f_LUTFilter(tempData, width, height, stride, lutData);
unsigned char* pSrc = srcData;
unsigned char* pLut = tempData;
unsigned char* pSkin = skinPDF;
for(int j = 0; j < height; j++)
{
for(int i = 0; i < width; i++)
{
int r, g, b, a;
b = CLIP3((pSrc[0] * (100 - ratio) + pLut[0] * ratio) / 100, 0, 255);
g = CLIP3((pSrc[1] * (100 - ratio) + pLut[1] * ratio) / 100, 0, 255);
r = CLIP3((pSrc[2] * (100 - ratio) + pLut[2] * ratio) / 100, 0, 255);
a = (pSkin[0] + pSkin[1] + pSkin[2]) / 3;
pSrc[0] = CLIP3((b * a + pSrc[0] * (255 - a)) / 255, 0, 255);
pSrc[1] = CLIP3((g * a + pSrc[1] * (255 - a)) / 255, 0, 255);
pSrc[2] = CLIP3((r * a + pSrc[2] * (255 - a)) / 255, 0, 255);
pSrc += 4;
pLut += 4;
pSkin += 4;
}
}
free(tempData);
free(skinPDF);
return ret;
};
/*************************************************************************
*函数: 肤色美白曲线法
*参数:
*srcData:32BGRA 图像数据
*width: 图像宽度
*height: 图像高度
*stride: 图像 Stride
*belta: 曲线参数 belta,范围[2,10],默认:2
*ratio: 磨皮程度,范围 [0,100] *返回值: 0-成功,其他失败
*参考资料: "A Two-Stage Contrast Enhancement Algorithm for Digital Images"
**************************************************************************/
int f_SkinWhiteCurve(unsigned char* srcData, int width, int height, int
stride, int belta, int ratio)
{
int ret = 0;
int length = height * stride;
unsigned char* skinPDF = (unsigned char*)malloc(sizeof(unsigned char) *
length);
memcpy(skinPDF, srcData, sizeof(unsigned char) * length);
ret = f_SkinPDF(skinPDF, width, height, stride);
int maskSmoothRadius = 3;
ret = f_FastGaussFilter(skinPDF, width, height, stride,
maskSmoothRadius);
unsigned char* pSrc = srcData;
unsigned char* pSkin = skinPDF;
for(int j = 0; j < height; j++)
{
for(int i = 0; i < width; i++)
{
int r, g, b, a;
//skin white curve
b = CLIP3(log((float)pSrc[0] * (belta - 1) / 255.0f + 1) /
log((float)belta) * 255.0f, 0, 255);
g = CLIP3(log((float)pSrc[1] * (belta - 1) / 255.0f + 1) /
log((float)belta) * 255.0f, 0, 255);
r = CLIP3(log((float)pSrc[2] * (belta - 1) / 255.0f + 1) /
log((float)belta) * 255.0f, 0, 255);
b = CLIP3((b * ratio + pSrc[0] * (100 - ratio)) / 100, 0, 255);
g = CLIP3((g * ratio + pSrc[1] * (100 - ratio)) / 100, 0, 255);
r = CLIP3((r * ratio + pSrc[2] * (100 - ratio)) / 100, 0, 255);
//skin pdf
a = (pSkin[0] + pSkin[1] + pSkin[2]) / 3;
pSrc[0] = CLIP3((b * a + pSrc[0] * (255 - a)) / 255, 0, 255);
pSrc[1] = CLIP3((g * a + pSrc[1] * (255 - a)) / 255, 0, 255);
pSrc[2] = CLIP3((r * a + pSrc[2] * (255 - a)) / 255, 0, 255);
pSrc += 4;
pSkin += 4;
}
}
free(skinPDF);
return ret;
}
inline int ModeSmoothLight(int basePixel,int mixPixel)
{
int res = 0;
res = mixPixel > 128 ?
((int)((float)basePixel+((float)mixPixel+(float)mixPixel-
255.0f)*((sqrt((float)basePixel/255.0f) )*255.0f-(float)basePixel)/255.0f)):
((int)((float)basePixel+((float)mixPixel+(float)mixPixel-
255.0f)*((float)basePixel-(float)basePixel*(float)basePixel/255.0f)/255.0f));
return CLIP3(res, 0, 255);
};
/*************************************************************************
*函数: PS 图层法肤色美白
*参数:
*srcData:32BGRA 图像数据
*width: 图像宽度
*height: 图像高度
*stride: 图像 Stride
*ratio: 磨皮程度,范围 [0,100]
*返回值: 0-成功,其他失败
**************************************************************************/
int f_SkinWhitePS(unsigned char* srcData, int width, int height, int stride,
int ratio)
{
int ret = 0;
int length = height * stride;
unsigned char* skinPDF = (unsigned char*)malloc(sizeof(unsigned char) *
length);
memcpy(skinPDF, srcData, sizeof(unsigned char) * length);
ret = f_SkinPDF(skinPDF, width, height, stride);
int maskSmoothRadius = 3;
ret = f_FastGaussFilter(skinPDF, width, height, stride, maskSmoothRadius);
unsigned char* pSrc = srcData;
unsigned char* pSkin = skinPDF;
for(int j = 0; j < height; j++)
{
for(int i = 0; i < width; i++)
{
int r, g, b, a;
//skin white using smoothlight of ps
b = ModeSmoothLight(pSrc[0], 255);
g = ModeSmoothLight(pSrc[1], 255);
r = ModeSmoothLight(pSrc[2], 255);
b = CLIP3((b * ratio + pSrc[0] * (100 - ratio)) / 100, 0, 255);
g = CLIP3((g * ratio + pSrc[1] * (100 - ratio)) / 100, 0, 255);
r = CLIP3((r * ratio + pSrc[2] * (100 - ratio)) / 100, 0, 255);
//skin pdf
a = (pSkin[0] + pSkin[1] + pSkin[2]) / 3;
pSrc[0] = CLIP3((b * a + pSrc[0] * (255 - a)) / 255, 0, 255);
pSrc[1] = CLIP3((g * a + pSrc[1] * (255 - a)) / 255, 0, 255);
pSrc[2] = CLIP3((r * a + pSrc[2] * (255 - a)) / 255, 0, 255);
pSrc += 4;
pSkin += 4;
}
}
free(skinPDF);
return ret;
}



皮肤调色算法

皮肤调色算法也叫肤色调节算法,它的定义就是通过算法来实现皮肤颜色的变换。上节中介绍的美白算法,实际上就是肤色调节的一种特例,将皮肤颜色调白即是美白。目前肤色调节在美颜相机中有较多使用,通过肤色调节,美颜相机中给出了各种滤镜特效,让人的皮肤呈现白里透红、粉嫩淡雅、健康小麦色等。



肤色调节算法的流程如下:

①使用 Photoshop、Gimp 等图像编辑软件对样例图调出所需肤色。

②根据步骤①,调出经典 LUT。

③将查找表应用于人像照片的皮肤区域。

根据上述流程,我们给出一组糖果色肤色的效果测试,如下图所示。



糖果色效果调色



在上图中,使用 PS 中的“可选颜色”与“曲线”功能调配了一种糖果肤色的效果,并对应生成了 LUT。使用这个 LUT 结合皮肤区域检测得到最后的肤色调节效果,如下图所示。从图中可以看到,除了皮肤区域,其他区域基本没有调色,这样就达到了调至糖果色皮肤的目的。

糖果色肤色调色(LUT+肤色概率模型)



肤色调节算法与 LUT 美白算法非常类似,代码可以通用,唯一不同之处在于 LUT 的不同,我们给出 C 语言实现的代码,如下:

#include <string.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include"Commen.h"
#include"f_LUTFilter.h"
#include"f_SkinPDF.h"
#include"f_GaussFilter.h"
#include"f_SkinColor.h"
/*************************************************************************
*函数: 肤色调节
*参数:
*srcData:32BGRA 图像数据
*width: 图像宽度
*height: 图像高度
*stride: 图像 Stride
*skinMask: 皮肤蒙版
*lutData: 32BGRA LUT 图像数据
*ratio: 肤色程度,范围 [0,100]
*返回值: 0-成功,其他失败
**************************************************************************/
int f_SkinColor(unsigned char* srcData, int width, int height, int stride,
unsigned char* lutData, int ratio)
{
int ret = 0;
int length = height * stride;
unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) *
length);
memcpy(tempData, srcData, sizeof(unsigned char) * length);
unsigned char* skinPDF = (unsigned char*)malloc(sizeof(unsigned char) *
length);
memcpy(skinPDF, srcData, sizeof(unsigned char) * length);
ret = f_SkinPDF(skinPDF, width, height, stride);
int maskSmoothRadius = 3;
ret = f_FastGaussFilter(skinPDF, width, height, stride,
maskSmoothRadius);
ret = f_LUTFilter(tempData, width, height, stride, lutData);
unsigned char* pSrc = srcData;
unsigned char* pLut = tempData;
unsigned char* pSkin = skinPDF;
for(int j = 0; j < height; j++)
{
for(int i = 0; i < width; i++)
{
int r, g, b, a;
b = CLIP3((pSrc[0] * (100 - ratio) + pLut[0] * ratio) / 100, 0, 255);
g = CLIP3((pSrc[1] * (100 - ratio) + pLut[1] * ratio) / 100, 0, 255);
r = CLIP3((pSrc[2] * (100 - ratio) + pLut[2] * ratio) / 100, 0, 255);
a = (pSkin[0] + pSkin[1] + pSkin[2]) / 3;
pSrc[0] = CLIP3((b * a + pSrc[0] * (255 - a)) / 255, 0, 255);
pSrc[1] = CLIP3((g * a + pSrc[1] * (255 - a)) / 255, 0, 255);
pSrc[2] = CLIP3((r * a + pSrc[2] * (255 - a)) / 255, 0, 255);
pSrc += 4;
pLut += 4;
pSkin += 4;
}
}
free(tempData);
free(skinPDF);
return ret;
};



本文节选自《图像视频滤镜与人像美颜美妆算法详解》

胡耀武 谭娟 李云夕 著

本书系统、全面地介绍了与图像视频滤镜和人像美颜美妆特效相关的算法基础知识与方法思路,涵盖了市面上流行的美颜美妆App的特效功能,包括传统方法和基于深度学习的AI滤镜和美颜算法。



发布于: 2020 年 08 月 19 日阅读数: 133
用户头像

还未添加个人签名 2019.10.21 加入

还未添加个人简介

评论

发布
暂无评论
恢复青春气息,就靠这套人像美肤算法了