美文网首页Android 音视频
Android opengl 实现动态贴纸(仿QQ的拍摄)

Android opengl 实现动态贴纸(仿QQ的拍摄)

作者: IT陈 | 来源:发表于2018-03-23 08:45 被阅读323次

    回顾

    在前面视频录制篇尾(https://www.jianshu.com/p/4f63b2436401)中有提到视频录制+人脸识别+贴纸的想法,而上一篇(https://www.jianshu.com/p/6d2d4f00b43c)中也使用opengl实现了多种特效,在这些技术与理论基础上能否实现市面上常见的动态贴纸效果呢?

    效果图

    先来个效果图,这个是抓取QQ的资源包来做的:


    视频动态贴纸特效.gif

    分析

    素材解析

    刚开始看到QQ的这个拍摄特效时,觉得很有意思,又正好之前就想着研究一下动态贴纸的做法,于是在sd卡中找到QQ的文件目录.../tencent/MobileQQ/capture_ptv_template/ptv_template_usable/video_qiuhongbaowu3_iOS/,里面有bgm、hongbao两个目录和params.json文件,bgm中是背景音乐mp3文件,hongbao中是80张素材图片,params.json是规则配置文件。

    至此大体能知道,其实视频动效是由一系列图片连续显示的效果。查看params.json,里面包含类型定义、显示时间间隔、素材列表及显示顺序,同时每个素材信息还包括镂空中心点、镂空区域大小、旋转角度等信息。显然这个镂空区域是用来填充人脸的,这样人脸图像放在底层,贴纸在上层,需要让镂空区域与人脸重合,以便看起来就像是人脸在贴纸上的效果。

    原理与关键点分析

    解析素材后可以看出,根据配置文件,对素材列表根据顺序及时间间隔,不断替换显示出来,其实视频也是这个原理。现在有个问题:怎么让素材图片隔段时间替换一张,不断显示出来呢?

    前面文章中的视频录制,就是需要设置帧率,也就是说每隔一段时间都会刷新画面,如果我们设置filter,那么每个filter的draw就会在刷新时被调用。所以我们写一个filter,解析配置文件得到文件列表及其他信息,这样在draw函数中,每次根据时间来判断当前应该画哪一张图:

    ...
    if (this.loopStartTime == -1L) {
        this.loopStartTime = System.currentTimeMillis();
    }
    int currentIndex = (int) ((System.currentTimeMillis() - this.loopStartTime) / this.mSticker.frameDuration);
    if (currentIndex >= this.mSticker.frameCount) {
        if (!this.mSticker.looping) {
            this.loopStartTime = -1L;
            this.lastIndex = -1;
            return;
        }
        currentIndex = 0;//新一轮中从第一张开始显示,并重新计时
        this.loopStartTime = System.currentTimeMillis();
    }
    if (currentIndex < 0) {
        currentIndex = 0;
    }
    StaticImage image = mSticker.images.get(currentIndex);
    if (this.lastIndex != currentIndex) {
        mTexture.load(image.path);//加载图片
    }
    ...
    

    对于渲染图片,在之前的demo中已经有现成源码可以使用,那如何把头像绘制到贴纸的镂空位置呢?

    头像抠图

    人脸识别可以借助第三方的sdk,face++、商汤等收费的,识别效果比较好,主要是识别到的关键点比较多,或者讯飞的免费,基本能够满足。
    根据识别结果的头像区域,再计算贴纸镂空区域位置与大小,使用glsl做渲染位置计算:

    precision highp float;
    varying highp vec2 vCamTextureCoord;
    uniform sampler2D uCamTexture;
    uniform vec4 srcRect;//头像在源画面(画布)中的位置
    uniform vec4 dstRect;//头像在新画布中的位置
    
    void main(){
        lowp vec4 c1 = texture2D(uCamTexture, vCamTextureCoord);
    
        //注意这个y坐标的计算,因为屏幕坐标第的Y轴方向与纹理的Y轴方向是相反的
        lowp vec2 vCamTextureCoord2 = vec2(vCamTextureCoord.x,1.0 - vCamTextureCoord.y);
        vec2 point = vCamTextureCoord2;
    
        if(point.x>dstRect.r && point.x<dstRect.b && point.y>dstRect.g && point.y<dstRect.a)
        {
            //这个需要根据缩放比例来计算真实位置
            vec2 imagexy = vec2(
                (srcRect.b - srcRect.r) * (point.x - dstRect.r) / (dstRect.b - dstRect.r) + srcRect.r,
                1.0 - ((srcRect.a - srcRect.g) * (point.y - dstRect.g) / (dstRect.a - dstRect.g) + srcRect.g)
            );
            c1 = texture2D(uCamTexture, imagexy);
        }
    
        gl_FragColor = c1;
    }
    

    至此,我们可以这样理解这个过程:
    原始画面 -> 抠头像 -> 画贴纸
    即两次渲染,得到合成的画面,再加上时间间隔及不同贴纸的显示,就得到上术的效果。
    由于我们在抠头像后是绘制到当前贴纸的镂空位置,这样无论头像在原始画面的什么位置,只要能识别出来,都不会影响抠图操作及最终效果。

    总结

    现在来看,动态贴纸从原理上其实也是很简单的,而且组合方式很多,所以才能看到很多APP上有那么多的贴纸模版,当然有些是有加入肢体识别的,比如QQ,这方便鹅厂坐的很好。
    其实也要“感谢”那些第三方SDK的收费,还有XXX的UOK,促使我们自己来研究这些。

    吐槽一下,对于那些本来没有基础、不想花钱,而还想要达到别人的精致效果的,那就只能呵呵了。

    本文相关的代码暂时不会更新到DEMO中,其实思想比较重要。

    相关文章

      网友评论

      • glumes:文章提供的 glsl 就将相机返回的图像中的人脸移动到新的位置,但还是在相机的纹理上做的操作,我这样理解没错吧
        IT陈:你可以理解为,把所有要显示的信息,人脸、贴纸等放在一张图上,排完版后再提供给屏幕和输出用

      本文标题:Android opengl 实现动态贴纸(仿QQ的拍摄)

      本文链接:https://www.haomeiwen.com/subject/rjmzqftx.html