(转)原文地址:http://www.aiuxian.com/article/p-656517.html
一、简介 Stasm:
1、stasm是一个c++软件包,用来定位人脸中面部的landmarks(路标,特征点)。输入带有人脸图像,返回landmarks的位置。
2、Stasm被设计工作在大约垂直(竖直)且带有中性表情的直视的人脸。对于生气或者带有表情的将得到不好的效果。
3、Stasm采用的HAT(Histogram Array Transform)描述子来做模版匹配,类似与SIFT描述子。
二、测试和运行
用vs 2010打开 stasm4.0.0\vc10目录下的minimal.sln文件,进入工程后,假如opencv的配置环境,包括(.h,lib,和dll),就可以运行看到效果。
三、Stasm的库函数
库接口定义在stasm_lib.h文件中,landmarks的名字列在stasm_landmarks.h的头文件中。
1、简单接口:
最简单的方法:使用stasm_search_single函数,该函数使用opencv的前侧人脸检测器找到图像中最大的人脸,并且返回landmarks的位置。
人脸的宽度至少是图像宽度的10%。
2、更多用途的接口:
1)一个图像中多张人脸的标定(landmarks)
2)可以找到一些连续的接口,一致的接口。
最基本的思想是:首先调用stasm_open_image函数来检测人脸,其次重复的调用stasm_search_auto函数来一个一个地landmarks(标记)人脸。具体详情参考minimal2.cpp和stasm_lib.h中的注释。
3、multiface 参数
stasm_open_image函数中的参数multiface,如果设置为1,你可以重复地调用stasm_search_auto函数,直到图像找到图像中的所有人脸(人脸检测器能够检测到的)。
如果设置为0,stasm_search_auto函数返回一个“最好”的人脸,通常是OpenCV人脸检测器检测到的最大的人脸。
4、用户部分初始化
在许多应用中,需要人为的手动矫正人脸上的一些点,使用stasm_search_pinned函数可以实现。
主要用在用户指定5个点:眼睛的外角(2个),鼻子的顶端(1个),和嘴角(2个)。
5、工具函数
1)stasm_convert_shape函数,例如:stasm_convert_shape(newlandmarks, 76);newlandmarks为float类型的数组,大小为2*stasm_NLANDMARKS,stasm_NLANDMARKS为默认值77。即可以改变寻找特征点的个数,一般为20,22,68,76和默认的77。对于其他值如40,则特征点全为0(不工作)。
2)stasm_face_points_into_image函数,是landmarks(标记, 特征点)在图像的边界内部。例如如果一个人的前额被图像的边缘剪切了,Stasm将会把landmarks(特征点)的位置定位在图像的边界外面。
3)stasm_printf 打印输出流,类似与printf函数,但也可以打印到文件stasm.log。如果stasm_init函数的trace参数设置为1,则输出stasm.log日志文件。
四、注意人脸检测实现
对于左侧人脸,OpenCV经常会检测不到,因为人脸太过于靠近图像的边缘。
对于左侧图像,Stasm通过人工增加边界1.2*1.2倍的大小,这样可以简单的人脸,但是也降低了检测的速度。
enum stasm_LANDMARKS_77 // stasm77 landmarks
{
L_LTemple, // 00
L_LJaw01, // 01
L_LJawNoseline, // 02 nose line on left jaw
L_LJawMouthline, // 03 mouth line on left jaw
L_LJaw04, // 04
L_LJaw05, // 05
L_CTipOfChin, // 06
L_RJaw07, // 07
L_RJaw08, // 08
L_RJawMouthline, // 09
L_RJawNoseline, // 10
L_RJaw11, // 11
L_RTemple, // 12
L_RForehead, // 13
L_CForehead, // 14
L_LForehead, // 15
L_LEyebrowTopInner, // 16
L_LEyebrowTopOuter, // 17
L_LEyebrowOuter, // 18
L_LEyebrowBotOuter, // 19
L_LEyebrowBotInner, // 20
L_LEyebrowInner, // 21
L_REyebrowInner, // 22
L_REyebrowTopInner, // 23
L_REyebrowTopOuter, // 24
L_REyebrowOuter, // 25
L_REyebrowBotOuter, // 26
L_REyebrowBotInner, // 27
L_REyelid, // 28
L_LEyelid, // 29
L_LEyeInner, // 30
L_LEye31, // 31
L_LEyeTop, // 32
L_LEye33, // 33
L_LEyeOuter, // 34
L_LEye35, // 35
L_LEyeBot, // 36
L_LEye37, // 37
L_LPupil, // 38
L_RPupil, // 39
L_REyeInner, // 40
L_REye41, // 41
L_REyeTop, // 42
L_REye43, // 43
L_REyeOuter, // 44
L_REye45, // 45
L_REyeBot, // 46
L_REye47, // 47
L_RNoseMid, // 48
L_CNoseMid, // 49
L_LNoseMid, // 50
L_LNostrilTop, // 51
L_CNoseTip, // 52
L_RNostrilTop, // 53
L_RNoseSide, // 54
L_RNostrilBot, // 55
L_CNoseBase, // 56
L_LNostrilBot, // 57
L_LNoseSide, // 58
L_LMouthCorner, // 59
L_LMouth60, // 60
L_LMouthCupid, // 61
L_CTopOfTopLip, // 62
L_RMouthCupid, // 63
L_RMouth64, // 64
L_RMouthCorner, // 65
L_RMouth66, // 66
L_CBotOfTopLip, // 67
L_LMouth68, // 68
L_LMouth69, // 69
L_CTopOfBotLip, // 70
L_RMouth71, // 71
L_RMouth72, // 72
L_RMouth73, // 73
L_CBotOfBotLip, // 74
L_LMouth75, // 75
L_LMouth76 // 76
};
六、测试程序
需要在调试时输入四个参数,0 25 1 i000qa-fn.jpg。
参数0表示我们只处理一张人脸
参数25表示检测到的人脸的最小宽度,这里设置为25,是为了下面的调试
参数1表示我们需要打印stasm.log日志
参数i000qa-fn.jpg表示输入的图像名字
// test_stasm_lib.cpp: test stasm_lib.cpp
//
// Copyright (C) 2005-2013, Stephen Milborrow
#include
#include
#include
#include
#include "opencv/highgui.h" // needed for imread
#include "stasm_lib.h"
#include "stasm_lib_ext.h" // needed for stasm_search_auto_ext
#include "stasm_landmarks.h"
#pragma warning(disable:4996) // 'vsprintf': This function may be unsafe
static void Exit(const char* format, ...) // args like printf
{
char s[1024+1];
va_list args;
va_start(args, format);
vsprintf(s, format, args);
va_end(args);
stasm_printf("\n%s\n", s);
exit(1);
}
//在控制台打印出标记点
static void PrintLandmarks(const float* landmarks, const char* msg)
{
stasm_printf("%s:\n", msg);
for (int i = 0; i < stasm_NLANDMARKS; i++)
stasm_printf("%3d: %4.0f %4.0f\n",
i, landmarks[i*2], landmarks[i*2+1]);//点的位置(x,y)坐标
}
//标定出这些点
static void BiaoDing(cv::Mat_ &img,float landmarks[],int nlandmarks=stasm_NLANDMARKS)
{
for(int i=0;i& img,
float landmarks[],
int nlandmarks = stasm_NLANDMARKS)
{
for (int i = 0; i < nlandmarks-1; i++)
{
const int ix = cvRound(landmarks[i*2]); // this point
const int iy = cvRound(landmarks[i*2+1]);
const int ix1 = cvRound(landmarks[(i+1)*2]); // next point
const int iy1 = cvRound(landmarks[(i+1)*2+1]);
cv::line(img,
cv::Point(ix, iy), cv::Point(ix1, iy1), 255, 1);
}
}
int main(int argc, const char** argv)
{
if (argc != 5)
Exit("Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE");
const int multi = argv[1][0] - '0';//将输入的为char*类型的参数转换为int如输入的为0则输出multi为0
if (multi != 0 && multi != 1) //我们设置为0,因为我们只处理一个人脸
Exit("Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE, "
"with MULTI 0 or 1, you have MULTI %s", argv[1]);
int minwidth = -1;
if (sscanf(argv[2], "%d", &minwidth) != 1 ||//设置检测的人脸的最小宽度,如果没有扫描成功,或者最小的宽度<1或者大于100则退出
minwidth < 1 || minwidth > 100)
{
Exit("Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE with "
"MINWIDTH 1 to 100, you have MINWIDTH %s", argv[2]);
}
const int trace = argv[3][0] - '0';//我们想跟踪日志,所以我们设置为1
if (trace < 0 || trace > 1)
Exit("Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE, with TRACE 0 or 1");
if (!stasm_init("../data", trace))
Exit("stasm_init failed: %s", stasm_lasterr());
const char* path = argv[4]; // image name//第五个参数(输入的四个参数),图像的名字
stasm_printf("Reading %s\n", path);
const cv::Mat_ img(cv::imread(path, CV_LOAD_IMAGE_GRAYSCALE));
if (!img.data) // could not load image?
Exit("Cannot load %s", path);
cv::Mat_ outimg(img.clone());
if (!stasm_open_image((const char*)img.data, img.cols, img.rows,
path, multi != 0, minwidth))
Exit("stasm_open_image failed: %s", stasm_lasterr());
// Test stasm_search_auto.
// The min face size was set in the above stasm_open_image call.
float landmarks[2 * stasm_NLANDMARKS]; // x,y coords
int iface = 0;
while (1)
{
stasm_printf("--- Auto Face %d ---\n", iface);
int foundface;
float estyaw;
if (!stasm_search_auto_ext(&foundface, landmarks, &estyaw))//如果没有成功运行
Exit("stasm_search_auto failed: %s", stasm_lasterr());
if (!foundface)//如果没有找到人脸,或者最后一个人脸结束
{
stasm_printf("No more faces\n");
break; // note break
}
char s[100]; sprintf(s, "\nFinal with auto init (estyaw %.0f)", estyaw);
PrintLandmarks(landmarks, s);//标记人脸
DrawLandmarks(outimg, landmarks);//连线人脸
iface++;//统计找到的人脸的个数。
if (trace)
stasm_printf("\n");
}
imwrite("test_stasm_lib_auto.bmp", outimg);
//下面是测试用的,当设置只寻找一个人脸,且minwidh=25并且找到了人脸,则进入调试状态。
if (multi == 0 && minwidth == 25 && iface)
{
// Test stasm_search_pinned. A human user is not at hand, so gyp by using
// points from the last face found above for our 5 start points
stasm_printf("--- Pinned Face %d ---\n", iface);
float pinned[2 * stasm_NLANDMARKS]; // x,y coords
memset(pinned, 0, sizeof(pinned));//初始化一个pin空间和stasm_NLANDMARK一样大
pinned[L_LEyeOuter*2] = landmarks[L_LEyeOuter*2] + 2;
pinned[L_LEyeOuter*2+1] = landmarks[L_LEyeOuter*2+1];
pinned[L_REyeOuter*2] = landmarks[L_REyeOuter*2] - 2;
pinned[L_REyeOuter*2+1] = landmarks[L_REyeOuter*2+1];
pinned[L_CNoseTip*2] = landmarks[L_CNoseTip*2];
pinned[L_CNoseTip*2+1] = landmarks[L_CNoseTip*2+1];
pinned[L_LMouthCorner*2] = landmarks[L_LMouthCorner*2];
pinned[L_LMouthCorner*2+1] = landmarks[L_LMouthCorner*2+1];
pinned[L_RMouthCorner*2] = landmarks[L_RMouthCorner*2];
pinned[L_RMouthCorner*2+1] = landmarks[L_RMouthCorner*2+1];
memset(landmarks, 0, sizeof(landmarks));//将landmarks重置为0
if (!stasm_search_pinned(landmarks,//利用pinned矫正landmarks
pinned, (const char*)img.data, img.cols, img.rows, path))
Exit("stasm_search_pinned failed: %s", stasm_lasterr());
PrintLandmarks(landmarks, "Final with pinned init");
outimg = img.clone();
DrawLandmarks(outimg, landmarks);
imwrite("test_stasm_lib_pinned.bmp", outimg);
// test stasm_convert_shape,找到了位置之后的处理
float newlandmarks[2 * stasm_NLANDMARKS]; // x,y coords
#if 0
memcpy(newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof(float));
stasm_convert_shape(newlandmarks, 68);
PrintLandmarks(newlandmarks, "stasm77 to xm2vts");
#endif
#if 0
outimg = img.clone();
DrawLandmarks(outimg, newlandmarks, 68);
imwrite("test_stasm_lib_68.bmp", outimg);
#endif
#if 0
memcpy(newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof(float));
stasm_convert_shape(newlandmarks, 76);
PrintLandmarks(newlandmarks, "stasm77 to stasm76");
#endif
#if 0
outimg = img.clone();
DrawLandmarks(outimg, newlandmarks, 76);
imwrite("test_stasm_lib_76.bmp", outimg);
#endif
#if 1
memcpy(newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof(float));
stasm_convert_shape(newlandmarks, 22);
PrintLandmarks(newlandmarks, "stasm77 to stasm22");
outimg = img.clone();
// DrawLandmarks(outimg, newlandmarks, 22);
BiaoDing(outimg,newlandmarks,22);
imwrite("test_stasm_lib_22.bmp_biaoding.bmp", outimg);
memcpy(newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof(float));
stasm_convert_shape(newlandmarks, 20);
PrintLandmarks(newlandmarks, "stasm77 to stasm20");
outimg = img.clone();
//DrawLandmarks(outimg, newlandmarks, 20);
BiaoDing(outimg,newlandmarks,19);
imwrite("test_stasm_lib_20_biaoding.bmp", outimg);
#endif
}
return 0; // success
}
七、运行效果
1)20个landmarks位置,分别为点图和连线图:
2)68个landmarks特征点
3)76个特征点
4)77个特征点(默认)
学习讨论之用,联系作者删除