人像抠图 + OpenGL ES 还能这样玩?没想到吧

2021-07-16 10:45:37 浏览数 (1)

OpenGL ES 利用抠图算法实现人像留色

人像留色人像留色
人像留色人像留色

人像留色的原理

现在人像分割技术就像当初的人脸检测算法一样,称为广泛使用的基础算法。今天本文介绍的人像留色其实就是三年前某 AI 巨头利用 video 分割技术展示的应用场景:人体区域保留彩色,人体区域之外灰度化。所以人像留色的关键技术在于高精度高性能的分割算法。

OpenGL ES 利用抠图算法实现人像留色OpenGL ES 利用抠图算法实现人像留色

首先利用分割算法获取到人像的 mask 图(灰度图),其中人像区域的灰度值大于 0 ,非人像区域的灰度值等于 0 。在 shader 中,首先对 mask 图采样判断采样点是否位于人像区域,然后分别进行不同的处理。

获取人像 mask 图

那么如何获取人像 mask 图?Github 上已经有很多大神开源了相关的分割或者抠图算法。

这里推荐 3 个比较受欢迎的开源项目

Multi-Human-Parsing

Multi-Human-ParsingMulti-Human-Parsing

Multi-Human-Parsing 的优势在于支持多人不同部位的分割,同时支持目标检测和人体部位分割。

Multi-Human-Parsing 将人群场景图像划分为语义一致的属于身体部位或衣服物品的区域,从而为图像中的每个像素分配一个语义部位标签,以及它所属的身份. 有很多应用场景,如虚拟现实、视频监控、群体行为分析等。 Multi-Human-Parsing 的分割精度对于人体关键点检测其实够用了。

项目地址:

https://github.com/ZhaoJ9014/Multi-Human-Parsing

BackgroundMattingV2

Real-Time High-Resolution Background MattingReal-Time High-Resolution Background Matting

大名鼎鼎的 BackgroundMattingV2 算法,这也是本文所使用的抠图算法,主要特点就是实时、高分辨率、高精度的分割(Background Matting),项目免费可商用。

但是要在移动端落地的话,性能将会是很大的瓶颈,需要进行大量的算法优化,这也是目前大部分 AI 算法面临的问题:如何将 AI 算法落地到低算力平台。

项目地址:

https://github.com/PeterL1n/BackgroundMattingV2

TensorFlow Lite

TensorFlow LiteTensorFlow Lite

TensorFlow Lite 是针对移动端的开源机器学习框架,支持 Android 和 iOS,提供了丰富的算法模型,包括图像分割、目标检测、图像分类、超分等模型。

其中分割模型支持的场景比较丰富,包括人体、宠物和物体等。

代码语言:txt复制
val labelsArrays = arrayOf(
  "background", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus",
  "car", "cat", "chair", "cow", "dining table", "dog", "horse", "motorbike",
  "person", "potted plant", "sheep", "sofa", "train", "tv"
)

TensorFlow Lite 所提供的分割模型,使用 GPU 的话,单张 2K 的图处理时间在几百毫秒,基本上做不了 video 处理,另外分割精度也是 demo 级别的。

项目地址:

https://github.com/tensorflow/examples/tree/master/lite/examples/image_segmentation

人像留色的实现

人像留色 2人像留色 2

用于实现人像留色的简单 shader :

代码语言:txt复制
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D u_texture0;//rgba
uniform sampler2D u_texture1;//人像灰度图
uniform int u_renderType;
uniform float u_offset;
void main()
{
    float gray = texture(u_texture1, v_texCoord).r;//对 mask 进行采样
    vec4 rgba  = texture(u_texture0, v_texCoord);
    if(gray > 0.01) {
        outColor = rgba;
    }
    else
    {
        float Y = 0.299 * rgba.r   0.587 * rgba.g   0.114 * rgba.b;//RGB 灰度化
        vec4 grayColor = vec4(vec3(Y), 1.0);
        outColor = mix(grayColor, rgba, u_offset);//混合渐变
    }
}

shader 中首先对 mask 采样,然后判断采样点是否位于人像区域(灰度值是否大于 0 ),若采样点位于人像区域外,对颜色进行灰度化。

另外需要注意 OpenGL 访问的图像内存默认是 4 字节对齐,这样灰度 Mask 图的宽度不是 4 的整数倍的话,会有花屏现象,这里需要取消对齐设置:

代码语言:txt复制
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_GrayTexId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_GrayImage.width, m_GrayImage.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_GrayImage.ppPlane[0]);
glBindTexture(GL_TEXTURE_2D, GL_NONE);

完整实现代码见项目:

https://github.com/githubhaohao/NDK_OpenGLES_3_0

0 人点赞