You need to enable JavaScript to run this app.
导航
PC接入指南
最近更新时间:2025.03.11 19:27:05首次发布时间:2025.03.11 19:20:36
我的收藏
有用
有用
无用
无用

为了方便开发者快速接入Effect SDK,我们以源码的形式提供了桌面端不同开发框架的接入demo,本文档旨在阐述demo工程的使用,如果你想要直接使用Effect SDK底层C接口,请参考通用API接口说明-特效及demo内源码。
Effect SDK的使用主要包括:

  1. OpenGL context管理
  2. 输入/输出图片的格式
  3. Effect SDK初始化
  4. Effect SDK特效/算法功能使用
  5. Effect SDK销毁

用伪代码来大致展示为:

// Windows需要额外加载初始化angle动态库
angle::LoadEGL();
angle::LoadGLES();

// 创建OpenGL context
gl_context = new OpenGLContext();
// 创建Effect SDK句柄
gl_context->makeCurrent();
bef_effect_handle_t handle;
bef_effect_ai_create(&handle);
// 在线授权验证及初始化Effect SDK
bef_effect_ai_check_license(handle, license_path);
bef_effect_ai_init(handle, ...);
gl_context->releaseCurrent();


// 设置期望生效的特效
bef_effect_ai_set_effect(handle, effect_path);
bef_effect_ai_composer_set_nodes_with_tags(handle, ...);


// 调用Effect SDK进行特效处理
while(continue_effect_process){
    gl_context->makeCurrent();
    bef_effect_ai_algorithm_texture(handle, ...);
    /* 
    * in_texture: 输入图片的原始纹理
    * out_texture: Effect SDK进行特效处理之后的纹理
    * 成功调用之后out_texture即为你期望特效效果的纹理,可以进行渲染或其他操作
    */
    bef_effect_ai_process_texture(handle, in_texture, out_texture, frame_timestamp);
    gl_context->releaseCurrent();
}
// 销毁Effect SDK句柄
gl_context->makeCurrent();
bef_effect_ai_destroy(handle);
gl_context->releaseCurrent();
delete gl_context;

注意⚠️

  1. 除设置期望生效的特效外的代码块必须在同一个线程(最好是一个单独的线程)且必须在有效的OpenGL context上下文内执行,而设置期望生效的特效的代码块可以在其他线程执行;即Effect SDK的初始化/销毁,特效处理必须在GL线程内进行,而特效开关设置可以在其他线程调用。
  2. 道具包是与App key/secret绑定的,前期集成测试可以使用我们demo中提供的key/secret与道具包,后期可以直接替换成自己的key/secret与道具包;如果你有多个key/secret,请不要混用对应的道具包。

下面我们会以各个不同开发框架的demo进行详细的阐述。

Windows

Qt Demo

Qt demo是一个采集摄像头进行实时特效渲染处理的app,涵盖了Effect SDK种类繁多的特效(滤镜,贴纸,美妆,美体,虚拟背景等等)以及算法(人脸识别,手势识别,人体分割等等)能力,同时demo内使用C++对底层的Effect SDK的C接口进行了封装,方便开发者更快速接入Effect SDK。

推荐开发环境

  • Qt 5.15.2 (非必须)
  • Visual Studio 2019
  • CMake 3.14

注意⚠️
Windows平台上Effect SDK依赖angle动态库的能力,所以需要加载并初始化对应的组件(libGLESv2.dll与libEGL.dll),详情参考下述EffectManager模块的说明
OpenGL context必须为使用angle库创建的,请不要使用其他的GL context,详情请参考BEFEffectGLContext模块

主要组件及关键API说明

主要组件及关键API说明
EffectManager管理EffectHandle以及加载并初始化angle动态库符号
loadEGLDLL/unloadEGLDLL初始化angle动态库符号
createHandle/releaseHandle管理EffectHandle对象
EffectHandle封装了底层Effect SDK的C接口,提供特效处理操作,如果你需要创建多个EffectHandle对象(比如同时采集多个摄像头视频流进行特效处理),那么需要打开MULTI_HANDLE_USE宏

setInput

设置需要特效处理的原始图片buffer,格式必须为:RGB/RGBA,BGR/BGRA,I420,同步操作,调用完成后即可通过getOutput获得结果

getOutput获取特效处理后的图片buffer,格式和输入格式一致,buffer内存由EffectHandle对象管理,调用者无需释放
updateComposerNodes/setFilter/setSticker设置预期的特效
BEFEffectGLContext提供OpenGL context管理
initGLContext/releaseGLContext初始化/销毁GL context
makeCurrentContext/doneCurrentContext设置GL context
BEImageUtil提供BGR/BGRA,RGB/RGBA,I420格式的shader转换
transferBufferToTexture将指定格式的图片转换为指定格式的GPU纹理
transferTextureToTexture将指定格式的纹理转换为另一个格式的纹理
readPixel将纹理中的内容读取到CPU buffer中
BEAlgorithmManager封装了算法检测相关接口,提供快速进行算法检测的能力
initProcessInfo/initResourceFinder初始化算法检测模块
addAlgorithm/removeAlgorithm开启/关闭要检测的算法小项
processAlgorithm执行算法检测
getInputInfo设置输入buffer
getOutputInfo获取算法检测结果

BELicenseVC.h
license_requester.h

展示如何进行在线鉴权及license的下载校验,详细的鉴权原理及过程参考在线授权说明

BEDataManager封装了特效道具包资源的使用,包括道具包内tag字段的使用,如果有某一个道具不知道如何使用,请参考BEDataManager内的使用方法

以上组件中,除了BELicenseVC和BEDataManager外,其他组件推荐直接集成到自己的项目中使用,可以大幅降低集成开发的成本。

工程结构(查看Demo源码CMakeLists.txt,按需进行编译)

add_subdirectory(EffectManager) # 用C++封装底层SDK的C接口,一般接入直接用这里提供的C++接口即可
add_subdirectory(Algorithm)     # 提供算法(人脸,手势等)的接入封装代码
add_subdirectory(app)           # UI部分,展示如何使用Algorithm和EffectManager封装的接口,这里UI框架使用的是Qt 5.15.2

如果只想接入特效相关类型的功能(包括美颜,滤镜,美妆,贴纸等等)

只编译EffectManager即可,然后调用里面的EffectHandle对象接口就可以了,大致的流程为:

// 创建Effect SDK句柄管理器
IEffectManager* manager = createEffectManager();

// 创建美颜句柄
IEffectHandle* handle = manager->createHandle();

// 传入license,model文件路径
// license文件通过使用demo源码的licenseManager目录里面的接口通过curl下载到本地
// model文件根据购买的道具不同会对应不同的模型文件,可以联系商务获取,也可以先试用我们demo的模型进行测试
handle->initResource();

// 传入道具包的路径,设置想要开启的特效
// 路径为道具包绝对路径,所有路径均需确保是utf-8编码
handle->updateComposerNodes();

// 传入图片buffer进行特效处理,buffer格式目前支持RGB/RGBA, BGR/BGRA, I420
// 其他格式需要先转换成这些格式才能传给特效SDK
handle->setInput();

// 获取特效处理后的图片buffer,返回的buffer格式和输入的格式一致
handle->getOutput();

// 销毁Effect SDK句柄
handle->releaseHandle();

// 销毁美颜句柄管理器
destroyEffectManager(manager);

详细的流程可以参考demo内VideoFilter.cpp里面对EffectHandle的使用。

注意⚠️

  1. 传入道具包路径参数的时候必须是全路径,且字符串为UTF8编码
  2. 使用IEffectHandle的接口时可以无需考虑OpenGL context设置,IEffectHandle对象内部已经进行了封装处理
  3. IEffectHandle对象是线程安全的,可以在其他线程调用,但是如果你想同时对多路视频流进行特效处理,比如同时采集多个摄像头视频流,那么最好创建多个IEffectHandle对象,每一个对象单独处理一路视频,需要打开MULTI_HANDLE_USE宏
  4. 特效处理是耗时操作,所以尽量不要在UI线程调用特效接口,以免阻塞App UI

如果想要接入算法相关的功能(包括人脸检测,手势检测,人体关键点等等)

那么编译EffectManager以及Algorithm即可,在成功搭建了EffectManager之后,可以参考Demo内"BEAlgorithmVC"内对算法相关功能的处理,大致的流程为:

// 和前面的特效处理流程一样
// 需要先创建并成功初始化IEffectManager以及IEffectHandle对象
// 详见前述特效处理流程

// 创建IAlgorithmManager对象
IAlgorithmManager* algo_mgr = createAlgorithmManager();

// 初始化算法模块,提供license和模型文件路径
// 路径为绝对路径,所有路径均需确保是utf-8编码
algo_mgr->initResourceFinder();
algo_mgr->initProcessInfo();

// 注册渲染回调到IEffectHandle中
// 这里用一个lambda对象作示例
IEffectHandle->setRenderCallback([](){
    // 在IEffectHandle的渲染回调中做算法检测
    // 将IEffectHandle吐出的buffer输入到IAlgorithmManager中
    algo_mgr->getInputInfo()->clear();
    algo_mgr->getInputInfo()->m_buffer = input->frame.imageData;
    algo_mgr->getInputInfo()->m_width = input->frame.width;
    algo_mgr->getInputInfo()->m_height = input->frame.height;
    algo_mgr->getInputInfo()->m_bytePerRow = input->frame.width * input->frame.channel;
    algo_mgr->getInputInfo()->m_format = (bef_ai_pixel_format)input->pixelFormat;
    // 这里的orientation根据实际输入填写,详见头文件中bef_ai_rotate_type注释
    algo_mgr->getInputInfo()->m_ori = BEF_AI_CLOCKWISE_ROTATE_0;
    
    // 执行算法检测
    algo_mgr->processAlgorithm();
    
    // 拿到检测结果
    result = algo_mgr->getOutputInfo();
});


// 添加想要执行的算法检测小项
// 详见BE_FEATURE枚举类型定义
algo_mgr->addAlgorithm();

// 关闭指定的检测小项
algo_mgr->removeAlgorithm();

// 销毁IAlgorithmManager对象
destroyAlgorithmManager();

详细的算法检测流程可以参考demo内BEAlgorithmVC.cpp模块。

如果想要对demo进行源码级的调试开发

那么还需要编译app模块,需要搭建Qt 5.15.2的环境。

  • 开发环境

目前源码目录中已经包含了Effect SDK的开发文件与资源文件:
头文件:third/effect_sdk/inc
库文件:third/effect_sdk/libs
美颜素材:app/EffectResource/resource/ComposeMakeup
贴纸素材:app/EffectResource/resource/stickers_resource
滤镜素材:app/EffectResource/resource/Filter
模型文件:app/EffectResource/resource/model

自己集成时使用 sdk的编译压缩包,不要直接使用demo目录的effect_sdk库,方便后续的问题跟进。

  • 编译运行
    编译可以使用demo内的build.bat或build_64.bat脚本(编译输入参数Debug or Release, 如:./build.bat Debug), 编译前需要编辑build.bat和build_64.bat脚本修改Qt的路径和VS为你本机的路径,如果你使用的不是VS2019,记得同时修改cmake参数:
set QtPath="C:\Qt\5.15.2\msvc2019"
set QtBinPath="C:\Qt\5.15.2\msvc2019\bin"
set MS_BUILD="C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"

cmake -G "Visual Studio 16 2019" ../ -A Win32 -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DPLATFORM=%PLATFORM% -DCMAKE_PREFIX_PATH=%QtPath% -DENABLE_PROFILE=%PROFILE_STATUS%

build成功之后会在对应目录生成VS工程文件 ByteEffects.sln,打开即可编译调试。

编译也可以直接用Qt Creator(建议版本6.0.0以上)直接打开当前目录的CMakeLists.txt,然后进行编译运行。

选择32位或64位的配置:
Image
配置参数参考:
Image

Win32 Demo

Win32 demo利用了Qt demo内的EffectManager模块,使用OpenCV渲染来展示基本的特效处理,如果你觉得Qt demo工程太庞大不好搭建调试,那么可以考虑尝试构建Win32 demo,执行demo内的recource_copy_win.bat执行资源拷贝后,打开EffectDemo内的VS工程编译即可。

注意⚠️
目前线上Windows端的标品只提供Qt Demo,如需Win32 Demo,请联系我们单独获取。

推荐的开发环境

  • Visual Studio 2019

工程结构

EffectManager // 和Qt demo内的一样,提供底层Effect SDK的封装
EffectBaseApp // 使用OpenCV,展示基本的美颜特效处理流程

核心逻辑说明

// 创建IEffectManager
IEffectManager*  effectManager = createEffectManager();

// 加载并初始化angle动态库符号
effectManager->loadEGLDLL();

// 验证本地license,如果license校验失败则尝试拉取最新的在线license并保存到本地
std::string licensePath = appPath + "license/license.bag";
int licenseStatus = effectManager->checkLicenseStatus(licensePath.c_str());
if (licenseStatus != 0) {
    // request license file
    char* authMsg = nullptr;
    int authMsgLen = 0;
    char* license = nullptr;
    int len = 0;
    effectManager->getAuthMessage(&authMsg, &authMsgLen);

    // 这里替换为你自己申请的key/secret
    std::string key = "biz_license_tool_test_keyd64a6d073fbd42f8979a626572220595";
    std::string secret = "58979358c3e69215762eb5ecc3df7c9e";
    int code = request_license(key.c_str(), secret.c_str(), authMsg, &license, &len);
    if (code == 0) {
        FileUtil::createDirectory(appPath + "license");
        FileUtil::writeToFile(licensePath.c_str(), (unsigned char*)license, len);
        delete[] license;
    }
    effectManager->releaseAuthMessage(authMsg);
}

// 设置资源路径
std::string modelDir = appPath + "../../resource/model/";
std::string composerDir = appPath + "../../resource/ComposeMakeup/";
std::string stickerDir = appPath + "../../resource/stickers_resource/";
std::string filterDir = appPath + "../../resource/Filter/";

// 构造并初始化IEffectHandle
IEffectHandle* effectHandle = effectManager->createHandle();
effectHandle->setUsePBO(true);
effectHandle->initResource(licensePath.c_str(), modelDir.c_str());

// 设置美颜道具
#ifdef TEST_BEAUTY
std::vector<std::string> nodePaths;
std::vector<std::string> tags;

nodePaths.push_back(composerDir + "beauty_IOS_lite");
nodePaths.push_back(composerDir + "beauty_IOS_lite");
nodePaths.push_back(composerDir + "beauty_IOS_lite");
nodePaths.push_back(composerDir + "reshape_lite");
nodePaths.push_back(composerDir + "beauty_IOS_lite");

// 设置风格妆
#ifdef TEST_STYLE_MAKEUP
nodePaths.push_back(composerDir + "style_makeup/baicha");
#endif

// 设置美颜道具小项的tag
tags.push_back("smooth");
tags.push_back("whiten");
tags.push_back("sharp");
tags.push_back("Internal_Deform_Overall");
tags.push_back("Internal_Deform_Eye");

// 设置风格妆小项的tag,没有tag可以填空字符串
#ifdef TEST_STYLE_MAKEUP
tags.push_back("");
#endif

// 设置nodePaths中添加的所有特效
effectHandle->updateComposerNodes(nodePaths, tags);

// 设置不同小项的强度
// 使用“道具包路径,道具包tag”来定位具体要设置的美颜小项
// 每一个道具包内部的tag(如果有)都可能不一样,具体可以参考Qt demo内‘BEDataManager’对象内部的代码
// 或者查看通用API文档:https://cv-api.bytedance.com/doc/openapi/2036/200123
effectHandle->updateComposerNodeIntensity(nodePaths[0].c_str(), tags[0].c_str(), 0.5);
effectHandle->updateComposerNodeIntensity(nodePaths[1].c_str(), tags[1].c_str(), 0.35);
effectHandle->updateComposerNodeIntensity(nodePaths[2].c_str(), tags[2].c_str(), 0.7);
effectHandle->updateComposerNodeIntensity(nodePaths[3].c_str(), tags[3].c_str(), 0.35);
effectHandle->updateComposerNodeIntensity(nodePaths[4].c_str(), tags[4].c_str(), 0.35);

#ifdef TEST_STYLE_MAKEUP
effectHandle->updateComposerNodeIntensity(nodePaths[5].c_str(), "Makeup_ALL", 0.8);
effectHandle->updateComposerNodeIntensity(nodePaths[5].c_str(), "Filter_ALL", 0.5);
#endif

#endif

// 设置滤镜及滤镜强度
#ifdef TEST_FILTER
std::string filterPath = filterDir + "Filter_02_14";
effectHandle->setFilter(filterPath.c_str());
effectHandle->setFilterIntensity(0.8);
#endif

// 设置贴纸
#ifdef TEST_STICKER
std::string stickerPath = stickerDir + "stickers/heimaoyanjing";
effectHandle->setSticker(stickerPath.c_str());
#endif

// 采集摄像头视频源进行特效处理
int count = 0;
for (; cv::waitKey(1) != 27; cam >> frame)
{
    if (frame.rows == 0 || frame.cols == 0) {
        continue;
    }

    // 设置要处理的图片buffer
    // 格式必须为:BGR/BGRA/RGB/RGBA/I420,其他格式需要转换成这些格式之后才能使用
    effectHandle->setInput(imageWidth, imageHeight, frame.data, pixel_format_rgba);
    // 获得特效处理之后的buffer,格式和输入格式保持一致
    unsigned char* data = effectHandle->getOutput();
    cv::Mat framOut(imageHeight, imageWidth, CV_8UC4, data);
    cvtColor(framOut, framOut, cv::COLOR_RGBA2BGR);

    //cvtColor(framOut, framOut, cv::COLOR_RGBA2BGR);
    imshow("EffectBaseApp", framOut);
}

// 销毁IEffectHandle及IEffectManager
effectManager->releaseHandle(effectHandle);
destroyEffectManager(effectManager);

demo的逻辑比较简单易懂,主要是利用EffectManager模块的能力,EffectManager的具体使用可以参考Qt demo中的说明。

C# Demo

C# demo和Win32 demo一样,利用了EffectManager模块封装的能力,提供使用C#采集摄像头视频流进行特效处理的示例。

注意⚠️
目前线上Windows端的标品只提供Qt Demo,如需C# Demo,请联系我们单独获取。

推荐的开发环境

  • .Net Core 3.1
  • Visual Studio 2019

工程结构

ByteEffect.cs // 封装C++ EffectManager模块的胶水层,详见Qt demo中EffectManager模块的说明
Program.cs    // 使用OpenCV,展示基本的美颜特效处理流程

核心逻辑说明

// 加载并初始化angle动态库符号
ByteEffect.loadEGLDLL();

var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
var appDir = Path.GetDirectoryName(exePath);

var licensePath = $@"{appDir}\effect\license\license.bag";
var licensePathPtr = MallocIntPtr(licensePath);

// 校验license,失败则拉取最新的license到本地
var licenseStatus = ByteEffect.checkLicenseFile(licensePathPtr);
if (licenseStatus != 0) {
    Console.WriteLine($@"license error: {licenseStatus}");

    // replace your key and secret
    string key = "biz_license_tool_test_keyb2cc169b1c7340dfaa2c70027acb99f5";
    string secret = "dc5468fe3f3b555cda0d04b9f856f5a3";

    var licenseDir = $@"{appDir}\effect\license\";
    if (!Directory.Exists(licenseDir))
        Directory.CreateDirectory(licenseDir);

    RequestLicenseFile(licensePath, key, secret);
}

// 创建EffectHandle对象
IntPtr effectHandle = ByteEffect.createEffectHandle();

var resourceDir = $@"{appDir}\..\..\..\resource";
var modelDirPtr = MallocIntPtr($@"{resourceDir}\model");

var composerPath = $@"{resourceDir}\ComposeMakeup";
var filterPath = $@"{resourceDir}\Filter";
var stickerPath = $@"{resourceDir}\stickers_resource\stickers";

// 初始化Effect SDK
ByteEffect.initResource(effectHandle, licensePathPtr, modelDirPtr);

// 设置滤镜及滤镜强度
#if TEST_FILTER
var filterPathPtr = MallocIntPtr(filterPath + "\\Filter_02_14");
ByteEffect.setFilter(effectHandle, filterPathPtr);
ByteEffect.setFilterIntensity(effectHandle, 0.8f);
FreeIntPtr(filterPathPtr);
filterPathPtr = IntPtr.Zero;
#endif

// 设置贴纸
#if TEST_STICKER
// only one sticker can exist at the same time
var stickerPathPtr = MallocIntPtr(stickerPath + "\\heimaoyanjing");
ByteEffect.setSticker(effectHandle, stickerPathPtr);
FreeIntPtr(stickerPathPtr);
stickerPathPtr = IntPtr.Zero;

// if you want to reset sticker, you can do like this code
// var emptyPathPtr = MallocIntPtr(stickerPath + "");
// ByteEffect.setSticker(effectHandle, stickerPath + "");
// FreeIntPtr(emptyPathPtr);
#endif

// 设置虚拟背景
#if TEST_VIRTUAL_BACKGROUND
var mattingPathPtr = MallocIntPtr(stickerPath + "\\matting_bg");
var bgPathPtr = MallocIntPtr(resourceDir + "\\bg.png");
var keyPtr = MallocIntPtr(RENDER_CACHE_TETURE_KEY);
ByteEffect.setSticker(effectHandle, mattingPathPtr);
ByteEffect.setRenderCacheTexture(effectHandle, keyPtr, bgPathPtr);
FreeIntPtr(mattingPathPtr);
FreeIntPtr(bgPathPtr);
FreeIntPtr(keyPtr);
mattingPathPtr = IntPtr.Zero;
bgPathPtr = IntPtr.Zero;
keyPtr = IntPtr.Zero;
#endif

// 设置美颜
#if TEST_COMPOSER
var beautyPathPtr = MallocIntPtr(composerPath + "\\beauty_IOS_lite");
var reshapePathPtr = MallocIntPtr(composerPath + "\\reshape_lite");

IntPtr[] nodes = new IntPtr[3];
nodes[0] = beautyPathPtr;
nodes[1] = reshapePathPtr;

// 设置风格妆
#if TEST_STYLE_MAKEUP
// set style makeup resource
var aidouPathPtr = MallocIntPtr(composerPath + "\\style_makeup\\aidou");
nodes[2] = aidouPathPtr;
ByteEffect.updateComposerNodes(effectHandle, nodes, 3);
#else
ByteEffect.updateComposerNodes(effectHandle, nodes, 2);
#endif

// 设置不同小项的强度
// 使用“道具包路径,道具包tag”来定位具体要设置的美颜小项
// 每一个道具包内部的tag(如果有)都可能不一样,具体可以参考Qt demo内‘BEDataManager’对象内部的代码
// 或者查看通用API文档:https://cv-api.bytedance.com/doc/openapi/2036/200123
ByteEffect.updateComposerNodeIntensity(effectHandle, beautyPathPtr, "smooth", 0.7f);
ByteEffect.updateComposerNodeIntensity(effectHandle, beautyPathPtr, "whiten", 0.35f);
ByteEffect.updateComposerNodeIntensity(effectHandle, beautyPathPtr, "sharp", 0.7f);
ByteEffect.updateComposerNodeIntensity(effectHandle, reshapePathPtr, "Internal_Deform_Overall", 0.8f);
ByteEffect.updateComposerNodeIntensity(effectHandle, reshapePathPtr, "Internal_Deform_Eye", 0.35f);

// if you want remove composer
// reset intensity 
// ByteEffect.updateComposerNodeIntensity(effectHandle, beautyPathPtr, "smooth", 0.0f);
// after reset intensity, if you want remove beauty_IOS_lite, code like this 
// and it also remove (smooth, whiten, sharp)
// {
// 
//    nodes[0] = reshapePathPtr;
//    ByteEffect.updateComposerNodes(effectHandle, nodes, 1);
//  }
FreeIntPtr(beautyPathPtr);
FreeIntPtr(reshapePathPtr);
beautyPathPtr = IntPtr.Zero;
reshapePathPtr = IntPtr.Zero;
#endif

var window = new Window("EffectDemo");
var image = new Mat();

while (true) {
    capture.Read(image);
    if (image.Empty())
        break;

    Cv2.Flip(image, image, FlipMode.Y);

    // Cv2.CvtColor(image, image, ColorConversionCodes.BGR2RGBA);
    Cv2.CvtColor(image, image, ColorConversionCodes.BGR2BGRA);
    
    // 设置要处理的图片buffer
    // 格式必须为:BGR/BGRA/RGB/RGBA/I420,其他格式需要转换成这些格式之后才能使用
    ByteEffect.setInput(effectHandle, image.Width, image.Height, image.Data, (int)ByteEffect.BytePixelFormat.pixel_format_bgra);

    // 获得特效处理之后的buffer,格式和输入格式保持一致
    IntPtr outImage =  ByteEffect.getOutput(effectHandle);

    var frameOut = new Mat(image.Height, image.Width, MatType.CV_8UC4, outImage);
    Cv2.CvtColor(frameOut, frameOut, ColorConversionCodes.BGRA2BGR);
    // show image
    window.ShowImage(frameOut);

    int flag = Cv2.WaitKey(1);
    if (flag >= 0)
    {
        break;
    }
}

FreeIntPtr(licensePathPtr);
FreeIntPtr(modelDirPtr);
licensePathPtr = IntPtr.Zero;
modelDirPtr = IntPtr.Zero;

// 销毁Effect SDK资源
ByteEffect.releaseEffectHandle(effectHandle);
ByteEffect.unloadEGLDLL();

demo的逻辑比较简单易懂,主要是利用C++ EffectManager模块的能力,EffectManager的具体使用可以参考Qt demo中的说明。

MacOS

Mac平台上提供了使用Objective C对摄像头实时采集并进行特效处理的原生App demo,UI界面与特效功能都和Windows平台的Qt demo对齐保持一致。

推荐的开发环境

  • Xcode 12+
  • MacOS 10.11+
  • Cocoapod 1.7.1+

注意⚠️
Mac平台的OpenGL context必须为3.2 Core Profile,在创建NSOpenGLContext的时候请指定NSOpenGLProfileVersion3_2Core,3.2+版本需要集成阶段做好完备的测试确保道具都能正常加载

主要组件及关键API说明

主要组件及关键API说明
EffectHandle封装了底层Effect SDK的C接口,提供特效处理操作
initEffect/releaseEffect管理EffectHandle对象
setOpenGLContext设置NSOpenGLContext,必须为3.2 Core Profile
updateComposerNodes/setFilterPath/setStickerPath设置预期的特效
processWithBuffer
BEImageUtils提供CVPixelBuffer与RGBA/BGRA/I420的转换
transforCVPixelBufferToBufferCVPixelBuffer与RGBA/BGRA/I420的转换
transforBufferToBuffer不同格式的buffer转换
BERequester/BELicenseCheck展示如何进行在线鉴权及license的下载校验,详细的鉴权原理及过程参考在线授权说明
BEEffectDataManager封装了特效道具包资源的使用,包括道具包内tag字段的使用,如果有某一个道具不知道如何使用,请参考BEEffectDataManager内的使用方法
BEAlgorithmTask算法检测小项task的基类
initWithProvider构造检测task,每一个算法所需要的资源文件不尽相同,可以直接用工程中的 BEAlgorithmResourceHelper 实现类,它可以拿到所有算法所需要的资源
initTask初始化算法检测task
process执行算法检测
setConfig设置算法参数,每一个算法都有不同的参数key,定义在各算法的头文件中
destroyTask销毁算法检测task

工程结构

EffectManager # 封装底层SDK的C接口,一般接入直接用这里提供的接口即可
Algorithm     # 提供算法(人脸,手势等)的接入封装代码
ByteEffects   # UI部分,展示如何使用Algorithm和EffectManager封装的接口

如果只想接入特效相关类型的功能(包括美颜,滤镜,美妆,贴纸等等)

那么将EffectManager编译成静态库即可,然后调用里面的EffectHandle对象接口就可以了,大致的流程为:

//创建EffectHandle美颜句柄
handle = [[EffectHandle alloc] initWithPath:licenseDirPath, modelDir:modelDirPath];

// 设置NSOpenGLContext(这里的OpenGL context必须为3.2 Core Profile,即NSOpenGLProfileVersion3_2Core)
handle->setOpenGLContext();

// 初始化Effect SDK
// 需要在GL context环境执行,即设置OpenGL context:调用EffectHandle->lockOpenGLContext/unlockOpenGLContext,详见demo源码
handle->initEffect();

// 传入道具包的路径,设置想要开启的特效
handle->updateComposerNodes("");

// 传入图片buffer进行特效处理, 处理完后会返回处理好的texture给调用方
// demo里面默认输入buffer格式为BGRA,如果有其他格式需要自行修改demo代码或使用BEImageUtils进行格式转换
// 需要在GL context环境执行,即设置OpenGL context:调用EffectHandle->lockOpenGLContext/unlockOpenGLContext,详见demo源码
handle->processWithBuffer();

// 释放美颜句柄
// 需要在GL context环境执行,即设置OpenGL context:调用EffectHandle->lockOpenGLContext/unlockOpenGLContext,详见demo源码
handle->releaseEffect();

如果想要接入算法相关的功能(包括人脸检测,手势检测,人体关键点等等)

那么将EffectManager以及Algorithm编译成静态库,然后使用BEAlgorithmTaskFactory创建想要执行检测的算法task,大致的流程为:

// 和前面的特效处理流程一样
// 需要先创建并成功初始化Effect SDK
// 详见前述特效处理流程

// 创建算法检测小项task
tsk = [BEAlgorithmTaskFactory create:[BEAlgorithmKey create:key isTask:YES] provider:[[BEAlgorithmResourceHelper alloc] initWithLicense:licenseDirPath]];

// 初始化task
tsk.initTask();

// 设置算法检测参数
[tsk setConfig:[BEAlgorithmKey create:MOUTH_MASK_KEY isTask:NO] p:[NSNumber numberWithBool:selected]];

// 执行算法检测并拿到结果
result = [tsk process:buffer width:iWidth height:iHeight stride:iWidth*4 format:BEF_AI_PIX_FMT_BGRA8888 rotation:0];

详细的算法使用可以参考demo内BEAlgorithmVC模块的使用。

如果你想要调试demo源码

那么首先需要配置编译环境

  • 开发环境

目前源码目录中已经包含了effect sdk的开发文件与资源文件:
头文件:include/BytedEffectSDK
库文件:libs
模型文件:ByteEffects/Resource/ModelResource.bundle
美颜素材:ByteEffects/Resource/ComposeMakeup.bundle
滤镜素材:ByteEffects/Resource/FilterResource.bundle
贴纸素材:ByteEffects/Resource/StickerResource.bundle

集成时请使用SDK的编译压缩包,不要直接使用demo目录的effect_sdk库,方便后续的问题跟进。

  • 编译运行

在demo源码目录执行pod init,pod成功后打开ByteEffects.xcworkspace工程,同时配置自己的开发账号的AppID即可进行编译:
Image

注意⚠️
默认是配置的x86_64架构的sdk, 若本地开发环境是m1版本的Mac,需要替换libs目录的库为arm64架构的sdk

Electron

Electron demo内提供了Mac和Windows两个平台预编译好的EffectManager模块,可以直接在代码内使用,EffectManager模块的使用方法和前述demo内一样,这里就不再赘述了。

推荐的开发环境

  • Windows: Visual Studio 2019
  • Electron: v20.0.2
  • Node: v16.16.0

核心逻辑说明

  • 在线授权及校验
const effectNode = require('../plugin/effect_node/effect_node.node');

// 检查授权文件是否有效,success: 0 , 其他错误码需要重新下载授权
var licenseStatus = effectNode.checkLicenseStatus(licenseDir + 'license.bag');

// 根据key和secret生成授权内容
var requestContent = effectNode.getRequestContent(key, secret);

// content 内容为effectNode 接口getRequestContent返回的内容
function licenseRequest(content, secret)
{
  const {net} = require('@electron/remote');
  const request = net.request({
        headers: {
            'Content-Type': 'application/json',
        },
        method: 'POST',
        url: 'https://cv-tob.bytedance.com/v1/api/sdk/tob_license/getlicense'
    });

  request.write(content);
  request.on('response', (response) => {
      console.log(`**statusCode:${response.statusCode}`);
      if (response.statusCode != 200) {
        // error code
      }
      
      response.on("data", (chunk)=>{
          const jsonObj = JSON.parse(chunk);
          console.log("status_code:", jsonObj.status_code);
          if (jsonObj.status_code != 0) {
            // error code
          }
          else {
              // write license to locale file
              var license = effectNode.writeLicenseResult(secret, jsonObj.data, jsonObj.digest, licenseDir + 'license.bag');
          }
      })
  });
  //close request
  request.end();
  return 0;
}
  • 特效处理逻辑
// 初始化egl相关动态库
// 只有Windows需要
effectNode.loadEGLDLL();

// 创建EffectHandle对象
effectHandle = effectNode.createEffectObject();

 // 初始化EffectHandle句柄
 // 这里的license.bag是前面在线鉴权拉取的最新license文件
effectHandle.initResource(licenseDir + 'license.bag', modelDir);

// 获取c++ native层接口,用于传递到c++ camera采集模块处理图像帧
var effectInterface = effectHandle.effectInterface(); 

// 如果有处理视频帧的node模块,可以设置effectInterface内部进行特效处理
// videoCap.setEffectInterface(effectInterface);


// 传入道具包路径,设置预期特效
effectHandle.updateComposerNodes({
        "composerDir":composerNodeDir,
        "tags":tags,
});
    
// 更新素材强度值
effectHandle.updateComposerNodeIntensity(nodePath, key, value);

// 设置滤镜素材
effectHandle.setFilter(filterpath);
// 设置滤镜强度 (value: 0.0 - 1.0)
effectHandle.setFilterIntensity(value);

 // 设置贴纸
 effectHandle.setSticker(stickerPath);
 
 // 设置处理的图像buffer
// @param: buffer 图像buffer (type ArrayBuffer)
// @param: width  图像宽度
// @param: height 图像宽度
// @param: format 图像格式 0: RGBA 1: BGRA
effectHandle.setInput(buffer, with, height, format);

// 获取特效处理后的buffer,图像格式和宽高与输入的格式一致
effectHandle.getOutput();

// 设置 render cache texture,配合素材使用
// 目前用于虚拟背景
//  @param: key 纹理名称
/// @param: path 文件绝对路径
effectHandle.setRenderCacheTexture(key, path);

// 设置是否同步加载素材
//  @param: needLoad (bool 类型) 设置true则会同步加载,默认异步加载素材
effectHandle.setNeedLoadResource(needLoad);

关于EffectHandle的详细用法,请参阅前述demo文档的详细说明。