目标
在本教程中,您将学习如何:
- 创建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
...
}
接下来,创建两个文本文件,分别包含图像文件列表和注释文件列表。确保两个文件中图像和注释的顺序匹配。此外,建议使用绝对路径而不是相对路径。在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倍缩放下训练两个模型(分别是原图的1/2和1/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
变量表示的标志点容器以及配置变量。
如果(conf.size()>0){
printf(" - 找到带有眼睛的脸部 %i ", (int)conf.size());
std::vector<std::vector<Point2f> > landmarks;
facemark->fitConfig(image, faces_eyes, landmarks, conf);
对于(unsigned j=0;j<landmarks.size();j++){
}
printf("%f ms\n",fittime*1000);
}否则{
printf("初始化无法计算 - 跳过\n");
}
拟合过程完成后,可以使用drawFacemarks
函数可视化结果。