AI视觉实战2:实时头发染色

简介: 在实时视频编辑领域,头发变色、修改发型是很流行和受欢迎的场景。这种功能除了音视频相关的技术,还离不开AI能力的支持。而且这种场景本身对实时性要求高,很适合在端侧应用落地。上一篇文章我们基于谷歌的MediaPipe项目实现了本地实时人脸检测功能,本文我们再来一步一步跑通端侧实时染色功能。

image.png


1. 背景介绍


在实时视频编辑领域,头发变色、修改发型是很流行和受欢迎的场景。这种功能除了音视频相关的技术,还离不开AI能力的支持。而且这种场景本身对实时性要求高,很适合在端侧应用落地。上一篇文章我们基于谷歌的MediaPipe项目实现了本地实时人脸检测功能,本文我们再来一步一步跑通端侧实时染色功能。下面是效果:


image.png


2. 需求分析


上一篇中,人脸检测输入是一帧帧图片,输出是识别到的人脸数量,坐标及对应得分列表,我们可以通过得分与设置的阈值比较判断是否有人脸,还可以根据返回的坐标,给人脸标一个方框。


实时头发染色功能输入的仍然是一帧帧图片,因为头发本身是不规则的,如果输出坐标的话很难再去绘制,所以这次模型为我们返回了一个完整的图片内容,图片上是变色的头发的内容,并且和原图片头发位置坐标保持一直,这样我们可以先绘制原图像,再绘制变色的染色头发图片。


3. 代码实现


和上一篇类似,运行模型一般我们有以下几个步骤:


  1. 加载模型;
  2. 摄像头预览纹理转换为RGBA
  3. 将图像数据feed到模型引擎进行推理
  4. 解析渲染结果


3.1 加载模型


hair_segmentation模型加载时tflite::ops::builtin::BuiltinOpResolver新增了三个自定义operations:


tflite::ops::builtin::BuiltinOpResolver  resolver;
resolver.AddCustom("MaxPoolingWithArgmax2D",
            mediapipe::tflite_operations::RegisterMaxPoolingWithArgmax2D());
resolver.AddCustom("MaxUnpooling2D",
            mediapipe::tflite_operations::RegisterMaxUnpooling2D());
resolver.AddCustom("Convolution2DTransposeBias",
            mediapipe::tflite_operations::RegisterConvolution2DTransposeBias());


对应实现函数:


TfLiteRegistration* RegisterMaxPoolingWithArgmax2D() {
  static TfLiteRegistration reg = {
      [](TfLiteContext*, const char*, size_t) -> void* {
        return new TfLitePaddingValues();
      },
      [](TfLiteContext*, void* buffer) -> void {
        delete reinterpret_cast<TfLitePaddingValues*>(buffer);
      },
      Prepare, Eval};
  return ®
}
TfLiteRegistration* RegisterMaxUnpooling2D() {
  static TfLiteRegistration reg = {
      [](TfLiteContext*, const char*, size_t) -> void* {
        return new TfLitePaddingValues();
      },
      [](TfLiteContext*, void* buffer) -> void {
        delete reinterpret_cast<TfLitePaddingValues*>(buffer);
      },
      Prepare, Eval};
  return ®
}
TfLiteRegistration* RegisterConvolution2DTransposeBias() {
  static TfLiteRegistration reg = {nullptr, nullptr, Prepare, Eval};
  return ®
}


通过InterpreterBuilder创建执行器std::unique_ptr<tflite::Interpreter>后,获取模型输入输出函数:


static tflite_tensor_t      s_tensor_input;
static tflite_tensor_t      s_tensor_segment;
tflite_get_tensor_by_name (&s_interpreter, 0, "input_1",  &s_tensor_input);
tflite_get_tensor_by_name (&s_interpreter, 1, "conv2d_transpose_4",  &s_tensor_segment);


tflite_tensor_t结构有ptr指针成员,输入时存放图像信息,输出时存放被渲染过的头发的图像信息。


3.2 摄像头预览纹理转换为RGBA


纹理转RGBA跟上一篇人脸检测一样,不在赘述。


3.3 将图像数据feed到模型引擎进行推理


feed数据到模型跟上一篇人脸检测一样,不在赘述。feed完后开始执行推理:


typedef struct _segmentation_result_t
{
    float *segmentmap;
    int   segmentmap_dims[3];
} segmentation_result_t;
int invoke_segmentation (segmentation_result_t *segment_result)
{
    if (interpreter->Invoke() != kTfLiteOk)
    {
        DBG_LOGE ("ERR: %s(%d)\n", __FILE__, __LINE__);
        return -1;
    }
    segment_result->segmentmap         = (float *)s_tensor_segment.ptr;
    segment_result->segmentmap_dims[0] = s_tensor_segment.dims[2];
    segment_result->segmentmap_dims[1] = s_tensor_segment.dims[1];
    segment_result->segmentmap_dims[2] = s_tensor_segment.dims[3];
    return 0;
}


结果主要包含被染发的图像数据。


3.4 解析渲染结果


绘制时先绘制原始图像纹理,然后绘制模型返回的修改后的数据:


void render_segment_result (int ofstx, int ofsty, int draw_w, int draw_h, 
                       texture_2d_t *srctex, segmentation_result_t *segment_ret)
{
    float *segmap = segment_ret->segmentmap;
    int segmap_w  = segment_ret->segmentmap_dims[0];
    int segmap_h  = segment_ret->segmentmap_dims[1];
    int segmap_c  = segment_ret->segmentmap_dims[2];
    int x, y, c;
    static unsigned int *imgbuf = NULL;
    float hair_color[4] = {0};
    float back_color[4] = {0};
    static float s_hsv_h = 0.0f;
    if (imgbuf == NULL)
    {
        imgbuf = (unsigned int *)malloc (segmap_w * segmap_h * sizeof(unsigned int));
    }
    s_hsv_h += 5.0f;
    if (s_hsv_h >= 360.0f)
        s_hsv_h = 0.0f;
    colormap_hsv (s_hsv_h / 360.0f, hair_color);
#if defined (RENDER_BY_BLEND)
    float lumi = (hair_color[0] * 0.299f + hair_color[1] * 0.587f + hair_color[2] * 0.114f);
    hair_color[3] = lumi;
#endif
    /* find the most confident class for each pixel. */
    for (y = 0; y < segmap_h; y ++)
    {
        for (x = 0; x < segmap_w; x ++)
        {
            int max_id;
            float conf_max = 0;
            for (c = 0; c < MAX_SEGMENT_CLASS; c ++)
            {
                float confidence = segmap[(y * segmap_w * segmap_c)+ (x * segmap_c) + c];
                if (c == 0 || confidence > conf_max)
                {
                    conf_max = confidence;
                    max_id = c;
                }
            }
            float *col = (max_id > 0) ? hair_color : back_color;
            unsigned char r = ((int)(col[0] * 255)) & 0xff;
            unsigned char g = ((int)(col[1] * 255)) & 0xff;
            unsigned char b = ((int)(col[2] * 255)) & 0xff;
            unsigned char a = ((int)(col[3] * 255)) & 0xff;
            imgbuf[y * segmap_w + x] = (a << 24) | (b << 16) | (g << 8) | (r);
        }
    }
    GLuint texid;
    glGenTextures (1, &texid );
    glBindTexture (GL_TEXTURE_2D, texid);
    glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
        segmap_w, segmap_h, 0, GL_RGBA,
        GL_UNSIGNED_BYTE, imgbuf);
#if !defined (RENDER_BY_BLEND)
    draw_colored_hair (srctex, texid, ofstx, ofsty, draw_w, draw_h, 0, hair_color);
#else
    draw_2d_texture_ex (srctex, ofstx, ofsty, draw_w, draw_h, 0);
    unsigned int blend_add  [] = {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE};
    draw_2d_texture_blendfunc (texid, ofstx, ofsty, draw_w, draw_h, 0, blend_add);
#endif
    glDeleteTextures (1, &texid);
    render_hsv_circle (ofstx + draw_w - 100, ofsty + 100, s_hsv_h);
}


4. 总结


本文介绍了AI技术的实时头发染色模型使用,主要应用于视频特效编辑等场景。该模型用到了BuiltinOpResolver的AddCustom新方法。

目录
相关文章
|
3月前
|
机器学习/深度学习 数据采集 人工智能
PyTorch学习实战:AI从数学基础到模型优化全流程精解
本文系统讲解人工智能、机器学习与深度学习的层级关系,涵盖PyTorch环境配置、张量操作、数据预处理、神经网络基础及模型训练全流程,结合数学原理与代码实践,深入浅出地介绍激活函数、反向传播等核心概念,助力快速入门深度学习。
199 1
|
3月前
|
人工智能 自然语言处理 API
快速集成GPT-4o:下一代多模态AI实战指南
快速集成GPT-4o:下一代多模态AI实战指南
375 101
|
2月前
|
人工智能 缓存 运维
【智造】AI应用实战:6个agent搞定复杂指令和工具膨胀
本文介绍联调造数场景下的AI应用演进:从单Agent模式到多Agent协同的架构升级。针对复杂指令执行不准、响应慢等问题,通过意图识别、工具引擎、推理执行等多Agent分工协作,结合工程化手段提升准确性与效率,并分享了关键设计思路与实践心得。
422 20
【智造】AI应用实战:6个agent搞定复杂指令和工具膨胀
|
2月前
|
存储 人工智能 搜索推荐
LangGraph 记忆系统实战:反馈循环 + 动态 Prompt 让 AI 持续学习
本文介绍基于LangGraph构建的双层记忆系统,通过短期与长期记忆协同,实现AI代理的持续学习。短期记忆管理会话内上下文,长期记忆跨会话存储用户偏好与决策,结合人机协作反馈循环,动态更新提示词,使代理具备个性化响应与行为进化能力。
358 10
LangGraph 记忆系统实战:反馈循环 + 动态 Prompt 让 AI 持续学习
|
2月前
|
人工智能 IDE 开发工具
从6人日到1人日:一次AI驱动的客户端需求开发实战
从6人日到1人日:一次AI驱动的客户端需求开发实战
从6人日到1人日:一次AI驱动的客户端需求开发实战
|
2月前
|
数据采集 人工智能 JSON
Prompt 工程实战:如何让 AI 生成高质量的 aiohttp 异步爬虫代码
Prompt 工程实战:如何让 AI 生成高质量的 aiohttp 异步爬虫代码
|
3月前
|
人工智能 JSON 测试技术
AI智能体开发实战:从提示工程转向上下文工程的完整指南
曾被热捧的提示工程正逐渐退潮,本文揭示其局限性,并提出“上下文工程”新范式:通过结构化提示、精准上下文管理、工具调用与统一状态,构建可扩展、可恢复、生产级的智能体工作流,推动AI系统迈向工程化与可控化。
383 9
AI智能体开发实战:从提示工程转向上下文工程的完整指南
|
3月前
|
存储 消息中间件 人工智能
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
239 10
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
|
3月前
|
存储 消息中间件 人工智能
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
113 11
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡

热门文章

最新文章