Android OpenGL 实现“人像背景虚化”效果

字节流动

共 5651字,需浏览 12分钟

 · 2024-04-12


手机上的人像模式,也被人们称作“背景虚化”或 ”双摄虚化“ 模式,也称为 Bokeh 模式,能够在保持画面中指定的人或物体清晰的同时,将其他的背景模糊掉。突出画面的主体部分,主观上美感更强烈。


VIVO 手机人像模式效果


人像模式的一般实现原理是,利用双摄系统获取景深信息并通过深度传感器和图像分割技术准确分离主题与背景,随后应用人像增强处理和背景虚化算法,例如美颜、肤色优化以及基于深度信息的虚化等,最终提供清晰突出的人像照片。


所以,人像模式的实现对于软件层面来说,关键还是有能精确输出带有深度(景深)信息的图像分割算法。


随着深度学习的发展,现在已经可以做到通过单个摄像头,推测场景的深度图,推荐一个 Deep-Image-Matting 开源模型:
https://github.com/foamliu/Deep-Image-Matting


如果只是简单的实现人像 Matting , 可以参考飞鸽传书的开源项目 ncnn_Android_RobustVideoMatting ,它使用的是腾讯的 ncnn 神经网络计算框架,ncnn 是一个为手机端极致优化的高性能神经网络前向计算框架。


VideoMatting Demohttps://github.com/githubhaohao/AndroidVideoMatting


接下来,本文将教您如何利用人像分割和 OpenGL 的滤镜来实现人像背景虚化效果。


照例先上效果图,OpenGL 实现的“人像背景虚化”效果





实现原理


“人像背景虚化”效果实现,首先获取到人像的 mask 图,然后基于这个 mask 图对人像进行保护,对背景做一些模糊(虚化)和一些高光的“光斑”效果,因为真实的光学系统在大光圈成像时,会在某些高光像素处形成特别质感的光斑。


景深信息

如果我们拿到的 mask 图包含了景深信息的话,我们还可以进行更加细致的处理,比如光斑的近大远小,虚化效果的层次感,会更加接近手机人像模式成片的效果。


“光斑”效果的实现参考我之前的文章:抖音、视频号流行的 Bokeh 效果是怎么实现的?https://mp.weixin.qq.com/s/O4DFpruMdQecJBPga6Q5zA ,我们从里面选一个圆形的光斑。


实现圆形光斑的 shader :


#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform sampler2D sTexture;
uniform sampler2D sPortraitTexture;
uniform highp vec2 inputSize;
layout(location = 0) out vec4 outColor;

float sdCircle(in vec2 p, in float r) {
    return length(p)-r;
}

void main() {
    float effectValue = 0.4;
    vec2 uv = v_texCoord;
    vec2 d = 1.0 / inputSize;
    float scale = (inputSize.x + inputSize.y)/1080.0;
    float kernel = floor(20.0 * scale * effectValue);
    vec4 maxCol = vec4(0.0);
    vec4 mask = texture(sPortraitTexture, uv);
    for(float x = -kernel;x<kernel;x++) {
        for(float y = -kernel;y<kernel;y++) {
            vec2 xy = vec2(x,y)/kernel;
            if(sdCircle(xy, 0.5 + effectValue * 0.2) < 0.0) {
                vec4 col = texture(sTexture, uv + d * vec2(x,y));
                maxCol = max(maxCol, col * mask.a);
            }
        }
    }
    outColor = maxCol;
}

有了光斑效果和抠出来的人像,可以使用 Blend 滤镜来验证一波:

//Blend
#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform sampler2D sTexture;//做完光斑效果后的图像
uniform sampler2D sPortraitTexture;//mask 分割出来的人像
layout(location = 0out vec4 outColor;

void main({
    vec4 portraitCol = texture(sPortraitTexture, v_texCoord);
    vec4 srcCol = texture(sTexture, v_texCoord);
    outColor = mix(srcCol, portraitCol, portraitCol.a);
}


效果如下:


“光斑”看起来很不自然,没有虚化的感觉,接下来对图像先做模糊再做光斑的效果,看看融合起来效果如何。


模糊效果实现可以直接使用高斯模糊算法,参考链接:https://mp.weixin.qq.com/s/D53C1KtY2slLBX28Ggmx2g ,这里为了减少计算量选择使用一个轻量的快速高斯模糊滤镜,篇幅有限源码不再贴出来,文末有获取方式。


加了模糊之后的效果,看起来自然了很多:


细心观察,发现了新的问题,人像边缘有一圈黑色的边,看起来很丑。



由2个原因导致的这个问题,一是算法抠图精确度不足,人像没有扣干净;二是融合的时候没有 alpha 预乘导致半透明的边缘发黑。针对这两个原因优化下我们的 blend shader :


#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform sampler2D sTexture;//做完光斑和模糊效果后的图像
uniform sampler2D sPortraitTexture;/mask 分割出来的人像
layout(location = 0) out vec4 outColor;

void main() {
    vec4 portraitCol = texture(sPortraitTexture, v_texCoord);
    vec4 srcCol = texture(sTexture, v_texCoord);
    srcCol *= srcCol.a;// alpha 预乘
    portraitCol *= portraitCol.a;// alpha 预乘
    outColor = mix(srcCol, portraitCol, portraitCol.a);
    outColor.rgb /= max(0.000001, outColor.a);
}


来看最后的效果:

后续优化方向,使用景深(深度)图控制不同区域光斑的大小和虚化的强度。需要完整代码的同学可以在下方扫码添加我的微信:Byte-Flow 获取。


-- END --


进技术交流群,扫码添加我的微信:Byte-Flow



获取相关资料和源码


学习音视频、OpenGL ES、Vulkan 、Metal、图像滤镜、视频特效及相关渲染技术的付费社群,面试指导,1v1 简历服务,职业规划。


我的付费社群


推荐:

Android FFmpeg 实现带滤镜的微信小视频录制功能

全网最全的 Android 音视频和 OpenGL ES 干货,都在这了

一文掌握 YUV 图像的基本处理

抖音传送带特效是怎么实现的?

所有你想要的图片转场效果,都在这了

面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?

我用 OpenGL ES 给小姐姐做了几个抖音滤镜

浏览 1
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报