一、简介 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倍的大小,这样可以简单的人脸,但是也降低了检测的速度。
五、stasm_landmarks.h文件内的landmarks的名字:
01 |
enum stasm_LANDMARKS_77 // stasm77 landmarks |
02 |
{ |
03 |
L_LTemple, // 00 |
04 |
L_LJaw01, // 01 |
05 |
L_LJawNoseline, // 02 nose line on left jaw |
06 |
L_LJawMouthline, // 03 mouth line on left jaw |
07 |
L_LJaw04, // 04 |
08 |
L_LJaw05, // 05 |
09 |
L_CTipOfChin, // 06 |
10 |
L_RJaw07, // 07 |
11 |
L_RJaw08, // 08 |
12 |
L_RJawMouthline, // 09 |
13 |
L_RJawNoseline, // 10 |
14 |
L_RJaw11, // 11 |
15 |
L_RTemple, // 12 |
16 |
L_RForehead, // 13 |
17 |
L_CForehead, // 14 |
18 |
L_LForehead, // 15 |
19 |
L_LEyebrowTopInner, // 16 |
20 |
L_LEyebrowTopOuter, // 17 |
21 |
L_LEyebrowOuter, // 18 |
22 |
L_LEyebrowBotOuter, // 19 |
23 |
L_LEyebrowBotInner, // 20 |
24 |
L_LEyebrowInner, // 21 |
25 |
L_REyebrowInner, // 22 |
26 |
L_REyebrowTopInner, // 23 |
27 |
L_REyebrowTopOuter, // 24 |
28 |
L_REyebrowOuter, // 25 |
29 |
L_REyebrowBotOuter, // 26 |
30 |
L_REyebrowBotInner, // 27 |
31 |
L_REyelid, // 28 |
32 |
L_LEyelid, // 29 |
33 |
L_LEyeInner, // 30 |
34 |
L_LEye31, // 31 |
35 |
L_LEyeTop, // 32 |
36 |
L_LEye33, // 33 |
37 |
L_LEyeOuter, // 34 |
38 |
L_LEye35, // 35 |
39 |
L_LEyeBot, // 36 |
40 |
L_LEye37, // 37 |
41 |
L_LPupil, // 38 |
42 |
L_RPupil, // 39 |
43 |
L_REyeInner, // 40 |
44 |
L_REye41, // 41 |
45 |
L_REyeTop, // 42 |
46 |
L_REye43, // 43 |
47 |
L_REyeOuter, // 44 |
48 |
L_REye45, // 45 |
49 |
L_REyeBot, // 46 |
50 |
L_REye47, // 47 |
51 |
L_RNoseMid, // 48 |
52 |
L_CNoseMid, // 49 |
53 |
L_LNoseMid, // 50 |
54 |
L_LNostrilTop, // 51 |
55 |
L_CNoseTip, // 52 |
56 |
L_RNostrilTop, // 53 |
57 |
L_RNoseSide, // 54 |
58 |
L_RNostrilBot, // 55 |
59 |
L_CNoseBase, // 56 |
60 |
L_LNostrilBot, // 57 |
61 |
L_LNoseSide, // 58 |
62 |
L_LMouthCorner, // 59 |
63 |
L_LMouth60, // 60 |
64 |
L_LMouthCupid, // 61 |
65 |
L_CTopOfTopLip, // 62 |
66 |
L_RMouthCupid, // 63 |
67 |
L_RMouth64, // 64 |
68 |
L_RMouthCorner, // 65 |
69 |
L_RMouth66, // 66 |
70 |
L_CBotOfTopLip, // 67 |
71 |
L_LMouth68, // 68 |
72 |
L_LMouth69, // 69 |
73 |
L_CTopOfBotLip, // 70 |
74 |
L_RMouth71, // 71 |
75 |
L_RMouth72, // 72 |
76 |
L_RMouth73, // 73 |
77 |
L_CBotOfBotLip, // 74 |
78 |
L_LMouth75, // 75 |
79 |
L_LMouth76 // 76 |
80 |
}; |
六、测试程序
需要在调试时输入四个参数,0 25 1 i000qa-fn.jpg。
参数0表示我们只处理一张人脸
参数25表示检测到的人脸的最小宽度,这里设置为25,是为了下面的调试
参数1表示我们需要打印stasm.log日志
参数i000qa-fn.jpg表示输入的图像名字
001 |
// test_stasm_lib.cpp: test stasm_lib.cpp |
002 |
// |
003 |
// Copyright (C) 2005-2013, Stephen Milborrow |
004 |
005 |
#include <stdio.h> |
006 |
#include <stdarg.h> |
007 |
#include <stdlib.h> |
008 |
#include <time.h> |
009 |
#include "opencv/highgui.h" // needed for imread |
010 |
#include "stasm_lib.h" |
011 |
#include "stasm_lib_ext.h" // needed for stasm_search_auto_ext |
012 |
#include "stasm_landmarks.h" |
013 |
014 |
#pragma warning(disable:4996) // 'vsprintf': This function may be unsafe |
015 |
016 |
static void Exit( const char * format, ...) // args like printf |
017 |
{ |
018 |
char s[1024+1]; |
019 |
va_list args; |
020 |
va_start (args, format); |
021 |
vsprintf (s, format, args); |
022 |
va_end (args); |
023 |
stasm_printf( "\n%s\n" , s); |
024 |
exit (1); |
025 |
} |
026 |
027 |
//在控制台打印出标记点 |
028 |
static void PrintLandmarks( const float * landmarks, const char * msg) |
029 |
{ |
030 |
stasm_printf( "%s:\n" , msg); |
031 |
for ( int i = 0; i < stasm_NLANDMARKS; i++) |
032 |
stasm_printf( "%3d: %4.0f %4.0f\n" , |
033 |
i, landmarks[i*2], landmarks[i*2+1]); //点的位置(x,y)坐标 |
034 |
} |
035 |
//标定出这些点 |
036 |
static void BiaoDing(cv::Mat_<unsigned char > &img, float landmarks[], int nlandmarks=stasm_NLANDMARKS) |
037 |
{ |
038 |
for ( int i=0;i<nlandmarks;i++) |
039 |
{ |
040 |
img(cvRound(landmarks[i*2+1]),cvRound(landmarks[2*i]))=255; |
041 |
} |
042 |
} |
043 |
//画出标记点,连线形式 |
044 |
static void DrawLandmarks( |
045 |
cv::Mat_<unsigned char >& img, |
046 |
float landmarks[], |
047 |
int nlandmarks = stasm_NLANDMARKS) |
048 |
{ |
049 |
for ( int i = 0; i < nlandmarks-1; i++) |
050 |
{ |
051 |
const int ix = cvRound(landmarks[i*2]); // this point |
052 |
const int iy = cvRound(landmarks[i*2+1]); |
053 |
const int ix1 = cvRound(landmarks[(i+1)*2]); // next point |
054 |
const int iy1 = cvRound(landmarks[(i+1)*2+1]); |
055 |
cv::line(img, |
056 |
cv::Point(ix, iy), cv::Point(ix1, iy1), 255, 1); |
057 |
} |
058 |
} |
059 |
060 |
int main( int argc, const char ** argv) |
061 |
{ |
062 |
if (argc != 5) |
063 |
Exit( "Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE" ); |
064 |
065 |
const int multi = argv[1][0] - '0' ; //将输入的为char*类型的参数转换为int如输入的为0则输出multi为0 |
066 |
if (multi != 0 && multi != 1) //我们设置为0,因为我们只处理一个人脸 |
067 |
Exit( "Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE, " |
068 |
"with MULTI 0 or 1, you have MULTI %s" , argv[1]); |
069 |
070 |
int minwidth = -1; |
071 |
if ( sscanf (argv[2], "%d" , &minwidth) != 1 || //设置检测的人脸的最小宽度,如果没有扫描成功,或者最小的宽度<1或者大于100则退出 |
072 |
minwidth < 1 || minwidth > 100) |
073 |
{ |
074 |
Exit( "Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE with " |
075 |
"MINWIDTH 1 to 100, you have MINWIDTH %s" , argv[2]); |
076 |
} |
077 |
078 |
const int trace = argv[3][0] - '0' ; //我们想跟踪日志,所以我们设置为1 |
079 |
if (trace < 0 || trace > 1) |
080 |
Exit( "Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE, with TRACE 0 or 1" ); |
081 |
082 |
if (!stasm_init( "../data" , trace)) |
083 |
Exit( "stasm_init failed: %s" , stasm_lasterr()); |
084 |
085 |
const char * path = argv[4]; // image name//第五个参数(输入的四个参数),图像的名字 |
086 |
stasm_printf( "Reading %s\n" , path); |
087 |
const cv::Mat_<unsigned char > img(cv::imread(path, CV_LOAD_IMAGE_GRAYSCALE)); |
088 |
if (!img.data) // could not load image? |
089 |
Exit( "Cannot load %s" , path); |
090 |
091 |
cv::Mat_<unsigned char > outimg(img.clone()); |
092 |
093 |
if (!stasm_open_image(( const char *)img.data, img.cols, img.rows, |
094 |
path, multi != 0, minwidth)) |
095 |
Exit( "stasm_open_image failed: %s" , stasm_lasterr()); |
096 |
097 |
// Test stasm_search_auto. |
098 |
// The min face size was set in the above stasm_open_image call. |
099 |
100 |
float landmarks[2 * stasm_NLANDMARKS]; // x,y coords |
101 |
int iface = 0; |
102 |
while (1) |
103 |
{ |
104 |
stasm_printf( "--- Auto Face %d ---\n" , iface); |
105 |
int foundface; |
106 |
float estyaw; |
107 |
if (!stasm_search_auto_ext(&foundface, landmarks, &estyaw)) //如果没有成功运行 |
108 |
Exit( "stasm_search_auto failed: %s" , stasm_lasterr()); |
109 |
if (!foundface) //如果没有找到人脸,或者最后一个人脸结束 |
110 |
{ |
111 |
stasm_printf( "No more faces\n" ); |
112 |
break ; // note break |
113 |
} |
114 |
char s[100]; sprintf (s, "\nFinal with auto init (estyaw %.0f)" , estyaw); |
115 |
PrintLandmarks(landmarks, s); //标记人脸 |
116 |
DrawLandmarks(outimg, landmarks); //连线人脸 |
117 |
iface++; //统计找到的人脸的个数。 |
118 |
if (trace) |
119 |
stasm_printf( "\n" ); |
120 |
} |
121 |
imwrite( "test_stasm_lib_auto.bmp" , outimg); |
122 |
123 |
//下面是测试用的,当设置只寻找一个人脸,且minwidh=25并且找到了人脸,则进入调试状态。 |
124 |
if (multi == 0 && minwidth == 25 && iface) |
125 |
{ |
126 |
// Test stasm_search_pinned. A human user is not at hand, so gyp by using |
127 |
// points from the last face found above for our 5 start points |
128 |
129 |
stasm_printf( "--- Pinned Face %d ---\n" , iface); |
130 |
float pinned[2 * stasm_NLANDMARKS]; // x,y coords |
131 |
memset (pinned, 0, sizeof (pinned)); //初始化一个pin空间和stasm_NLANDMARK一样大 |
132 |
pinned[L_LEyeOuter*2] = landmarks[L_LEyeOuter*2] + 2; |
133 |
pinned[L_LEyeOuter*2+1] = landmarks[L_LEyeOuter*2+1]; |
134 |
pinned[L_REyeOuter*2] = landmarks[L_REyeOuter*2] - 2; |
135 |
pinned[L_REyeOuter*2+1] = landmarks[L_REyeOuter*2+1]; |
136 |
pinned[L_CNoseTip*2] = landmarks[L_CNoseTip*2]; |
137 |
pinned[L_CNoseTip*2+1] = landmarks[L_CNoseTip*2+1]; |
138 |
pinned[L_LMouthCorner*2] = landmarks[L_LMouthCorner*2]; |
139 |
pinned[L_LMouthCorner*2+1] = landmarks[L_LMouthCorner*2+1]; |
140 |
pinned[L_RMouthCorner*2] = landmarks[L_RMouthCorner*2]; |
141 |
pinned[L_RMouthCorner*2+1] = landmarks[L_RMouthCorner*2+1]; |
142 |
143 |
memset (landmarks, 0, sizeof (landmarks)); //将landmarks重置为0 |
144 |
if (!stasm_search_pinned(landmarks, //利用pinned矫正landmarks |
145 |
pinned, ( const char *)img.data, img.cols, img.rows, path)) |
146 |
Exit( "stasm_search_pinned failed: %s" , stasm_lasterr()); |
147 |
PrintLandmarks(landmarks, "Final with pinned init" ); |
148 |
outimg = img.clone(); |
149 |
DrawLandmarks(outimg, landmarks); |
150 |
imwrite( "test_stasm_lib_pinned.bmp" , outimg); |
151 |
152 |
// test stasm_convert_shape,找到了位置之后的处理 |
153 |
float newlandmarks[2 * stasm_NLANDMARKS]; // x,y coords |
154 |
#if 0 |
155 |
memcpy (newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof ( float )); |
156 |
stasm_convert_shape(newlandmarks, 68); |
157 |
PrintLandmarks(newlandmarks, "stasm77 to xm2vts" ); |
158 |
#endif |
159 |
#if 0 |
160 |
outimg = img.clone(); |
161 |
DrawLandmarks(outimg, newlandmarks, 68); |
162 |
imwrite( "test_stasm_lib_68.bmp" , outimg); |
163 |
#endif |
164 |
#if 0 |
165 |
memcpy (newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof ( float )); |
166 |
stasm_convert_shape(newlandmarks, 76); |
167 |
PrintLandmarks(newlandmarks, "stasm77 to stasm76" ); |
168 |
#endif |
169 |
#if 0 |
170 |
outimg = img.clone(); |
171 |
DrawLandmarks(outimg, newlandmarks, 76); |
172 |
imwrite( "test_stasm_lib_76.bmp" , outimg); |
173 |
#endif |
174 |
175 |
#if 1 |
176 |
memcpy (newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof ( float )); |
177 |
stasm_convert_shape(newlandmarks, 22); |
178 |
PrintLandmarks(newlandmarks, "stasm77 to stasm22" ); |
179 |
outimg = img.clone(); |
180 |
// DrawLandmarks(outimg, newlandmarks, 22); |
181 |
BiaoDing(outimg,newlandmarks,22); |
182 |
imwrite( "test_stasm_lib_22.bmp_biaoding.bmp" , outimg); |
183 |
184 |
memcpy (newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof ( float )); |
185 |
stasm_convert_shape(newlandmarks, 20); |
186 |
PrintLandmarks(newlandmarks, "stasm77 to stasm20" ); |
187 |
outimg = img.clone(); |
188 |
//DrawLandmarks(outimg, newlandmarks, 20); |
189 |
BiaoDing(outimg,newlandmarks,19); |
190 |
imwrite( "test_stasm_lib_20_biaoding.bmp" , outimg); |
191 |
#endif |
192 |
} |
193 |
194 |
return 0; // success |
195 |
} |
七、运行效果
1)20个landmarks位置,分别为点图和连线图:
2)68个landmarks特征点
3)76个特征点
4)77个特征点(默认)
var imgs = ("#articleCon img"); for(var i = 0 ; i