回顾
在前面视频录制篇尾(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中,其实思想比较重要。
网友评论