目標
在本教程中,您將學習如何
- 建立 FacemarkAAM 的例項
- 訓練 AAM 模型
- 使用 FacemarkAAM 進行擬合
準備
在繼續本教程之前,您應該下載面部特徵點檢測的資料集。我們建議您下載 LFPW 資料集,可以從 https://ibug.doc.ic.ac.uk/download/annotations/lfpw.zip 獲取。
確保註釋格式受 API 支援,註釋檔案中的內容應如下所示:
version: 1
n_points: 68
{
212.716603 499.771793
230.232816 566.290071
...
}
接下來要做的是建立 2 個文字檔案,分別包含影像檔案列表和註釋檔案列表。確保兩個檔案中影像和註釋的順序匹配。此外,建議使用絕對路徑而不是相對路徑。在 Linux 機器中建立檔案列表的示例:
ls $PWD/trainset/*.jpg > images_train.txt
ls $PWD/trainset/*.pts > annotation_train.txt
images_train.txt 檔案內容示例
/home/user/lfpw/trainset/100032540_1.jpg
/home/user/lfpw/trainset/100040721_1.jpg
/home/user/lfpw/trainset/100040721_2.jpg
/home/user/lfpw/trainset/1002681492_1.jpg
annotation_train.txt 中的內容示例:
/home/user/lfpw/trainset/100032540_1.pts
/home/user/lfpw/trainset/100040721_1.pts
/home/user/lfpw/trainset/100040721_2.pts
/home/user/lfpw/trainset/1002681492_1.pts
可選地,您可以為測試集建立類似的檔案。
在本教程中,由於預訓練模型的檔案大小較大(約 500MB),因此不會提供。透過遵循本教程,您將能夠在幾分鐘內訓練獲得自己的訓練模型。
使用 AAM 演算法
完整的程式碼可在 face/samples/facemark_demo_aam.cpp 檔案中找到。在本教程中,將介紹一些重要部分的解釋。
建立 AAM 演算法的例項
params.model_filename =
"AAM.yaml";
首先,建立 AAM 演算法的引數例項。在本例中,我們將修改預設的縮放因子列表。預設情況下,使用的縮放因子為 1.0(無縮放)。這裡我們添加了兩個縮放因子,這將使例項以 2 和 4 的比例訓練兩個模型(分別縮小 2 倍和 4 倍,擬合時間更快)。但是,您應該確保此縮放因子不要太大,因為它會將影像縮放到非常小的影像。因此,它將丟失所有用於地標檢測目的的重要資訊。
或者,您可以像此示例一樣覆蓋預設縮放:
std::vector<float>scales;
scales.push_back(1.5);
scales.push_back(2.4);
FacemarkAAM::Params params;
params.scales = scales;
載入資料集
std::vector<String> images_train;
std::vector<String> landmarks_train;
loadDatasetList(images_path,annotations_path,images_train,landmarks_train);
資料集列表已載入到程式中。我們將在下一步中將資料集中的樣本逐一放入。
將樣本新增到訓練器
std::vector<Point2f> facial_points;
for(size_t i=0;i<images_train.size();i++){
image =
imread(images_train[i].c_str());
facemark->addTrainingSample(image, facial_points);
}
來自資料集列表的影像以及其對應的註釋資料逐一載入。然後將樣本對新增到訓練器。
訓練過程
使用單行程式碼呼叫訓練過程。確保所有需要的訓練樣本都已新增到訓練器。
擬合準備
首先,您需要載入測試檔案列表。
String testFiles(images_path), testPts(annotations_path);
if(!test_images_path.empty()){
testFiles = test_images_path;
testPts = test_images_path;
}
std::vector<String> images;
std::vector<String> facePoints;
由於 AAM 需要初始化引數(旋轉、平移和縮放),因此您需要宣告所需的變數來儲存這些資訊,這些資訊將使用自定義函式獲得。由於本例中 getInitialFitting() 函式的實現不是最優的,您可以建立自己的函式。
透過將訓練模型的基準形狀與當前人臉影像進行比較來獲得初始化。在本例中,旋轉是透過將輸入人臉影像中兩隻眼睛形成的線的角度與基準形狀中的同一條線進行比較來獲得的。同時,縮放是透過將輸入影像中眼睛之間的線的長度與基準形狀進行比較來獲得的。
擬合過程
擬合過程從檢測給定影像中的人臉開始。
myDetector(image, faces, &face_cascade);
如果找到至少一張臉,則下一步是計算初始化引數。在本例中,由於 getInitialFitting() 函式不是最優的,因此它可能無法從給定的人臉上找到一對眼睛。因此,我們將過濾掉沒有初始化引數的人臉,在本例中,conf 向量中的每個元素都表示每個過濾後的人臉的初始化引數。
std::vector<FacemarkAAM::Config> conf;
std::vector<Rect> faces_eyes;
for(unsigned j=0;j<faces.size();j++){
if(getInitialFitting(image,faces[j],s0,eyes_cascade, R,T,scale)){
faces_eyes.push_back(faces[j]);
}
}
對於儲存在 conf 向量中的擬合引數,最後一個引數表示擬合過程中將使用的縮放因子的 ID。在本例中,擬合將使用最大的縮放因子 (4),預計與其他縮放相比,它的計算時間最快。如果 ID 大於模型中可用的訓練比例,則使用具有最大比例 ID 的模型。
擬合過程非常簡單,您只需要放入對應的影像,表示給定影像中所有面部的 ROI 的 cv::Rect 向量,由 landmarks 變量表示的地標點容器,以及配置變數。
if(conf.size()>0){
printf(" - 發現帶有眼睛的面部 %i ", (int)conf.size());
std::vector<std::vector<Point2f> > landmarks;
facemark->fitConfig(image, faces_eyes, landmarks, conf);
for(unsigned j=0;j<landmarks.size();j++){
}
printf("%f ms\n",fittime*1000);
}else{
printf("初始化無法計算 - 跳過\n");
}
擬合過程完成後,您可以使用 drawFacemarks 函式視覺化結果。