人脸分析模块
简介
人脸分析模块提供人脸关键点检测以及人脸属性分析能力。
- 依赖资源文件
face_info_pack.bin
- 人脸106关键点索引图
-
人脸198点关键点索引图 V1
V1版本198点关键点包含20点眉毛关键点(左右各10),32点眼睛关键点(左右各16)以及40点嘴唇关键点
-
人脸256关键点索引图 V2
在106点关键点的基础上,拓展增加了嘴唇、眼睛、眉毛精细关键点,以及新增虹膜关键点。
其中左右眉毛各新增13点,左右眼睛各22点,嘴唇40点,瞳孔各20点,共新增150点,完整提供256点关键点。
- 瞳孔关键点索引图
技术规格
支持平台 | Android、iOS、Windows、Mac、Linux |
---|---|
支持角度 | yaw ≤ ±90° pitch ≤ ±90° |
支持输入格式 | RGB888 |
支持最大人脸数 | 10 |
支持距离 | 人脸占图片短边1/10以上 |
支持最小输入尺寸 | 短边20px |
接口说明
- 初始化
注意初始化前需要获取联网权限以完成授权
mlsdk::MLSDKEngineBase* engine = mlsdk::GetMLSDKEngineInstance("/path/to/license", ${machineid});
mlsdk::InitModel(engine, MLMODEL_FACE, face_model_path);
- 人脸检测
cv::Mat original = cv::imread("/path/to/image");
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
std::vector<mlsdk::FaceInfoOutput> faceInfoList;
std::vector<unsigned char> image_data(image.data, image.data + image.total() * image.channels());
mlsdk::HumanFaceDetection(engine, image_data, image.cols, image.rows, mt, LANDMARKTYPE_EXTRA_LANDMARKS_V2, &faceInfoList);
- 销毁引擎
mlsdk::DestroyMLSDKEngineInstance(engine);
API参数
int HumanFaceDetection(MLSDKEngineBase *engine, const std::vector<unsigned char> &input, int width, int height,
MLMediaType mediaType, uint64_t config,
std::vector<FaceInfoOutput> *output)
-
参数说明
参数名 参数说明 engine 初始化引擎时获得的句柄 input 连续紧致的RGB缓冲区,其大小为输入原图的width * height * 3, 内容为输入原图的RGBRGB排列的,无须行对齐的、行优先存储的像素 width 原图宽 height 原图高 mediaType 可选: MLMediaType_Image = 1,MLMediaType_Video
注意不要混用图像模式与视频模式,如需同时使用多个模式,请初始化多个engine实例。config 选择精细关键点类型、是否打开性别年龄颜值检测、是否打开人脸分割检测等 -
config配置
config目前可以由以下部分取或组成:
const uint64_t CONFIG_LMT_NO_EXTRA_LANDMARKS = 0b01; // 无额外关键点
const uint64_t CONFIG_LMT_EXTRA_LANDMARKS_V1 = 0b10; // 198点
const uint64_t CONFIG_LMT_EXTRA_LANDMARKS_V2 = 0b100; // 256点修改版
// 默认完全不检测性别年龄颜值【开启需要有对应的授权】
const uint64_t CONFIG_HUMANFACE_EXTRACT_ATTR = 0x10; // 打开人脸属性识别: 性别年龄人种
const uint64_t CONFIG_HUMANFACE_NO_ATTR = 0x30; // 默认:关闭人脸属性识别: 性别年龄人种
// 默认完全不分割,通过下面分别打开【开启需要有对应的授权】
const uint64_t CONFIG_HUMANFACE_PARSING = 0x200; // 嘴唇/牙齿/人头分割,注意需要对应的权限
const uint64_t CONFIG_HUMANFACE_NO_PARSING = 0x100; // 默认,不执行
cpp
举例:打开256点,检测性别年龄颜值,但不需要分割,则调用
CONFIG_LMT_EXTRA_LANDMARKS_V2 | CONFIG_HUMANFACE_NO_PARSING | CONFIG_HUMANFACE_EXTRACT_ATTR
- 结果类型 输出结果是一个数组vector,其中每一个FaceInfoOutput代表其中检测到的一个人脸信息,详细结构参考头文件。使用人脸额外关键点、人脸属性、人脸动作识别前,应主动检查返回结果大小,未授权部分结果将保持为空。
struct SegmentationOutput {
std::vector<float> probmap;
bool probmap_valid; // 只有probmap_valid=true, 整个结构才有意义
int width;
int height;
int raw_x; // 对应区域在原图左上角x坐标
int raw_y; // 对应区域在原图左上角y坐标
int raw_width; // 对应区域在原图的宽
int raw_height; // 对应区域在原图的高
};
struct FaceInfoOutput {
DetectionOutput det;
LandmarkOutput lm; // 基础关键点
LandmarkOutput extra_mouth; // 精细嘴部关键点
LandmarkOutput extra_left_eye; // 精细左眼关键点
LandmarkOutput extra_right_eye; // 精细右眼关键点
LandmarkOutput extra_left_eyebrow; // 精细左眉毛关键点
LandmarkOutput extra_right_eyebrow; // 精细右眉毛关键点
LandmarkOutput extra_left_iris; // 精细左瞳孔关键点
LandmarkOutput extra_right_iris; // 精细右瞳孔关键点
std::vector<float> attributes; // 人脸属性:年龄、性别、颜值
std::vector<std::string> attributes_str;// 人脸属性对应的中文说明
std::vector<bool> static_expression; // 人脸动作识别:静态动作,如嘴巴张开的状态
std::vector<bool> dynamic_expression; // 人脸动作识别:动态动作,如张嘴的动作
SegmentationOutput faceparsing; // 人脸分割结果,包含嘴唇、口腔、脸部、头发等
};
结果解析
人脸关键点
人脸关键点位于对应的类型为LandmarkOutput的结构体中,对于2D关键点,一般是xyxyxy...的排列,以左上角为(0, 0), 右下角为(width-1, height-1)
人脸姿态
人脸姿态位于 det中,分别是pitch, yaw, roll, 以角度为单位,正对屏幕的人脸为(0, 0, 0), 逆时针为负,顺时针为正
人脸分割
人脸分割包括对以下几个部分的分割结果
- 背景[0, 1]
- 脸部区域[2, 3]
- 头发(含帽子)[4,5]
- 口腔(含牙齿)[6, 7]
- 嘴唇[8, 9]
若需要获取人脸分割结果,需要获取对应的授权,且config带上CONFIG_HUMANFACE_PARSING, 分割结果位于faceparsing,注意判断faceparsing.probmap_valid=true,否则分割无效
人脸属性识别
人脸属性需要对应授权,并且config需要带上CONFIG_HUMANFACE_EXTRACT_ATTR, 默认是关闭的。人脸属性位于attributes,下标含义为:
enum HumanFaceAttrType{
HUMAN_ATTR_GENDER = 0,
HUMAN_ATTR_RACE_0 = 1,
HUMAN_ATTR_RACE_1 = 2,
HUMAN_ATTR_RACE_2 = 3,
HUMAN_ATTR_RACE_3 = 4,
HUMAN_ATTR_AGE = 5,
HUMAN_ATTR_BEAUTY = 6,
};
其中:
-
性别[0-0.5]男性,(0.5, 1]女性
-
年龄为真实年龄,一般为[0, 100]
-
颜值范围是[0, 100], 该效果受数据集影响较大,具体业务方可定制标准
人脸动作识别
人脸动作识别目前提供共17类静态动作以及5类动态动作,静态动作可由单张图判断,动态动作是指需要多帧图片才可以获得的,对于MLMediaType_Image模式,结果无效。 人脸返回结果中,以下两个数组内,true部分表示该位置对应的人脸动作被触发。
std::vector<bool> static_expression; // 人脸动作识别:静态动作,如嘴巴张开的状态
std::vector<bool> dynamic_expression; // 人脸动作识别:动态动作,如张嘴的动作
具体的位置下标为:
enum DynamicExpression
{
EYE_BLINK = 0, // 眨眼
MOUTH_AH = 1, // 张嘴
HEAD_YAW = 2, // 摇头
HEAD_PITCH = 3, // 点头
BROW_JUMP = 4 // 挑眉
};
enum StaticExpression
{
HEAD_NORMAL = 0, // 正脸
FACE_YAW_LEFT = 1, // 正左脸
FACE_YAW_RIGHT = 2, // 正右脸
FACE_PITCH_YAW_LEFT = 3, // 倾斜左脸
FACE_PITCH_YAW_RIGHT = 4, // 倾斜右脸
RISED_HEAD = 5, // 抬头
BOWED_HEAD = 6, // 低头
TWO_EYE_CLOSE = 7, // 双眼闭
TWO_EYE_OPEN = 8, // 双眼睁
LEFTEYE_OPEN_RIGHTEYE_CLOSE = 9, // 左眼睁右眼闭
LEFTEYE_CLOSE_RIGHTEYE_OPEN = 10, // 左眼闭右眼睁
CLOSED_MOUTH = 11, // 闭嘴
OPENED_MOUTH = 12, // 张嘴
LIPS_SMILE = 13, // 嘴角上扬,类似微笑
LIPS_POUTED = 14, // 嘟嘴
LIPS_SMIRK_LEFT = 15, // 左上歪嘴龙王笑
LIPS_SMIRK_RIGHT = 16, // 右上歪嘴龙王笑
};
解析示例
static std::vector<std::string> static_expression_names = {
"HEAD_NORMAL",
"FACE_YAW_LEFT",
"FACE_YAW_RIGHT",
"FACE_PITCH_YAW_LEFT",
"FACE_PITCH_YAW_RIGHT",
"RISED_HEAD",
"BOWED_HEAD",
"TWO_EYE_CLOSE",
"TWO_EYE_OPEN",
"LEFTEYE_OPEN_RIGHTEYE_CLOSE",
"LEFTEYE_CLOSE_RIGHTEYE_OPEN",
"CLOSED_MOUTH",
"OPENED_MOUTH",
"LIPS_SMILE",
"LIPS_POUTED",
"LIPS_SMIRK_LEFT",
"LIPS_SMIRK_RIGHT",
};
static std::vector<std::string> dynamic_expression_names = {
"EYE_BLINK",
"MOUTH_AH",
"HEAD_YAW",
"HEAD_PITCH",
"BROW_JUMP",
};
...
...
if(!faceInfoList.empty()){
// pitch yaw roll
cv::putText(image, "Pit: " + std::to_string(faceInfoList[0].det.pitch), {0, 10}, cv::FONT_HERSHEY_PLAIN, 0.5, {0, 255, 0}, 1);
cv::putText(image, "Yaw: " + std::to_string(faceInfoList[0].det.yaw), {0, 35}, cv::FONT_HERSHEY_PLAIN, 0.5, {0, 255, 0}, 1);
cv::putText(image, "Rol:" + std::to_string(faceInfoList[0].det.roll), {0, 60}, cv::FONT_HERSHEY_PLAIN, 0.5, {0, 255, 0}, 1);
// static expression
for(int i=0; i < faceInfoList[0].static_expression.size(); i++){
bool exp = faceInfoList[0].static_expression[i];
if(exp){
cv::putText(image, static_expression_names[i], {0, 60 + 15 * (1 + i)}, cv::FONT_HERSHEY_PLAIN, 0.5, {255, 255, 255}, 1);
}else{
cv::putText(image, static_expression_names[i], {0, 60 + 15 * (1+ i)}, cv::FONT_HERSHEY_PLAIN, 0.5, {128, 128, 128}, 1);
}
}
for(int i=0; i < faceInfoList[0].dynamic_expression.size(); i++){
bool exp = faceInfoList[0].dynamic_expression[i];
if(exp){
cv::putText(image, dynamic_expression_names[i], {200, 60 + 15 * (1 + i)}, cv::FONT_HERSHEY_PLAIN, 0.5, {255, 255, 255}, 1);
}else{
cv::putText(image, dynamic_expression_names[i], {200, 60 + 15 * (1+ i)}, cv::FONT_HERSHEY_PLAIN, 0.5, {128, 128, 128}, 1);
}
}
完整示例
cv::Mat image = cv::imread("/path/to/image");
cv::Mat original = image.clone();
cv::cvtColor(image, image, cv::COLOR_BGR2RGB); // 网络输入使用RGB buffer
std::vector<mlsdk::FaceInfoOutput> faceInfoList;
std::vector<unsigned char> image_data(image.data, image.data + image.total() * image.channels());
mlsdk::HumanFaceDetection(engine, image_data, image.cols, image.rows, mt, CONFIG_LMT_EXTRA_LANDMARKS_V2, &faceInfoList);
printf("%lu faces detected\n", faceInfoList.size());
for(auto face: faceInfoList){
auto& box = face.det.box;
for(int _i=0; _i < face.lm.landmarks.size()/2 && _i < 106; _i++){
cv::circle(image, {(int)face.lm.landmarks[_i*2], (int)face.lm.landmarks[_i*2+1]}, 2, {255, 0, 0}, -1);
}
for(auto& lm: {face.extra_right_eye, face.extra_left_eye, face.extra_mouth, face.extra_left_iris, face.extra_left_eyebrow, face.extra_right_iris, face.extra_right_eyebrow}){
for(int _i=0; _i < lm.landmarks.size()/2; _i++){
cv::circle(image, {(int)lm.landmarks[_i*2], (int)lm.landmarks[_i*2+1]}, 2, {0, 255, 0}, -1);
}
}
}
if(!faceInfoList.empty()){
// pitch yaw roll
cv::putText(image, "Pit: " + std::to_string(faceInfoList[0].det.pitch), {0, 10}, cv::FONT_HERSHEY_PLAIN, 0.5, {0, 255, 0}, 1);
cv::putText(image, "Yaw: " + std::to_string(faceInfoList[0].det.yaw), {0, 35}, cv::FONT_HERSHEY_PLAIN, 0.5, {0, 255, 0}, 1);
cv::putText(image, "Rol:" + std::to_string(faceInfoList[0].det.roll), {0, 60}, cv::FONT_HERSHEY_PLAIN, 0.5, {0, 255, 0}, 1);
// static expression
for(int i=0; i < faceInfoList[0].static_expression.size(); i++){
bool exp = faceInfoList[0].static_expression[i];
if(exp){
cv::putText(image, static_expression_names[i], {0, 60 + 15 * (1 + i)}, cv::FONT_HERSHEY_PLAIN, 0.5, {255, 255, 255}, 1);
}else{
cv::putText(image, static_expression_names[i], {0, 60 + 15 * (1+ i)}, cv::FONT_HERSHEY_PLAIN, 0.5, {128, 128, 128}, 1);
}
}
for(int i=0; i < faceInfoList[0].dynamic_expression.size(); i++){
bool exp = faceInfoList[0].dynamic_expression[i];
if(exp){
cv::putText(image, dynamic_expression_names[i], {200, 60 + 15 * (1 + i)}, cv::FONT_HERSHEY_PLAIN, 0.5, {255, 255, 255}, 1);
}else{
cv::putText(image, dynamic_expression_names[i], {200, 60 + 15 * (1+ i)}, cv::FONT_HERSHEY_PLAIN, 0.5, {128, 128, 128}, 1);
}
}
}
cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
cv::imshow("demo", image);