You need to enable JavaScript to run this app.
导航

iOS(v4.1.0.0及以上)

最近更新时间2022.02.28 19:42:10

首次发布时间2022.02.25 17:16:51

项目中加入 SDK
  1. 项目中已使用 cocoapods,下一步参考 3,否则参考 2
  2. 在项目根目录,执行 pod init && pod install,可得到 Podfile 文件
  3. 打开 iossample 文件夹,拷贝 BytedEffectSDK.podspec、libeffect-SDK.a、include 到自己项目根目录(Podfile 同级目录)
  4. 打开 Podfile 文件,增加一行 pod 'BytedEffectSDK', :path => './'
  5. 执行 pod install,并打开 项目名.xcworkspace,可以看到在 Pods/Development Pods 目录下已有 BytedEffectSDK
  6. 添加素材,将提供的素材包(一般是 resource 文件夹)添加到工程中
代码中集成 SDK

以下指南针对使用 sample 中封装的 Objective-C 代码进行集成,如果直接在项目中使用 CV SDK 提供的 C 接口集成,参见 接口说明-特效接口说明-算法

准备阶段

  1. 拷贝 iossample 项目中的 Core/Core 目录下的文件到自己项目中
  2. 如果需要使用 sample 中提供的视频采集、绘制接口(如果项目中已接入推流或有自己的图像处理,一般不需要这一步),同时拷贝 Common/Common 目录下文件到自己项目中,使用参见 使用 Sample 中的视频采集、绘制接口
  3. (可选)
    SDK版本为v4.2.1的情况,需要将BEEffectLicenseHelper.mm中的LICENSE_MODE修改为OFFLINE_LICENSE,
    image.png
    并将Config.h中的LICENSE_NAME改成绑定了自身应用包名的license的名字。
  4. (可选)
    SDK为v4.2.3及以上的情况,需要将BELicenseHelper.mm中的LICENSE_MODE修改为OFFLINE_LICENSE,并将Config.h中的LICENSE_NAME改成绑定了自身应用包名的license的名字。

以上为主要接口,项目中的其他代码功能可以参考 Sample 文件结构,如果其他代码也有需要,可将这些也拷贝到自己项目中。

使用阶段

SDK 的封装接口主要都是在 Core 模块下,分为特效、算法、画质等,每一种功能都有单独的封装接口,可分别使用。

使用特效

SDK 特效的统一封装接口为 BEEffectManager,SDK 的使用可以分为三个步骤:

  1. 初始化 SDK
  2. 使用 SDK 进行图像处理
  3. SDK 参数设置,如设置美颜、贴纸、滤镜等

注意,特效 SDK 全程依赖 OpenGL 环境,请保证所有 SDK 的函数调用都处于同一个 GlContext 下。

1.初始化 SDK

初始化 SDK 对应的方法为:

- (instancetype)initWithResourceProvider:(id<BEEffectResourceProvider>)provider;

CV SDK 的使用依赖于 openGL 环境,在调用 SDK 函数之前,先调用函数:

[EAGLContext setCurrentContext:context];

将上下文设置好。

如果项目中没有 GlContext,可以使用如下代码创建:

EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];

2.使用 SDK 进行图像处理

SDK 的处理方法主要为 BEEffectManager#processTexture:outputTexture:width:height:rotate:timeStamp,

/// @brief SDK 处理
/// @param texture 输入纹理
/// @param outputTexture 输出纹理
/// @param width 宽
/// @param height 高
/// @param rotate 算法检测角度
/// @param timeStamp 时间戳
- (bef_effect_result_t)processTexture:(GLuint)texture outputTexture:(GLuint)outputTexture width:(int)width height:(int)height rotate:(bef_ai_rotate_type)rotate timeStamp:(double)timeStamp;

它只能处理 2D 纹理并将结果输出到传入的 2D 纹理 outputTexture 中,如果当前的项目中无法直接拿到 2D 纹理,可以先试用 BEImageUtils 进行格式转换,这个类可以进行 CVPixelBuffer/Texture/Buffer 几种数据的转换,可根据实际情况使用,详情参见 BEImageUtils 使用。例如:

id<BEGLTexture> texture = [self.imageUtils transforCVPixelBufferToTexture:pixelBuffer];
// 获取对应的 OpenGL 纹理
int inputTexture = texture.texture;
// 获取对应的纹理的宽高
int inputWidth = texture.width;
int inputHeight = texture.height;

该函数还需要一个参数 timeStamp,即当前帧的时间,获取方法参见 timeStamp 获取

函数的输出也是 2D 纹理,如果没有现成的纹理,可以使用 BEImageUtils 创建,例如:

BEPixelBufferGLTexture *outputTexture = [self.imageUtils getOutputPixelBufferGLTextureWithWidth:texture.width height:texture.height format:BE_BGRA];
// 获取对应的 OpenGL 纹理
int outTexture = outputTexture.texture;
// 获取对应的 CVPixelBuffer
CVPixelBufferRef outputPixelBuffer = outputTexture.pixelBuffer;

在接入测试时建议将输入输出都利用 BEImageUtils 转成 BECVPixelBuffer,可以方便地观察 SDK 的输入输出,排查可能遇到的问题。

3.SDK 参数设置,如设置美颜、贴纸、滤镜等

注意,SDK 参数设置需要在初始化之后调用,请尽量与 SDK图像处理 处于同一线程使用,以避免可能出现的问题。

设置美颜、美型、美妆

美颜、美型、美妆的设置使用的是同一个接口,一般来说使一个美颜生效需要两步:

  1. 设置素材对应的路径
  2. 设置素材中,特效的强度(一般强度默认为 0,所以这一步不执行会没有效果)

设置素材路径接口

/// @brief 设置特效素材
/// @details 设置 ComposeMakeup.bundle 下的所有功能,包含美颜、美形、美体、美妆等
/// @param nodes 特效素材相对 ComposeMakeup.bundle/ComposeMakeup 的路径
- (void)updateComposerNodes:(NSArray<NSString *> *)nodes;

/// @brief 设置特效素材
/// @details 设置 ComposeMakeup.bundle 下的所有功能,包含美颜、美形、美体、美妆等
/// @param nodes 特效素材相对 ComposeMakeup.bundle/ComposeMakeup 的路径
/// @param tags 每一个特效素材对应一个 tag,tag 会传递给 SDK 素材的的一些配置
- (void)updateComposerNodes:(NSArray<NSString *> *)nodes withTags:(NSArray<NSString *> *)tags;

// 示例
[self.manager updateComposerNodes:[NSArray arrayWithObject:@"beauty_IOS_live"]];

此处的素材路径,是相对于 ComposeMakeup.bundle/ComposeMakeup 的路径,素材包结构参见 素材包结构说明

注意,SDK 内部不会保存已设置的素材,所以此方法每次调用都需要将所有需要生效的素材路径加上。

设置素材中,特效强度接口

/// @brief 更新组合特效中某个功能的强度
/// @param node 特效素材相对于 ComposeMakeup.bundle/ComposeMakeup 的路径
/// @param key 素材中的功能 key
/// @param intensity 强度 0-1
- (void)updateComposerNodeIntensity:(NSString *)node key:(NSString *)key intensity:(float)intensity;

// 示例
[self.manager updateComposerNodeIntensity:@"beauty_IOS_live" key:@"whiten" intensity:0.8];

以美颜素材为例,需要通过 key 来控制我们要修改的是美白、磨皮还是锐化的强度,素材中 key 与功能的对应关系参见 素材 key 对应说明

设置贴纸

设置贴纸接口

/// @brief 设置贴纸路径
/// @details 贴纸素材的文件路径,相对 StickerResource.bundle 路径,为 null 时为关闭贴纸
/// @param path 贴纸路径 relative path of sticker
- (void)setStickerPath:(NSString*) path;

// 示例
[self.manager setStickerPath:@"baibianfaxing"];

此处的贴纸路径为素材包中 StickerResource.bundle/stickers 中的相对路径。

设置滤镜

设置滤镜的接口

/// @brief 设置滤镜路径
/// @details 相对 FilterResource.bundle/Filter 路径,为 null 时关闭滤镜
/// @param path 相对路径
- (void)setFilterPath:(NSString *) path;

/// @brief 设置滤镜强度
/// @param intensity 滤镜强度,0-1
- (void)setFilterIntensity:(float)intensity;

// 示例
[self.manager setFilterPath:@"/Filter_01_38"];
[self.manager setFilterIntensity:0.8];

此处的滤镜路径为素材包中 FilterResource.bundle/Filter 中的相对路径。

使用算法

算法对应的代码封装在 Core/Core/Algorithm 目录下,每一个算法都是作为单独封装存在的,互相之间一般不会有依赖。比如人脸检测对应的封装为 BEFaceAlgorithmTask,手势检测对应的封装为 BEHandAlgorithmTask,他们有一个基类 BEAlgorithmTask 定义了所有算法的通用接口,下面以人脸检测举例说明算法使用流程。

1.算法初始化

算法的初始化函数定义在 BEAlgorithmTask 中,

- (instancetype)initWithProvider:(id<BEAlgorithmResourceProvider>)provider;

其中 BEAlgorithmResourceProvider 为算法提供了一些资源信息,例如模型文件,每一个算法所需的资源不尽相同,所以每一个算法都有自己的定义,人脸检测的定义如下:

@protocol BEFaceResourceProvider <BEAlgorithmResourceProvider>

- (const char *)faceModel;
- (const char *)faceExtraModel;
- (const char *)faceAttrModel;

@end

每一个算法定义的 ResourceProvider 都可以在各自的头文件中找到,外部使用算法的时候,需要有一个类来实现这个协议,一般情况下,可以直接使用工程中自带的 BEAlgorithmResourceHelper,这个类实现了所有算法的 ResourceProvider。

以上为创建算法实例,创建完成之后还需要调用初始化函数才能完成算法初始化:

/// @brief 初始化算法
- (int)initTask;

返回值表示初始化结果。

2.算法检测

算法检测的函数也定义在 BEAlgorithmTask 中,

/// @brief 算法调用
/// @param buffer buffer
/// @param width width
/// @param height height
/// @param stride stride,一般是 width * 4
/// @param format format
/// @param rotation rotation
- (id)process:(const unsigned char *)buffer width:(int)width height:(int)height stride:(int)stride format:(bef_ai_pixel_format)format rotation:(bef_ai_rotate_type)rotation;

所有算法的算法检测函数都是这个,需要传入一个 buffer 以及相关信息。如果当前的项目中只有 CVPixelBuffer 的话,可以使用 BEImageUtils 进行转换:

BEBuffer *buffer = [self.imageUtils transforCVPixelBufferToBuffer:pixelBuffer outputFormat:BE_BGRA];
unsigned char *inputBuffer = buffer.buffer;
int width = buffer.width;
int height = buffer.height;
int stride = buffer.bytesPerRow;

算法检测的结果在基类的声明中是一个 id,即也是每一个算法都不一样的,比如人脸检测的结果为 BEFaceAlgorithmResult,每一个算法的检测结果,同样可以在各自的头文件中找到定义。

3.算法参数设置

算法参数设置的函数定义为:

/// @brief 设置算法参数
/// @param key 参数 key
/// @param p 参数值
- (void)setConfig:(BEAlgorithmKey *)key p:(NSObject *)p;

其中 key表示参数的类型,p 为参数值,每一个算法的参数类型可以在算法的定义中看到,如人脸检测算法的定义:

@interface BEFaceAlgorithmTask : BEAlgorithmTask

+ (BEAlgorithmKey *)FACE_106;
+ (BEAlgorithmKey *)FACE_280;
+ (BEAlgorithmKey *)FACE_ATTR;
+ (BEAlgorithmKey *)FACE_MASK;
+ (BEAlgorithmKey *)MOUTH_MASK;
+ (BEAlgorithmKey *)TEETH_MASK;

@property (nonatomic, assign) unsigned long long initConfig;
@property (nonatomic, assign) unsigned long long detectConfig;
@property (nonatomic, assign) int maxFaceNum;

@end

它有 FACE_280、FACE_ATTR 这几种 key,我们可以使用 setConfig:p 函数来设置是否开启 280 点检测等,如

[self.algorithmTask setConfig:BEFaceAlgorithmTask.FACE_280 p:[NSNumber numberWithBool:YES]];

表示开启人脸检测的 280 点检测。

附录:使用 Sample 中的视频采集、绘制接口

Common 模块中提供了 BEVideoSourceProvider 接口,可用于实现相机数据的采集或图片、视频的图像流输出,每一种模式都有一个单独的类。BEVideoCapture 的回调代理为 BEVideoCaptureDelegate,不同模式下的回调方法不同,会在下面说明。

相机预览模式

相机预览模式对应的为 BEVideoCapture,其内部通过系统的 AVFoundation 功能进行相机数据采集。

初始化示例:

_capture = [[BEVideoCapture alloc] init];
_capture.delegate = self;

使用相机预览模式,需要实现的代理方法为:

/// @brief 输出 CMSampleBufferRef 回调
- (void)videoCapture:(id<BEVideoSourceProtocol>)source didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer withRotation:(int)rotation;

图片处理模式

图片处理模式对应的类为 BEImageCapture,其内部将图片的 buffer 通过 NSTimer 模拟成一个视频流进行输出。

初始化示例:

_capture = [[BEImageCapture alloc] initWithImage:self.image];
_capture.delegate = self;

使用图片模式,需要实现的代理方法为:

/// @brier 输出 buffer 回调
/// @param source source
/// @param buffer buffer
/// @param width 宽
/// @param height 高
/// @param bytesPerRow bytesPerRow
/// @param format buffer 格式
/// @param timeStamp 时间戳
- (void)videoCapture:(id<BEVideoSourceProtocol>)source didOutputBuffer:(unsigned char *)buffer width:(int)width height:(int)height bytesPerRow:(int)bytesPerRow format:(OSType)format timeStamp:(double)timeStamp;

视频处理模式

视频处理模式对应的类为 BELocalVideoCapture,其内部通过 AVAssetReader 将本地视频解码为 CMSampleBuffer 输出。

初始化示例:

_capture = [[BELocalVideoCapture alloc] initWithAsset:self.asset];
_capture.delegate = self;

使用视频模式,需要实现的代理方法为:

/// @brief 输出 CMSampleBufferRef 回调
- (void)videoCapture:(id<BEVideoSourceProtocol>)source didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer withRotation:(int)rotation;

处理结果绘制

Common 中使用 BEGLView 继承 GLKView,在内部通过 openGL 对纹理进行绘制,如果自己项目中暂时没有将 CV SDK 处理后的图像绘制到屏幕上的方法,可以使用这个类。然后通过下面的方法绘制纹理:

- (void)renderWithTexture:(unsigned int)name
                     size:(CGSize)size
                  flipped:(BOOL)flipped
      applyingOrientation:(int)orientation
                  fitType:(int)fitType;
附录:timeStamp 获取

如果是使用 SDK 处理相机输出的 CMSampleBufferRef,可通过如下方式获取:

CMTime sampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
double timeStamp = (double)sampleTime.value/sampleTime.timescale;

否则通过如下方式获取:

double timeStamp = [[NSDate date] timeIntervalSince1970]];
附录:BEImageUtils 使用

创建空纹理

代码:

BEPixelBufferGLTexture *outputTexture = [self.imageUtils getOutputPixelBufferGLTextureWithWidth:texture.width height:texture.height format:BE_BGRA];
// 获取对应的 OpenGL 纹理
int outTexture = outputTexture.texture;
// 获取对应的 CVPixelBuffer
CVPixelBufferRef outputPixelBuffer = outputTexture.pixelBuffer;

CVPixelBuffer 转成 2D 纹理

代码:

id<BEGLTexture> texture = [self.imageUtils transforCVPixelBufferToTexture:pixelBuffer];
// 获取对应的 OpenGL 纹理
int inputTexture = texture.texture;
// 获取对应的纹理的宽高
int inputWidth = texture.width;
int inputHeight = texture.height;

CVPixelBuffer 转成 buffer

代码:

BEBuffer *buffer = [self.imageUtils transforCVPixelBufferToBuffer:pixelBuffer outputFormat:BE_BGRA];
unsigned char *inputBuffer = buffer.buffer;
int width = buffer.width;
int height = buffer.height;
int stride = buffer.bytesPerRow;