注意:本文样例图片为了避免侵权,均使用AIGC生成;
本文介绍影楼精修中肤色统一算法的实现方案,并以像素蛋糕为例,进行分析说明。
肤色统一就是将人像照片中皮肤区域的颜色进行统一,看起来颜色均匀一致,不会出现颜色不均匀问题。我们以像素蛋糕软件为例,示例图如下:
可以看到,在对人脸和身体进行肤色统一之后,皮肤的颜色一致性变好了不少,给人主观感受更佳。
像素蛋糕的肤色统一功能历经了多个版本的迭代,目前包含AI肤色统一和肤色统一两个版本,同时又将人脸和身体区域做了区分,得到了脸部区域肤色统一和身体区域肤色统一功能调节,从用户或者设计师角度来讲,可控性更强,同时支持了单人和多人的处理。
肤色统一和AI肤色统一功能的差异:可能是AI肤色统一使用了基于深度学习的模型方案,直接自动对皮肤颜色和亮度进行调节,而肤色统一则是基于传统图像处理方案构建的;
我们这里以传统肤色统一为例进行说明。通过效果上的测试,将像素蛋糕的肤色统一特点总结如下:
1.皮肤区域颜色一致性提高,颜色向某个颜色靠拢,而且,这个颜色并非皮肤区域的颜色均值,而是亮度较高的颜色;
2.皮肤暗部区域的亮度会自动调整变亮;
3.人脸和皮肤区域可分开处理;
我们将按照这三个特点来进行实现;
算法方案
假设原图为S,最终效果图为D;
1.对S进行皮肤分割,获得皮肤区域Mask图Mask_skin,这一步需要较高的准确性,皮肤区域为1-255的灰度值,非皮肤区域为0;目前皮肤分割相对比较准确的图片API,如:美图皮肤分割API > 阿里云皮肤分割API;
美图API:https://ai.meitu.com/index?t=1747366168417
阿里云API:https://vision.aliyun.com/experience/detail?tagName=imageseg&children=SegmentSkin
2.对S进行人脸点位检测,根据人脸关键点,计算人脸区域Mask图Mask_face;
3.将肤色统一划分为皮肤区域亮度统一和颜色统一两个模块;
3.1亮度统一,以身体皮肤区域处理为例,对皮肤区域进行基于直方图信息的暗部区域自动亮度矫正;
思路:统计皮肤区域亮度直方图,计算亮度中值,统计亮度低于中值的暗部区域皮肤像素数量,用于动态计算亮度增强程度,根据亮度增强程度,对暗部区域亮度进行矫正,非暗部区域保持不变;
这里给出亮度统一的核心代码:
void SkinBrightenWithHistogram(unsigned char* bgraData, int width, int height, int stride, unsigned char* skinMask) {
int size = width * height;
float* luminance = (float*)malloc(sizeof(float) * size);
int histBins[256] = { 0 };
int skinCount = 0;
// 1. 提取亮度信息并构建直方图
for (int y = 0; y < height; ++y) {
unsigned char* row = bgraData + y * stride;
for (int x = 0; x < width; ++x, row += 4) {
int idx = y * width + x;
float r = row[2], g = row[1], b = row[0];
float yVal = 0.299f * r + 0.587f * g + 0.114f * b;
luminance[idx] = yVal;
if (skinMask[idx] > 20) {
int bin = (int)CLIP3(yVal, 0, 255);
histBins[bin]++;
skinCount++;
}
}
}
if (skinCount == 0) {
free(luminance);
return;
}
// 2. 使用直方图累加计算中值亮度
int medianIndex = skinCount / 2;
int cumulative = 0;
int medianBin = 0;
for (int i = 0; i < 256; ++i) {
cumulative += histBins[i];
if (cumulative >= medianIndex) {
medianBin = i;
break;
}
}
float medianY = (float)medianBin;
// 3. 统计暗部像素数量(亮度 < 中值)
int darkCount = 0;
for (int i = 0; i < medianBin; ++i) {
darkCount += histBins[i];
}
float darkRatio = (float)darkCount / skinCount;
4. 根据暗部比例动态计算增强强度 alpha
//float alpha = CLAMP(0.3f + darkRatio * 0.7f, 0.3f, 1.0f);
//if (medianY < 80.0f) {
// alpha *= 1.2f;
//}
//float gamma = 1.2f;
// 5. 根据暗部比例动态计算增强强度 alpha(提高提亮效果)
float alpha = CLIP3(0.4f + darkRatio * 1.0f, 0.4f, 1.2f); // 强化基础 alpha
if (medianY < 80.0f) {
alpha *= 1.4f; // 放大提亮倍数
}
else if (medianY < 100.0f) {
alpha *= 1.2f; // 中等放大
}
float gamma = 1.4f; // 增加 gamma(让暗部提亮更明显)
// 6. 提亮暗部区域
for (int y = 0; y < height; ++y) {
unsigned char* row = bgraData + y * stride;
for (int x = 0; x < width; ++x, row += 4) {
int idx = y * width + x;
if (skinMask[idx] < 5) continue;
float Y = luminance[idx];
if (Y < medianY) {
float norm = CLIP3(1.0f - (Y / (medianY + 1e-5f)), 0.0f, 1.0f);
float boost = alpha * powf(norm, gamma);
float newY = CLIP3(Y + boost * medianY, 0, 255);
float scale = newY / (Y + 1e-5f);
int gray = row[1] * skinMask[idx] / 255; // 用绿色通道估算灰度用于保色插值
for (int c = 0; c < 3; ++c) {
float val = row[2 - c] * scale;
val = (unsigned char)CLIP3(val, 0, 255);
row[2 - c] = (val * gray + row[2 - c] * (255 - gray)) / 255;
}
}
}
}
free(luminance);
}
3.2颜色统一,基于HSV或Lab颜色空间,计算皮肤区域颜色亮度直方图统计的中值以上的皮肤像素RGB均值,映射到HSV或Lab颜色空间,得到对应H或ab的均值(也可直接统计H或ab的均值);以HSV为例,根据原图皮肤像素的HSV和目标均值HSV,重新映射得到颜色统一为均值颜色后的HSV,然后映射到RGB;
HSV_result=(H_mean, S_src,V_src)
RGB_result=HSV_result---->RGB
4.使用3的方式,分别对人脸区域和身体皮肤区域进行肤色统一,分别得到效果图D_face和D_body;
5.设置人脸区域肤色统一参数k_face,身体肤色统一参数k_body,范围[0,100],将原图S和D_face,D_body进行alpha blend,得到最终效果图D;
上述算法流程就是本人基于传统数字图像处理算法构建的肤色统一算法,效果测试和对比如下:
通过测试效果对比,可以看到,对比原图S,本文算法在脖子等暗部区域亮度有一定的自动校正,同时,人脸和身体皮肤区域的肤色区域一致,基本实现了像素蛋糕类似的功能;
对于AI肤色统一,效果上会比传统方法更自然,同时,对于暗部的亮度矫正也会更准确,后续将陆续进行分析;