本人只是使用了stasm接口中的一部分功能,直接拿他們訓練好的模型進行使用,整個結構精簡過,若需要完整的stasm文件。可下在後面鏈接下載完整版的。
一、簡介 Stasm:
1、stasm是一個c++軟件包,用來定位人臉中面部的landmarks(特徵點)。輸入帶有人臉圖像,返回landmarks的位置。特徵點座標按照[x,y],[x,y]…………順序存放在一維數組中。
2、Stasm被設計工作在大約垂直(豎直)且帶有中性表情的直視的人臉。對於生氣或者帶有表情的將得到不好的效果。嚐嚐會出現下巴部位不準,即使是閉上眼睛也會標記出眼球,這個它用的算法思想有關。
3、Stasm採用的HAT(Histogram Array Transform)描述子來做模版匹配,類似與SIFT描述子。
二、測試和運行
用vs2010打開minimal.sln文件,進入工程後,假如opencv的配置環境,包括(.h,lib,和dll),就可以運行看到效果。本人實驗室用的是2.4.8版本的。這裏可能需要小小的配置一下。
三、Stasm的庫函數
庫接口定義在stasm_lib.h文件中,landmarks的名字列在stasm_landmarks.h的頭文件中。
1、簡單接口:
最簡單的方法:使用stasm_search_single函數,該函數使用opencv的前側人臉檢測器找到圖像中最大的人臉,並且返回landmarks的位置。
人臉的寬度至少是圖像寬度的10%。本人提供的demo中調用的就是這個函數。因爲目前我的項目中只涉及到一張圖中只有一個人臉的情況。
**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倍的大小,這樣可以簡單的人臉,但是也降低了檢測的速度。
對於左側人臉,OpenCV經常會檢測不到,因爲人臉太過於靠近圖像的邊緣。
對於左側圖像,Stasm通過人工增加邊界1.2*1.2倍的大小,這樣可以簡單的人臉,但是也降低了檢測的速度。
五、stasm_landmarks.h文件內的landmarks的名字:
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
};
六、測試程序
該程序直接調用攝像頭,實時觀察人臉五官的定位。也可以自己修改一下讀取圖片進行處理。
// minimal.cpp: Display the landmarks of a face in an image.
// This demonstrates stasm_search_single.
#include <stdio.h>
#include <stdlib.h>
#include "opencv/highgui.h"
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <opencv2/core/core.hpp>
#include "stasm_lib.h"
#include <core/core.hpp>
#include <direct.h>
#include <crtdbg.h>
#include "stasm_landmarks.h"
using namespace cv ;
int main()
{
VideoCapture capture(0);
if (!capture.isOpened())
{
printf("打開攝像頭失敗");
return -1;
}//檢測是否成功打開攝像頭
Mat img2;
while (1)
{
static const char* const path = "../data/testface.jpg";
capture>> img2;
if (!capture.read(img2))//這個判斷的必須的
{
break;
}
Mat gray;
Mat img;
cvtColor(img2,img,CV_BGR2GRAY,0);
if (waitKey(30) >= 0)//刷新間隔30ms,按任意鍵退出
break;
int foundface;
float landmarks[2 * stasm_NLANDMARKS]={0}; // x,y coords (note the 2)
if (!stasm_search_single(&foundface, landmarks,(const char*)img.data, img.cols, img.rows, path, "../data"))
{
printf("Error in stasm_search_single: %s\n", stasm_lasterr());
exit(1);
}
if (!foundface)
printf("No face found\n");
else
{
stasm_force_points_into_image(landmarks, img.cols, img.rows);//座標在範圍限制
for (int i = 0; i <stasm_NLANDMARKS; i++)
{
circle(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),3,CV_RGB(255,255,255),1,8, 0);
}
#if 0
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, (const char*)img.data, img.cols, img.rows, path))
printf("Nooooooooooooooooooooooooooooooooo");
for (int i = 0; i <stasm_NLANDMARKS; i++)
{
circle(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),3,CV_RGB(255,0,255),1,8, 0);
}
#endif
// 76點
#if 1
stasm_convert_shape(landmarks, 76);
//下巴輪廓[0~15)共15個點;
int start=0;
int end=15;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(255,255,255),1,8, 0);
}
//// 右眉毛[15~21)共6個點;
start=15;
end=21;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(255,255,255),1,8, 0);
}
line(img2,Point(cvRound(landmarks[end*2-2]),cvRound(landmarks[end*2-1])),Point(cvRound(landmarks[start*2]),cvRound(landmarks[start*2+1])),CV_RGB(255,255,255),1,8, 0);
// //右眉毛[21~27)共6個點;
start=21;
end=27;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(255,255,255),1,8, 0);
}
line(img2,Point(cvRound(landmarks[end*2-2]),cvRound(landmarks[end*2-1])),Point(cvRound(landmarks[start*2]),cvRound(landmarks[start*2+1])),CV_RGB(255,255,255),1,8, 0);
//左眼球[27~31)共4個點;
start=27;
end=31;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(255,255,255),1,8, 0);
}
line(img2,Point(cvRound(landmarks[end*2-2]),cvRound(landmarks[end*2-1])),Point(cvRound(landmarks[start*2]),cvRound(landmarks[start*2+1])),CV_RGB(255,255,255),1,8, 0);
//右眼球[32~36)共4個點;
start=32;
end=36;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(255,255,255),1,8, 0);
}
line(img2,Point(cvRound(landmarks[end*2-2]),cvRound(landmarks[end*2-1])),Point(cvRound(landmarks[start*2]),cvRound(landmarks[start*2+1])),CV_RGB(255,255,255),1,8, 0);
////鼻樑[37~46)共9個點
start=37;
end=46;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(255,255,255),1,8, 0);
}
////嘴外輪廓[48~60)共12個點
start=48;
end=60;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(255,255,255),1,8, 0);
}
line(img2,Point(cvRound(landmarks[end*2-2]),cvRound(landmarks[end*2-1])),Point(cvRound(landmarks[start*2]),cvRound(landmarks[start*2+1])),CV_RGB(255,255,255),1,8, 0);
////嘴2[60~66)共6個點
start=60;
end=66;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(255,255,255),1,8, 0);
}
line(img2,Point(cvRound(landmarks[end*2-2]),cvRound(landmarks[end*2-1])),Point(cvRound(landmarks[start*2]),cvRound(landmarks[start*2+1])),CV_RGB(255,255,255),1,8, 0);
//左眼球
start=68;
end=72;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(0,255,255),1,8, 0);
}
line(img2,Point(cvRound(landmarks[end*2-2]),cvRound(landmarks[end*2-1])),Point(cvRound(landmarks[start*2]),cvRound(landmarks[start*2+1])),CV_RGB(0,255,255),1,8, 0);
//右眼球
start=72;
end=76;
for (int i = start+1; i <end; i++)
{
line(img2,Point(cvRound(landmarks[i*2]),cvRound(landmarks[i*2+1])),Point(cvRound(landmarks[i*2-2]),cvRound(landmarks[i*2-1])),CV_RGB(0,255,255),1,8, 0);
}
line(img2,Point(cvRound(landmarks[end*2-2]),cvRound(landmarks[end*2-1])),Point(cvRound(landmarks[start*2]),cvRound(landmarks[start*2+1])),CV_RGB(0,255,255),1,8, 0);
#endif
}
int thickness = 1;
int lineType = 8;
cvNamedWindow("show_image",256);
cv::imshow("show_image", img2);
}
return 0;
}
76點圖
vs2010+opencv2+ASM 五官特徵點定位源碼
Active Shape Models with Stasm英文鏈接
歡迎志同道合着一起交流!!!
需要源碼的請留言