faceCapture

Project Url: Li-Shang/faceCapture
Introduction: an Android app kind like Facerig(but simpler)
More: Author   ReportBugs   
Tags:

[TOC]

人脸的姿势捕捉和模拟。实现了人脸识别以及人脸特征点的检测。根据人脸特征点的二维信息,还原出人脸姿势的旋转角。控制模型,让模型做出相同的姿势。

调用摄像头实时预览

在 Android 5.0 之前,可以直接在 Manifest 文件中获取到摄像头权限。但是在 5.0 之后,必须在运行时动态获取权限。同样在 Android 之后新增了一套 API camera2,但是由于我们要兼容 5.0 以下的机器,所以我使用了 API camera。

CameraPreview 类继承 SurfaceView,用来预览相机数据。首先获取设备支持的预览图片格式,大小等信息来初始化前置摄像头。利用 setPreviewCallbackWithBuffer 函数来来获取到预览的数据,在 Callback 函数中将 YUVImage 转换为 Bitmap。使用带有 Buffer 的回调函数可以加快程序的运行速度,确保实时的预览。

由于 Android camera API 的设计,预览数据必须设置一个 preview 或 texture。但是我希望将预览数据处理(人脸检测)后再显示,所有必须设置预览的 texture 然后将数据丢弃,我们自己渲染处理过后的预览数据。

更好的解决方案是从 texture 中获取数据,转换格式后进行处理,然后利用 OpenGL ES 渲染,这样比 CPU 操作速度快更多。(我并没有这样做。)

人脸识别及特征点检测

人脸识别及特征点检测采用 dlib。Dlib 是一个机器学习的 C++库,包含了许多机器学习常用的算法,而且文档和例子都非常详细。我们这里使用它的人脸检测功能。dlib 检测人脸并得到 68 个特征点(landmark)。

我们需要将 dlib(C++库)移植到 Android 上,我使用了 tzutalin 编译好的dlib-android。作者将 C++接口用 Java 包装好了,然后我们只需要调用 Java 即可。

人脸姿势估计

估计人脸法线方向

这一部分主要参考了论文 Estimating Gaze from a Single View of a Face (Andrew Gee and Roberto Cipolla University of Cambridge)。

如上图所示,我们定义$R_m \equiv L_m / L_f$, $R_n \equiv L_n / L_f$。我们建立一个坐标系,如下图(见图片的右下角)。

我们利用固定的比例$R_m$定位 nose base 点, nose base 到 nose tip 的连线即为人脸法向。人脸法向的两个偏移角$\theta, \tau$如图所示。另外记$\sigma$为三维空间中 optical axis 和 face normal 之间的夹角。那么人脸的法线为:$$\hat{n} = [\sin \sigma \cos \tau, \sin \sigma \sin \tau, -\cos \sigma]$$ $\sigma$可以用以下方法估计: $$R_n^2(1 - m_2) \cos^4 \sigma+ (m_1 - R_n^2 + 2m_2R_n^2) \cos^2 \sigma -- m_2R_n^2 = 0$$ 其中,$m_1 \equiv {l_n / l_f}^2$, $m_2 \equiv \cos^2 \theta$。解上面方程即可得到$\sigma$, 再代入即可求解出$\hat{n}$。 整个过程中用到的变量大致都在下图中。

计算欧拉角

人脸凝视方向与人脸法相方向相差近似固定的夹角,约为 15 度。那么就可以计算出人脸凝视方向。正常情况下,人脸面朝前方,法线沿着 z 轴方向。通过两个法线可以计算出四元数。

四元数是简单的超复数。 复数是由实数加上虚数单位 i 组成,其中$i^2 = -1$。 相似地,四元数都是由实数加上三个虚数单位 $i,j,k$ 组成,而且它们有如下的关系:$ i^2 = j^2 = k^2 = -1$,$ i^0 = j^0 = k^0 = 1 $, 每个四元数都是 1、i、j 和 k 的线性组合,即是四元数一般可表示为$a + bk+ cj + di$,其中 a、b、c 、d 是实数。 对于 i、j、k 本身的几何意义可以理解为一种旋转,其中 i 旋转代表 X 轴与 Y 轴相交平面中 X 轴正向向 Y 轴正向的旋转,j 旋转代表 Z 轴与 X 轴相交平面中 Z 轴正向向 X 轴正向的旋转,k 旋转代表 Y 轴与 Z 轴相交平面中 Y 轴正向向 Z 轴正向的旋转。我们可以得到两个向量旋转所产生的四元数,再将其转换成欧拉角。

模型操作

我用了 live2d 模型来完成实验。Live2D 是一种应用于电子游戏的绘图渲染技术,技术由日本 Cybernoids 公司开发。通过一系列的连续图像和人物建模来生成一种类似三维模型的二维图像,对于以动画风格为主的冒险游戏来说非常有用,缺点是 Live 2D 人物无法大幅度转身,开发商正设法让该技术可显示 360 度图像。Live2D Cubism 确实是名副其实的 2D,没有用到任何 3D 技术。其本质上是二维图片,所以对性能要求较低,非常适合 Android 设备使用。

我使用了 Live2d 2.0 SDK for android.

live2DModel.setParamFloat(L2DStandardID.PARAM_ANGLE_Z, (float) mActivity.emotion[0], 0.75f);
live2DModel.setParamFloat(L2DStandardID.PARAM_ANGLE_X, (float) mActivity.emotion[1], 0.75f);
live2DModel.setParamFloat(L2DStandardID.PARAM_ANGLE_Y, (float) mActivity.emotion[2], 0.75f);

同时增加了眨眼动作,使人物看起来更生动。同时做了帧与帧之间的平滑,进行了一定程度的防抖。

实验结果展示

我在小米 5 Android 7.01 和 Nexus 10 Android 4.2 上做的实验,均可以良好运行。

Apps
About Me
Google+: Trinea trinea
GitHub: Trinea