Skip to content

实现 iOS 美颜

集成 SDK

下载 SDK

前往 下载 页面,获取最新版的 SDK,然后解压。

添加 Framework

将 SDK 包内 Facebetter.framework 库,拷贝到你的项目路径下。

打开 Xcode,参考这里添加 Facebetter.framework 动态库,确保添加的动态库 Embed 属性设置为 Embed & Sign

Xcode 链接库

权限配置

Info.plist 中添加必要权限:

xml
<!-- 相机权限(可选):仅在 demo 使用相机采集时才需要 -->
<key>NSCameraUsageDescription</key>
<string>需要相机权限进行美颜拍照</string>

权限说明:

  • 相机权限:可选。仅在应用中使用相机采集图像进行美颜处理时需要,如果只处理已有图片不需要此权限。

导入头文件

objc
#import <Facebetter/FBBeautyEffectEngine.h>

日志配置

默认日志是关闭的,可以按需开启,支持控制台日志和文件日志开关。开启日志要放到美颜引擎创建之前。

objc
FBLogConfig* logConfig = [[FBLogConfig alloc] init];
// 日志级别
logConfig.level = FBLogLevel_Info;
// 控制台日志
logConfig.consoleEnabled = YES;
// 文件日志
logConfig.fileEnabled = YES;
logConfig.fileName = @"log path: xx/xx/facebetter.log";

创建配置引擎

按照 此页面 指引,获取 appidappkey

验证方式优先级:

  • 如果提供了 licenseJson,使用授权数据验证(支持在线响应和离线授权)
  • 否则使用 appIdappKey 进行自动联网验证
objc
FBEngineConfig *engineConfig = [[FBEngineConfig alloc] init];
engineConfig.appId = @"your appId";     // 配置你的 appid(可选,如果提供了 licenseJson 则不需要)
engineConfig.appKey = @"your appkey";   // 配置你的 appkey(可选,如果提供了 licenseJson 则不需要)
// 可选:使用授权数据验证(如果提供则优先使用)
// engineConfig.licenseJson = @"your license json string";
self.beautyEffectEngine = [FBBeautyEffectEngine createEngineWithConfig:engineConfig];

错误处理

创建引擎后建议检查是否成功:

objc
if (self.beautyEffectEngine == nil) {
    NSLog(@"Failed to create beauty engine");
    return;
}

启用特效类型

美颜功能可以按类型开启关闭,根据订阅版本启用相应的美颜类型,每种类型还有对应的参数可以调节

objc
// 所有订阅版本都支持
[self.beautyEffectEngine setBeautyTypeEnabled:FBBeautyType_Basic enabled:TRUE];
// Pro, Pro+ 版本支持
[self.beautyEffectEngine setBeautyTypeEnabled:FBBeautyType_Reshape enabled:TRUE];
// Pro, Pro+ 版本支持
[self.beautyEffectEngine setBeautyTypeEnabled:FBBeautyType_Makeup enabled:TRUE];
// 仅 Pro+ 版本支持
[self.beautyEffectEngine setBeautyTypeEnabled:FBBeautyType_VirtualBackground enabled:TRUE];

检查操作结果

所有 API 调用都应该检查返回值:

objc
int ret = [self.beautyEffectEngine setBeautyTypeEnabled:FBBeautyType_Basic enabled:TRUE];
if (ret == 0) {
    NSLog(@"Successfully enabled basic beauty");
} else {
    NSLog(@"Failed to enable basic beauty, error code: %d", ret);
}

调节美颜参数

设置美肤参数

通过 setBasicParam 接口,设置美肤参数,参数范围 [0.0, 1.0];

objc
[self.beautyEffectEngine setBasicParam:FBBasicParam_Smoothing floatValue:0.5f];

支持的美肤参数:

objc
typedef NS_ENUM(NSInteger, FBBasicParam) {
  FBBasicParam_Smoothing = 0,  // 磨皮
  FBBasicParam_Sharpening,     // 锐化
  FBBasicParam_Whitening,      // 美白
  FBBasicParam_Rosiness,       // 红润
};

设置美型参数

通过 setReshapeParam 接口,设置美型参数,参数范围 [0.0, 1.0];

objc
[self.beautyEffectEngine setReshapeParam:FBReshapeParam_FaceThin floatValue:0.5f];

支持从美型参数:

objc
typedef NS_ENUM(NSInteger, FBReshapeParam) {
  FBReshapeParam_FaceThin = 0,  // 瘦脸
  FBReshapeParam_FaceVShape,    // V脸
  FBReshapeParam_FaceNarrow,    // 窄脸
  FBReshapeParam_FaceShort,     // 短脸
  FBReshapeParam_Cheekbone,     // 颧骨
  FBReshapeParam_Jawbone,       // 下颌骨
  FBReshapeParam_Chin,          // 下巴
  FBReshapeParam_NoseSlim,      // 瘦鼻梁
  FBReshapeParam_EyeSize,       // 大眼
  FBReshapeParam_EyeDistance,   // 眼距
};

设置美妆参数

objc
[self.beautyEffectEngine setMakeupParam:FBMakeupParam_Lipstick floatValue:0.5f];

支持的美妆参数:

objc
typedef NS_ENUM(NSInteger, FBMakeupParam) {
  FBMakeupParam_Lipstick = 0,  // 口红
  FBMakeupParam_Blush,         // 腮红
};

设置虚拟背景

通过 setBeautyTypeEnabled 接口启用虚拟背景:

objc
[self.beautyEffectEngine setBeautyTypeEnabled:FBBeautyType_VirtualBackground enabled:TRUE];

通过 setVirtualBackground 接口设置虚拟背景:

objc
// 设置背景模式
FBVirtualBackgroundOptions *options = [[FBVirtualBackgroundOptions alloc] initWithMode:FBBackgroundModeBlur];
[self.beautyEffectEngine setVirtualBackground:options];

// 设置背景图片(需要先设置为 Image 模式)
FBVirtualBackgroundOptions *imageOptions = [[FBVirtualBackgroundOptions alloc] initWithMode:FBBackgroundModeImage];
imageOptions.backgroundImage = backgroundImageFrame;  // FBImageFrame 对象
[self.beautyEffectEngine setVirtualBackground:imageOptions];

设置引擎回调

监听License验证和引擎初始化状态:

objc
FBEngineCallbacks *callbacks = [[FBEngineCallbacks alloc] init];
callbacks.onEngineEvent = ^(FBEngineEventCode code, NSString* _Nullable message) {
    if (code == FBEngineEventCodeLicenseValidationSuccess) {
        // License验证成功
        NSLog(@"License验证成功");
    } else if (code == FBEngineEventCodeLicenseValidationFailed) {
        // License验证失败
        NSLog(@"License验证失败: %@", message);
    } else if (code == FBEngineEventCodeInitializationComplete) {
        // 引擎初始化完成
        NSLog(@"引擎初始化完成");
    } else if (code == FBEngineEventCodeInitializationFailed) {
        // 引擎初始化失败
        NSLog(@"引擎初始化失败: %@", message);
    }
};
[self.beautyEffectEngine setCallbacks:callbacks];

事件码:

  • FBEngineEventCodeLicenseValidationSuccess (0):License验证成功
  • FBEngineEventCodeLicenseValidationFailed (1):License验证失败
  • FBEngineEventCodeInitializationComplete (100):引擎初始化完成
  • FBEngineEventCodeInitializationFailed (101):引擎初始化失败

处理图像

创建图像

图像数据通过 FBImageFrame 封装,支持格式: YUVI420, NV12, NV21, RGB, RGBA, BGR, BGRA

通过 rgba 创建 FBImageFrame

objc
FBImageFrame *input_image = [FBImageFrame createWithRGBA:data width:width height:height stride:stride];

通过图片创建 FBImageFrame

objc
FBImageFrame *input_image = [FBImageFrame createWithFile:@"xxx.png"];

旋转图像

FBImageFrame 内置图像旋转方法, 可根据需要使用

objc
- (int)rotate:(FBImageRotation)rotation;

旋转角度

objc
typedef NS_ENUM(NSInteger, FBImageRotation) {
  FBImageRotation0,    // 0度
  FBImageRotation90,   // 顺时针旋转90度
  FBImageRotation180,  // 顺时针旋转180度
  FBImageRotation270,  // 顺时针旋转270度
};

处理图像

processMode 包括 Video 和 Image 两种,Video 适合直播,视频等场景使用,效率更高,Image 模式适合图片处理场景

objc
input_image.type = FBFrameTypeVideo;
FBImageFrame *output_image = [self.beautyEffectEngine processImage:input_image];

获取处理后图像数据

FBImageFrame 现在直接提供了获取图像数据的方法,无需通过 FBImageBuffer。

获取 RGBA 格式数据

objc
// 如果输出格式是 RGBA,直接获取数据
if ([outputImage format] == FBImageFormatRGBA) {
    const uint8_t* data = [outputImage data];
    int dataSize = outputImage.size;
    int width = outputImage.width;
    int height = outputImage.height;
    int stride = outputImage.stride;
}

获取 I420 格式数据

objc
// 如果输出格式是 I420,直接获取 Y、U、V 分量数据
if ([outputImage format] == FBImageFormatI420) {
    // 独立获取 Y, U, V 分量数据
    const uint8_t* dataY = [outputImage dataY];
    const uint8_t* dataU = [outputImage dataU];
    const uint8_t* dataV = [outputImage dataV];
    
    int strideY = [outputImage strideY];
    int strideU = [outputImage strideU];
    int strideV = [outputImage strideV];
    
    int width = outputImage.width;
    int height = outputImage.height;
}

格式转换

如果需要将 FBImageFrame 转换为其他格式,可以使用 convert: 方法:

objc
// 转换为 I420 格式
FBImageFrame *i420Frame = [outputImage convert:FBImageFormatI420];
if (i420Frame) {
    uint8_t* dataY = [i420Frame dataY];
    uint8_t* dataU = [i420Frame dataU];
    uint8_t* dataV = [i420Frame dataV];
    // 使用完毕后,ARC 会自动释放
}

// 转换为 RGBA 格式
FBImageFrame *rgbaFrame = [outputImage convert:FBImageFormatRGBA];
if (rgbaFrame) {
    const uint8_t* data = [rgbaFrame data];
    // 使用完毕后,ARC 会自动释放
}

FBImageFrame 支持转换为以下格式: I420, NV12, NV21, RGB, RGBA, BGR, BGRA

外部纹理处理

使用场景

外部纹理处理适用于以下场景:

  • OpenGL ES/Metal 渲染管线集成:当你的应用已经使用 OpenGL ES 或 Metal 进行渲染时,可以直接使用纹理作为输入和输出,避免 CPU-GPU 之间的数据拷贝
  • 实时视频处理:在视频渲染回调中直接处理纹理,减少内存拷贝开销
  • 性能优化:避免将纹理数据下载到 CPU 内存再上传回 GPU,提升处理效率

配置外部上下文

使用外部纹理处理时,需要在创建引擎时启用 externalContext 选项:

objc
FBEngineConfig *engineConfig = [[FBEngineConfig alloc] init];
engineConfig.appId = @"your appId";
engineConfig.appKey = @"your appKey";
engineConfig.externalContext = YES;  // 启用外部上下文模式

self.beautyEffectEngine = [FBBeautyEffectEngine createEngineWithConfig:engineConfig];

重要提示

  • externalContext = YES 时,引擎不会创建自己的 OpenGL 上下文,而是使用当前线程的 OpenGL 上下文
  • 必须在有效的 OpenGL 上下文中创建引擎和处理图像
  • 输入和输出的纹理必须在同一个 OpenGL 上下文中

创建纹理帧

使用 FBImageFrame.createWithTexture: 方法从 OpenGL 纹理创建图像帧:

objc
// 从 OpenGL 纹理创建 FBImageFrame
GLuint textureId = ...;  // 你的 OpenGL 纹理 ID
int width = 1920;
int height = 1080;
int stride = width * 4;  // RGBA 格式,每像素 4 字节

FBImageFrame *inputFrame = [FBImageFrame createWithTexture:textureId
                                                      width:width
                                                     height:height
                                                     stride:stride];
if (!inputFrame) {
    NSLog(@"Failed to create FBImageFrame from texture");
    return;
}

参数说明

  • textureId:OpenGL 纹理 ID(GL_TEXTURE_2D 类型)
  • width:纹理宽度(像素)
  • height:纹理高度(像素)
  • stride:行跨度(字节),通常为 width * 4(RGBA 格式)

获取输出纹理

处理图像后,可以直接通过 FBImageFrame.texture 属性获取输出纹理:

objc
// 处理图像
inputFrame.type = FBFrameTypeImage;
FBImageFrame *outputFrame = [self.beautyEffectEngine processImage:inputFrame];
if (!outputFrame) {
    NSLog(@"processImage returned nil");
    return;
}

// 获取输出纹理 ID 和尺寸
GLuint outputTextureId = [outputFrame texture];
int outputWidth = outputFrame.width;
int outputHeight = outputFrame.height;

if (outputTextureId == 0) {
    NSLog(@"Output frame does not have a texture");
    return;
}

完整示例

objc
@interface ExternalTextureViewController ()
@property (nonatomic, strong) FBBeautyEffectEngine *engine;
@end

@implementation ExternalTextureViewController

- (int)processVideoFrame:(GLuint)inputTexture 
                  width:(int)width 
                 height:(int)height 
              outputTexture:(GLuint *)outputTexture {
    
    // 延迟初始化引擎(在 OpenGL 上下文中)
    if (!self.engine) {
        FBEngineConfig *config = [[FBEngineConfig alloc] init];
        config.appId = @"your appId";
        config.appKey = @"your appKey";
        config.externalContext = YES;  // 关键:启用外部上下文
        
        self.engine = [FBBeautyEffectEngine createEngineWithConfig:config];
        [self.engine setBeautyTypeEnabled:FBBeautyType_Basic enabled:YES];
        [self.engine setBasicParam:FBBasicParam_Smoothing floatValue:0.5f];
    }
    
    // 从输入纹理创建 FBImageFrame
    int stride = width * 4;
    FBImageFrame *inputFrame = [FBImageFrame createWithTexture:inputTexture
                                                          width:width
                                                         height:height
                                                         stride:stride];
    if (!inputFrame) {
        return -1;
    }
    
    // 处理图像
    inputFrame.type = FBFrameTypeImage;
    FBImageFrame *outputFrame = [self.engine processImage:inputFrame];
    if (!outputFrame) {
        return -2;
    }
    
    // 获取输出纹理 ID
    GLuint outputTextureId = [outputFrame texture];
    if (outputTextureId == 0) {
        return -3;
    }
    
    // 返回输出纹理 ID
    *outputTexture = outputTextureId;
    
    return 0;  // 成功
}

@end

注意事项

1. 上下文要求

  • 必须在有效的 OpenGL 上下文中创建引擎externalContext = YES 时,引擎使用当前线程的 OpenGL 上下文,因此必须在 OpenGL 渲染线程中创建引擎
  • 上下文一致性:输入纹理、引擎处理、输出纹理必须在同一个 OpenGL 上下文中
  • 线程安全:OpenGL 操作必须在同一个线程中执行

2. 纹理格式要求

  • 输入纹理格式:支持 GL_RGBA 格式的 GL_TEXTURE_2D 纹理
  • 纹理参数:建议设置以下纹理参数以获得最佳效果:
    objc
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

3. 性能优化

  • 延迟初始化:在首次渲染回调中初始化引擎,确保在正确的 OpenGL 上下文中创建
  • 复用 FBImageFrame:如果可能,复用 FBImageFrame 对象以减少对象创建开销
  • 处理模式选择
    • FBFrameTypeVideo:适合实时视频流处理,性能更高
    • FBFrameTypeImage:适合单帧图片处理,质量更好

4. 内存管理

  • ARC 自动管理:iOS 使用 ARC 自动管理内存,但仍需注意及时释放不再使用的对象
  • 纹理生命周期:输出纹理由引擎管理,不需要手动删除,但输入纹理需要由调用方管理
  • 避免循环引用:在 block 或回调中使用 self 时,注意使用 __weak 避免循环引用
  • 格式转换后的对象:使用 convert: 转换后的 FBImageFrame 对象,ARC 会在适当时候自动释放

5. 错误处理

  • 检查返回值:所有 API 调用都应该检查返回值
  • 空指针检查:检查 createWithTexture:processImage: 的返回值是否为 nil
  • 纹理有效性:确保输入纹理 ID 有效且已绑定到当前 OpenGL 上下文

6. 常见问题

  • 引擎创建失败:检查是否在 OpenGL 上下文中创建,以及 externalContext 是否正确设置
  • 纹理处理失败:检查纹理格式是否为 RGBA,纹理参数是否正确设置
  • 上下文丢失:如果 OpenGL 上下文被销毁,需要重新创建引擎
  • Metal 支持:目前外部纹理处理仅支持 OpenGL ES,Metal 支持正在开发中

生命周期管理

释放资源

在 ViewController 销毁时,务必释放引擎资源:

objc
- (void)dealloc {
    if (self.beautyEffectEngine) {
        // 注意:FBBeautyEffectEngine 是单例,通常不需要手动释放
        // 但如果有自定义的清理逻辑,可以在这里处理
        self.beautyEffectEngine = nil;
    }
}

内存管理

  • 及时释放 FBImageFrame 对象(ARC 会自动管理)
  • 避免在循环中重复创建大量图像对象
  • 建议复用 FBImageFrame 对象
objc
// 使用完毕后释放资源
if (inputImage) {
    inputImage = nil; // ARC 会自动释放
}
if (outputImage) {
    outputImage = nil; // ARC 会自动释放
}
// 格式转换后的 frame,ARC 也会自动释放
if (i420Frame) {
    i420Frame = nil; // ARC 会自动释放
}

相关文档