目录
HeadPose Estimation头部姿态估计头部朝向(Android)
0.前言
1.HeadPose
2.pitch、yaw、roll三个角的区别
3.头部姿态估计评价指标
4.头部姿态估计数据
5.FSA-Net介绍
6. 头部姿态估计效果展示
7. 头部姿态估计Android源码下载
本篇,将介绍一种基于深度学习的头部姿态估计模型FSA-Net。鄙人已经复现论文的结果,并对FSA-Net进行了轻量化,以便在移动端可以跑起来;目前Android Demo已经集成人脸检测和头部朝向模型,在普通手机可实时检测(30ms左右),CPU支持多线程处理,GPU支持OpenCL加速处理,先看一下效果哈:
CPU-4线程 | GPU |
Android Demo支持的特性主要如下:
头部姿态估计(Head Pose Estimation ),也称头部朝向估计,主要是获得脸部朝向的角度信息,即欧拉角(pitch,yaw,roll)表示。
头部姿态估计方法很多,主要可以分为两大类
(1)基于PNP的头部姿态估计方法
使用透视变换可以完成2D到3D的转换,可以简单的想象为将照片上的人脸图像按照一定的角度进行多点拉扯形成3D图像,然后根据角度来判断姿态。使用的方法原理为使用2D平面上人脸的特征点和3D空间内对应的坐标点,按照求解pnp问题的思路。找到一个映射关系,从而估计头部的姿态。
经典的 Head Pose Estimation 算法的步骤一般为:
- 2D人脸关键点检测;
- 3D人脸模型匹配;
- 求解3D点和对应2D点的转换关系;
- 根据旋转矩阵求解欧拉角。
基于PNP的头部姿态估计是比较传统的算法,其效果比较依赖人脸关键点检测,实际测试误差还是比较大。
可参考资料:基于3D通用模型的头部姿态估计_一半糊涂、的博客-CSDN博客_头部姿态估计
(2)基于深度学习的方法
基于深度学习的方法,把脸部朝向的角度信息,即欧拉角(pitch,yaw,roll)当作一个多任务的回归模型(也可以转为分类)。其模型输入一张RGB人脸图像,输出三个值,代表头部朝向的欧拉角,(pitch,yaw,roll)。相比传统的头部姿态估计算法,该方法不依赖于人脸关键点,精度更高效果更好。
比如论文《Fine-Grained Head Pose Estimation Without Keypoints》就是这么简单粗暴:
论文地址:https://arxiv.org/abs/1710.00925v2
代码链接:https://github.com/natanielruiz/deep-head-pose
当然,还有FSA-Net,本博客就是在FSA-Net的基础上进行优化
参考资料:FSA-Net学习笔记_南风不竞:的博客-CSDN博客
欧拉角(pitch,yaw,roll)遵循三维空间右手笛卡尔坐标原则:
蓝色的代表偏航角,绿色的代表俯仰角,红色的代表滚转角
欧拉角 | 说明 | 图示 |
pitch | 俯仰,将物体绕X轴旋转(localRotationX),即点头 上负下正 | |
yaw | 航向,将物体绕Y轴旋转(localRotationY),即摇头 左正右负 | |
roll | 横滚,将物体绕Z轴旋转(localRotationZ), 即转头(歪头)左负右正 |
头部姿态估计主要有两种评价准则
(1)平均绝对误差(MAE)
(2)平均精度
数据集 | 说明 |
AFLW2000 |
|
BIWI |
|
FSA-Net 是2019年CVPR中的一篇文章,下面是FSA-NET模型架构图:
首先,输入的图片经过两条流(two Stream)。两条流在3个stage各自提取一个特征图。相同stage提取出的特征图经过fusion module(图中的绿色框)。
原文:fusion module 首先将每个stage的两个feature map,通过element-wise multiplication得到combined feature map。然后通过c 1x1 的卷积将特征图变成c个channel。最后,用平均池化将特征图变成 w*h,最终,我们得到k个stage的特征图Uk .如上图的 U1-Uk.
得到了K个大小为 特征图后,聚合模块的任务就是将其聚合为一个更小的更representative的特征图,具体来说就是将特征图精简为 。已有的一些方法如capsule 和NetVLAD没有关注空间之间的相对信息。所以在进入特征聚合模块前,先进行空间聚合(spatial grouping)。
GitHub - shamangary/FSA-Net: [CVPR19] FSA-Net: Learning Fine-Grained Structure Aggregation for Head Pose Estimation from a Single Image
目前Android Demo已经集成人脸检测和头部朝向模型,支持以下功能:
- 支持人脸检测:已经集成了轻量化的人脸检测,在普通手机只需要15ms左右,持CPU多线程处理,GPU支持OpenCL加速处理
- 支持头部姿态估计:已经集成了轻量化的头部姿态估计,在普通手机只需要7ms左右,持CPU多线程处理,GPU支持OpenCL加速处理
- 支持多人头部姿态估计
- Demo支持图片,视频,摄像头等多种方式输入数据
- 整个过程在普通手机可实时检测,30ms左右
算法核心代码,都采用C++实现,这是JNI部分,也是接口的核心代码:
package com.cv.tnn.model;
import android.graphics.Bitmap;
public class Detector {
static {
System.loadLibrary("tnn_wrapper");
}
/***
* 初始化关键点检测模型
* @param face_model: 人脸检测模型(不含后缀名)
* @param head_model: 头部朝向模型(不含后缀名)
* @param root:模型文件的根目录,放在assets文件夹下
* @param model_type:模型类型
* @param num_thread:开启线程数
* @param useGPU:关键点的置信度,小于值的坐标会置-1
*/
public static native void init(String face_model,String head_model, String root, int model_type, int num_thread, boolean useGPU);
/***
* 检测关键点
* @param bitmap 图像(bitmap),ARGB_8888格式
* @param score_thresh:置信度阈值
* @param iou_thresh: IOU阈值
* @param dst_bitmap图像(bitmap),头部姿态估计可视化效果图
* @return
*/
public static native FrameInfo[] detect(Bitmap bitmap, float score_thresh, float iou_thresh,Bitmap dst_bitmap);
}
Android源码的头部朝向坐标绘制,我是使用的OpenCV绘制实现的,然后把绘制好Bitmap图像通过JNI映射到上层,并进行显示,核心显示代码如下:
/***
* 绘制yaw,pitch,roll坐标轴(左手坐标系)
* @param imgBRG 输入必须是BGR格式的图像
* @param pitch红色X
* @param yaw 绿色Y
* @param roll 蓝色Z
* @param center 坐标原始点
* @param vis
* @param size
*/
void draw_yaw_pitch_roll_in_left_axis(cv::Mat &imgBRG, float pitch, float yaw, float roll,
cv::Point center, int size, int thickness, bool vis) {
float cx = center.x;
float cy = center.y;
char text[200];
sprintf(text, "(pitch,yaw,roll)=(%3.1f,%3.1f,%3.1f)", pitch, yaw, roll);
pitch = pitch * PI / 180;
yaw = -yaw * PI / 180;
roll = roll * PI / 180;
// X-Axis pointing to right. drawn in red
float x1 = size * (cos(yaw) * cos(roll)) + cx;
float y1 = size * (cos(pitch) * sin(roll) + cos(roll) * sin(pitch) * sin(yaw)) + cy;
cv::Scalar color_yaw_x(0, 0, 255); //BGR;
// Y-Axis | drawn in green
float x2 = size * (-cos(yaw) * sin(roll)) + cx;
float y2 = size * (cos(pitch) * cos(roll) - sin(pitch) * sin(yaw) * sin(roll)) + cy;
cv::Scalar color_pitch_y(0, 255, 0);
// Z-Axis (out of the screen) drawn in blue
float x3 = size * (sin(yaw)) + cx;
float y3 = size * (-cos(yaw) * sin(pitch)) + cy;
cv::Scalar color_roll_z(255, 0, 0);
float tipLength = 0.2;
cv::arrowedLine(imgBRG, cv::Point(int(cx), int(cy)), cv::Point(int(x1), int(y1)), color_yaw_x,
thickness,
tipLength);
cv::arrowedLine(imgBRG, cv::Point(int(cx), int(cy)), cv::Point(int(x2), int(y2)), color_pitch_y,
thickness,
tipLength);
cv::arrowedLine(imgBRG, cv::Point(int(cx), int(cy)), cv::Point(int(x3), int(y3)), color_roll_z,
thickness,
tipLength);
if (vis) {
cv::putText(imgBRG,
text,
cv::Point(cx, cy),
cv::FONT_HERSHEY_COMPLEX,
0.5,
(0, 0, 255));
}
}
一些Android测试测试效果:https://panjinquan.blog.csdn.net/article/details/124943419
Android效果图 | CPU-4线程 | GPU |
一些图片测试效果: