一、简介 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 |
六、测试程序
需要在调试时输入四个参数,0 25 1 i000qa-fn.jpg。
参数0表示我们只处理一张人脸
参数25表示检测到的人脸的最小宽度,这里设置为25,是为了下面的调试
参数1表示我们需要打印stasm.log日志
参数i000qa-fn.jpg表示输入的图像名字
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" |
014 | #pragma warning(disable:4996) // 'vsprintf': This function may be unsafe |
016 | static void Exit( const char * format, ...) |
020 | va_start (args, format); |
021 | vsprintf (s, format, args); |
023 | stasm_printf( "\n%s\n" , s); |
028 | static void PrintLandmarks( const float * landmarks, const char * msg) |
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]); |
036 | static void BiaoDing(cv::Mat_<unsigned char > &img, float landmarks[], int nlandmarks=stasm_NLANDMARKS) |
038 | for ( int i=0;i<nlandmarks;i++) |
040 | img(cvRound(landmarks[i*2+1]),cvRound(landmarks[2*i]))=255; |
044 | static void DrawLandmarks( |
045 | cv::Mat_<unsigned char >& img, |
047 | int nlandmarks = stasm_NLANDMARKS) |
049 | for ( int i = 0; i < nlandmarks-1; i++) |
051 | const int ix = cvRound(landmarks[i*2]); |
052 | const int iy = cvRound(landmarks[i*2+1]); |
053 | const int ix1 = cvRound(landmarks[(i+1)*2]); |
054 | const int iy1 = cvRound(landmarks[(i+1)*2+1]); |
056 | cv::Point(ix, iy), cv::Point(ix1, iy1), 255, 1); |
060 | int main( int argc, const char ** argv) |
063 | Exit( "Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE" ); |
065 | const int multi = argv[1][0] - '0' ; |
066 | if (multi != 0 && multi != 1) |
067 | Exit( "Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE, " |
068 | "with MULTI 0 or 1, you have MULTI %s" , argv[1]); |
071 | if ( sscanf (argv[2], "%d" , &minwidth) != 1 || |
072 | minwidth < 1 || minwidth > 100) |
074 | Exit( "Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE with " |
075 | "MINWIDTH 1 to 100, you have MINWIDTH %s" , argv[2]); |
078 | const int trace = argv[3][0] - '0' ; |
079 | if (trace < 0 || trace > 1) |
080 | Exit( "Usage: test_stasm_lib MULTI MINWIDTH TRACE IMAGE, with TRACE 0 or 1" ); |
082 | if (!stasm_init( "../data" , trace)) |
083 | Exit( "stasm_init failed: %s" , stasm_lasterr()); |
085 | const char * path = argv[4]; |
086 | stasm_printf( "Reading %s\n" , path); |
087 | const cv::Mat_<unsigned char > img(cv::imread(path, CV_LOAD_IMAGE_GRAYSCALE)); |
089 | Exit( "Cannot load %s" , path); |
091 | cv::Mat_<unsigned char > outimg(img.clone()); |
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()); |
100 | float landmarks[2 * stasm_NLANDMARKS]; |
104 | stasm_printf( "--- Auto Face %d ---\n" , iface); |
107 | if (!stasm_search_auto_ext(&foundface, landmarks, &estyaw)) |
108 | Exit( "stasm_search_auto failed: %s" , stasm_lasterr()); |
111 | stasm_printf( "No more faces\n" ); |
114 | char s[100]; sprintf (s, "\nFinal with auto init (estyaw %.0f)" , estyaw); |
115 | PrintLandmarks(landmarks, s); |
116 | DrawLandmarks(outimg, landmarks); |
121 | imwrite( "test_stasm_lib_auto.bmp" , outimg); |
124 | if (multi == 0 && minwidth == 25 && iface) |
129 | stasm_printf( "--- Pinned Face %d ---\n" , iface); |
130 | float pinned[2 * stasm_NLANDMARKS]; |
131 | memset (pinned, 0, sizeof (pinned)); |
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]; |
143 | memset (landmarks, 0, sizeof (landmarks)); |
144 | if (!stasm_search_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); |
153 | float newlandmarks[2 * stasm_NLANDMARKS]; |
155 | memcpy (newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof ( float )); |
156 | stasm_convert_shape(newlandmarks, 68); |
157 | PrintLandmarks(newlandmarks, "stasm77 to xm2vts" ); |
160 | outimg = img.clone(); |
161 | DrawLandmarks(outimg, newlandmarks, 68); |
162 | imwrite( "test_stasm_lib_68.bmp" , outimg); |
165 | memcpy (newlandmarks, landmarks, 2 * stasm_NLANDMARKS * sizeof ( float )); |
166 | stasm_convert_shape(newlandmarks, 76); |
167 | PrintLandmarks(newlandmarks, "stasm77 to stasm76" ); |
170 | outimg = img.clone(); |
171 | DrawLandmarks(outimg, newlandmarks, 76); |
172 | imwrite( "test_stasm_lib_76.bmp" , outimg); |
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(); |
181 | BiaoDing(outimg,newlandmarks,22); |
182 | imwrite( "test_stasm_lib_22.bmp_biaoding.bmp" , outimg); |
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(); |
189 | BiaoDing(outimg,newlandmarks,19); |
190 | imwrite( "test_stasm_lib_20_biaoding.bmp" , outimg); |
七、运行效果
1)20个landmarks位置,分别为点图和连线图:
2)68个landmarks特征点
3)76个特征点
4)77个特征点(默认)