掘金 人工智能 前天 15:38
基于 YOLOv8 + BeautyGAN + CodeFormer + Face Parsing 实现简单的人脸美颜的功能
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了人脸美颜技术,重点介绍了基于深度学习的美颜方案。该方案采用多模型组合的方式,首先使用YOLOv8-Face进行人脸检测与裁剪,然后利用BeautyGAN对人脸区域进行美颜,接着通过CodeFormer重建人脸细节,再使用Face Parsing获得面部掩码,最后将处理后的人脸无缝融合回原图。文章还分享了使用ONNXRuntime部署和加速模型的方法,并给出了完整的算法流程和相关代码示例,展示了如何将各个模型串联起来实现美颜功能。

✨基础美颜技术通常包含磨皮、美白、祛痘、祛黑眼圈等功能,而五官重塑则涉及瘦脸、大眼、提眉、缩鼻翼等调整,这些调整通常基于黄金比例动态进行。

⚙️基于深度学习的美颜流程通常包括:使用YOLOv8-Face进行人脸检测与裁剪;使用BeautyGAN对人脸区域进行美颜,以实现局部增强;使用CodeFormer对人脸区域进行细节重建,以提升纹理与自然度;利用Face Parsing获得美颜后的精准面部掩码;以及通过掩码将处理后的人脸融回原图,以维持整体风格与背景一致性。

🖼️在实现细节上,文章给出了BeautyGan模型的C++调用示例,展示了如何对输入图像进行预处理,如何使用ONNXRuntime进行推理,以及如何对输出数据进行后处理。调用BeautyGan模型需要两张图,一张是原始人脸图像,另一张是参考妆容图像。生成的图像会再经过CodeFormer模型进行细节优化。

🎨为了使美颜效果更自然,文章还介绍了融合算法的改进方向,例如在掩码边缘使用高斯模糊,并提出了后续尝试使用贝塞尔曲线平滑,以确保边缘过渡自然。

一. 人脸美颜

人脸美颜技术涵盖了多个领域和内容,结合人工智能、计算机视觉等技术,形成了多样化的应用场景和功能模块。

例如:

在这里只讨论磨皮、美白这些的实现。

二. 算法流程

2.1 基于双边滤波实现的磨皮

我之前写过一篇文章《OpenCV 笔记(29):图像降噪算法——高斯双边滤波、均值迁移滤波》(juejin.cn/post/735492…) 曾介绍过双边滤波的原理,在早期双边滤波还经常用于人脸美颜。

双边滤波器可以用如下的公式表示:

bilateralfilter(i,j)=k,lf(k,l)w(i,j,k,l)k,lw(i,j,k,l)bilateralfilter(i,j)= \frac{\sum_{k,l} f(k,l)*w(i,j,k,l)}{\sum_{k,l}w(i,j,k,l)}

2.2 基于深度学习实现的美颜

本文实现的方式有别于之前介绍的传统图像处理方式,使用多种模型组合的形式实现整个流程。

大致步骤如下:

    人脸检测与裁剪:使用 YOLOv8-Face 检测到人脸并裁剪。美颜模型处理:先用 BeautyGAN 对人脸区域进行美颜,可以得到局部增强的效果。细节优化:再以 CodeFormer 对人脸区域进行细节重建,可有效提升纹理与自然度。人脸解析与掩码:利用 Face Parsing 获得美颜后精准的面部掩码,便于后续与原图进行融合。无缝融合:通过掩码将处理后的人脸融回原图,理论上可维持整体风格与背景一致性。

美颜增强流水线: YOLOv8 -> BeautyGAN -> CodeFormer -> Face Parsing -> Mask 融合回原图

三. 整体的实现

整个流程涉及到多个模型,各个模型的部署和加速都使用 ONNXRuntime,每个模型的调用我都封装好了。

下面以 BeautyGan 模型的调用为例:

#include "../onnxruntime/OnnxRuntimeBase.h"using namespace cv;using namespace std;using namespace Ort;class BeautyGan: public OnnxRuntimeBase {public:    BeautyGan(std::string modelPath, const char* logId, const char* provider);    void inferImage(Mat& src, Mat makeup, Mat& dst);private:    vector<float> preprocess(Mat image);    Mat postprocess(float* output_data);    vector<float> input_image_1;    vector<float> input_image_2;    int inpWidth;    int inpHeight;    int outWidth;    int outHeight;};
#include "../../include/faceBeauty/BeautyGan.h"BeautyGan::BeautyGan(std::string modelPath, const char* logId, const char* provider): OnnxRuntimeBase(modelPath, logId, provider){    this->inpHeight = input_node_dims[0][2];    this->inpWidth = input_node_dims[0][3];    this->outHeight = output_node_dims[0][2];    this->outWidth = output_node_dims[0][3];}vector<float> BeautyGan::preprocess(Mat image){    cv::resize(image, image, cv::Size(this->inpWidth, this->inpHeight));    image.convertTo(image, CV_32F, 1.0 / 255.0);    std::vector<cv::Mat> channels(3);    cv::split(image, channels);    std::vector<float> result(this->inpWidth * this->inpHeight * image.channels());    const unsigned int channel_step = inpHeight * inpWidth;    for (int i = 0; i < 3; ++i) {        std::memcpy(result.data() + i * channel_step, channels[i].data, channel_step * sizeof(float));    }    return result;}cv::Mat BeautyGan::postprocess(float* output_data) {    std::vector<cv::Mat> output_channels;    const unsigned int channel_step = outHeight * outWidth;    for (int i = 0; i < 3; ++i) {        output_channels.emplace_back(outHeight, outWidth, CV_32F, output_data + i * channel_step);    }    cv::Mat output_img;    cv::merge(output_channels, output_img);    output_img = output_img * 255.0;    output_img.convertTo(output_img, CV_8U);    return output_img;}void BeautyGan::inferImage(Mat& src, Mat makeup, Mat& dst) {    // 图像预处理    this->input_image_1 = this->preprocess(src);        // 原始人脸图像    this->input_image_2 = this->preprocess(makeup);     // 参考妆容图像    std::array<int64_t,4> input_shape {1,3,this->inpHeight, this->inpWidth};    auto allocator_info = MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);    vector<Value> ort_inputs;    ort_inputs.push_back(Value::CreateTensor<float>(allocator_info, input_image_1.data(), input_image_1.size(), input_shape.data(), input_shape.size()));    ort_inputs.push_back(Value::CreateTensor<float>(allocator_info, input_image_2.data(), input_image_2.size(), input_shape.data(), input_shape.size()));    vector<Value> ort_outputs = this -> forward(ort_inputs);    // 后处理    float* output_data = ort_outputs.front().GetTensorMutableData<float>();    cv::Mat beautygan_crop = this->postprocess(output_data);    cv::resize(beautygan_crop, dst, src.size());}

BeautyGan 模型的调用需要输入两张图,一张是原始人脸图像(通过 YOLOv8 获取的人脸区域),一张是参考妆容图像。

跑完 BeautyGan 模型后,将生成的图像再跑 CodeFormer 模型,这两步是美颜的关键所在。

好了,下面的代码给出了完整的算法流程和相关注释:

各个模型文件和各个模型调用相关的代码可以在 github.com/fengzhizi71… 可以找到。

#include <iostream>#include <opencv2/imgproc.hpp>#include <opencv2/highgui.hpp>#include <vector>#include "include/faceSwap/Yolov8Face.h"#include "include/faceSwap/Face68Landmarks.h"#include "include/faceSwap/FaceEnhance.h"#include "include/faceBeauty/CodeFormer.h"#include "include/faceBeauty/FaceParsing.h"#include "include/faceBeauty/BeautyGan.h"#include "include/onnxruntime/Constants.h"using namespace cv;using namespace std;cv::Mat blend_face_skin_region(const cv::Mat& codeformed_face,                                      const cv::Mat& original_face,                                      const cv::Mat& skin_mask,                                      int feather_size = 15, double feather_sigma = 5.0) {    CV_Assert(codeformed_face.size() == original_face.size());    CV_Assert(skin_mask.size() == original_face.size());    CV_Assert(codeformed_face.type() == original_face.type());    CV_Assert(skin_mask.type() == CV_8UC1);    // feather mask 边缘    cv::Mat blurred_mask;    if (feather_size > 0) {        cv::GaussianBlur(skin_mask, blurred_mask, cv::Size(feather_size, feather_size), feather_sigma);    } else {        blurred_mask = skin_mask.clone();    }    // 创建一个输出图像,初始为 original    cv::Mat blended = original_face.clone();    // 使用 mask 将 codeformed_face 拷贝到 blended 中    codeformed_face.copyTo(blended, blurred_mask);  // 只复制 mask!=0 的区域    return blended;}int main(){    string image_path = ".../girl.jpg";    cv::Mat src = cv::imread(image_path);    imshow("src",src);    // 各种模型的加载    const string& onnx_provider = OnnxProviders::CPU;    const char* provider = onnx_provider.c_str();    string modelPath = "/Users/Tony/CLionProjects/MonicaImageProcessHttpServer/models";    string yolov8FaceModelPath = modelPath + "/yoloface_8n.onnx";    string face68LandmarksModePath = modelPath + "/2dfan4.onnx";    string faceEnhanceModePath = modelPath + "/gfpgan_1.4.onnx";    string beautyGanModePath = modelPath + "/beautygan.onnx";    string codeFormerModePath = modelPath + "/codeformer.onnx";    string faceParsingModePath = modelPath + "/face_parsing_resnet34.onnx";    const std::string& yolov8FaceLogId = "yolov8Face";    const std::string& face68LandmarksLogId = "face68Landmarks";    const std::string& faceEnhanceLogId = "faceEnhance";    const std::string& beautyGanLogId = "beautyGan";    const std::string& codeFormerLogId = "codeFormer";    const std::string& faceParsingLogId = "faceParsing";    Yolov8Face yolov8Face(yolov8FaceModelPath, yolov8FaceLogId.c_str(), provider);    Face68Landmarks face68Landmarks(face68LandmarksModePath, face68LandmarksLogId.c_str(), provider);    FaceEnhance faceEnhance(faceEnhanceModePath, faceEnhanceLogId.c_str(), provider);    BeautyGan beautyGan(beautyGanModePath,beautyGanLogId.c_str(), provider);    CodeFormer codeFormer(codeFormerModePath,codeFormerLogId.c_str(), provider);    FaceParsing faceParsing(faceParsingModePath,faceParsingLogId.c_str(), provider);    // 人脸检测与裁剪    Mat original_face;    Bbox box;    try {        vector<Bbox> boxes;        yolov8Face.detect(src, boxes);        box = boxes[0];        original_face = src(Rect(cv::Point(box.xmin,box.ymin), cv::Point(box.xmax,box.ymax)));    } catch(...) {    }    imshow("original_face",original_face);    // 查找人脸的关键点    vector<Point2f> face_landmark_5of68;    face68Landmarks.detect(src, box, face_landmark_5of68);    // 美颜模型处理    Mat beautygan_crop;    Mat makeup = cv::imread("/Users/Tony/BeautyGAN/imgs/makeup/vFG112.png"); // 参考妆容图像    beautyGan.inferImage(original_face, makeup, beautygan_crop);    imshow("beautygan",beautygan_crop);    // 使用 CodeFormer 对人脸优化细节    Mat codeformed_face; // CodeFormer 输出的人脸    codeFormer.inferImage(beautygan_crop, codeformed_face);    resize(codeformed_face, codeformed_face, original_face.size());    imshow("codeformer", codeformed_face);    // 人脸解析与获取掩码    cv::Mat skin_mask;    faceParsing.inferImage(codeformed_face, skin_mask);    imshow("skin_mask", skin_mask);    // 通过掩码将处理后人脸融回原图    cv::resize(skin_mask, skin_mask, codeformed_face.size());    cv::Mat blended_face = blend_face_skin_region(codeformed_face, original_face, skin_mask);    imshow("blended_face", blended_face);    blended_face.copyTo(src(Rect(cv::Point(box.xmin,box.ymin), cv::Point(box.xmax,box.ymax))));    // 最后再用 GFPGAN 模型对人脸进行增强    Mat result = faceEnhance.process(src, face_landmark_5of68);    imshow("result", result);    waitKey(0);    return 0;}

下面两张图分别是原图和最终的效果图。

下面三张图表示:使用 YOLOv8-Face 检测到人脸的区域、用 BeautyGAN 对人脸区域进行美颜、用 CodeFormer 对人脸区域细节重建。

用 BeautyGAN 对该图进行美颜看上去效果不是特别好,可能是因为 makeup 图像也就是参考妆容图像相对于原图太小的缘故。

下面两张图表示:用 Face Parsing 获得美颜后的面部Mask、通过 Mask 将处理后的人脸融回原图中的人脸区域。

为了看上去更自然,融回原图后,我还用了 GFPGAN 模型对人脸进行增强,才生成最后的效果图。

再跑一些图看看效果:

四. 总结

上述代码大致完成了一个简单的美颜功能,不足之处还是有很多,后续可能会逐步完善。

例如:

参考资料:

    github.com/Honlan/Beau…github.com/sczhou/Code…github.com/yakhyo/face…

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

人脸美颜 深度学习 YOLOv8 BeautyGAN CodeFormer
相关文章