【基于GBDT的人脸关键点检测器训练】级联回归树——QT(C++)+Linux

关于该训练的详细信息可参考cvpr2014顶刊:
One Millisecond Face Alignment with an Ensemble of Regression Trees

一.总体流程

GBDT(Gradient Boosted Decision Trees)是一种集成学习方法,主要用于回归和分类任务。它由多棵决策树组成,每棵树都是基于上一棵树的残差(或梯度)进行训练得到的。
GBDT 在面部关键点检测中的应用:
在面部关键点检测中,GBDT 可以用于回归问题,预测面部关键点的坐标。每个关键点的坐标可以看作是一个回归目标。通过训练 GBDT 模型,我们可以预测面部关键点的位置。

1.准备数据集:收集包含标记人脸的图像数据集,并准备相应的标记文件。标记文件可以是 XML、JSON 或者其他格式,用于描述每张图像中人脸的位置和边界框。

2.加载数据集:在 C++ 代码中编写函数来加载数据集。需要编写代码来读取图像文件和相应的标记文件,并将它们转换成 Dlib 库所支持的数据结构。

3.定义训练参数:定义训练人脸检测模型的参数,比如训练迭代次数、学习率等。根据需要调整这些参数。

4.创建训练器:使用 dlib 库提供的函数来创建一个人脸检测模型的训练器。可以选择不同的训练器,比如支持向量机(SVM)训练器或者其他机器学习(GBRT)训练器,根据需求选择合适的训练器。Dlib提供了shape_predictor_trainer用于人脸面部关键点检测器的训练

5.训练模型:使用准备好的数据集和训练器来训练人脸检测模型。调用训练器的训练函数,并传递数据集和训练参数作为参数。

6.评估模型:一旦训练完成,可以使用一些测试数据来评估模型的性能。使用测试数据集调用模型的评估函数,并分析评估结果。以避免过拟合,提高模型的鲁棒性。

7.调优:根据评估结果,可能需要调整训练参数、数据集或者模型结构来提高模型的性能。然后重新训练模型并进行评估。

8.使用模型:模型训练达到使用需求,就可以将其集成到 Qt C++ 应用中,并使用它来进行人脸检测。

二.具体实现-Ubuntu(QT C++)

1.准备数据集

了解iBUG 300-W数据集
iBUG 300-W 是一个常用的人脸关键点检测数据集,它包含了大量的人脸图像以及每张图像中的人脸关键点标注。链接

  1. 下载数据集解压(7zip)至对应文件夹下
    在这里插入图片描述

  2. 这里还可以自己对数据打标记( LabelImg 工具标记数据)

  3. 再次附上我们的人脸68位关键点示意图
    在这里插入图片描述 在这里插入图片描述

2.常见训练参数

在训练人脸关键点检测器时,可以调整的一些训练参数包括迭代次数、正则化参数、训练步长等。下面是一些常见的训练参数及其含义:

1.迭代次数(Max Iterations):
这是指训练算法运行的最大迭代次数。增加迭代次数通常可以提高模型的性能,但也会增加训练时间。
如果迭代次数设置得太小,可能会导致模型欠拟合;如果设置得太大,可能会导致过拟合。

2.正则化参数(Regularization Parameter):
正则化参数控制着模型的复杂度,帮助防止过拟合。它通常用于控制模型的平滑度。
增加正则化参数可以降低模型的复杂度,从而减少过拟合的风险;减少正则化参数则可以提高模型的拟合能力,但也可能导致过拟合。

3.训练步长(Learning Rate):
训练步长控制着参数更新的速度。较小的训练步长通常会使得模型收敛更加稳定,但会增加训练时间。
通常情况下,可以使用默认的学习率,并根据需要微调。

4.批量大小(Batch Size):
批量大小指每次迭代中用于训练的样本数量。增大批量大小可以提高训练的效率,但也会增加内存和计算资源的需求。
较小的批量大小可能导致训练过程更加嘈杂,但有助于模型更快地收敛。

5.线程数(Number of Threads):
线程数指用于训练的线程数量。增加线程数可以加快训练速度,但也会增加系统资源的消耗。
通常情况下,可以根据系统的硬件配置选择合适的线程数。

6.终止条件(Termination Criteria):
终止条件用于控制训练过程的停止条件,例如达到最大迭代次数、损失函数的收敛等。
设置合适的终止条件可以避免训练过度,并在达到一定的训练效果后停止训练。

3.Dlib训练参数详解

Dlibshape_predictor_trainer类用于训练形状预测器,通常用于面部关键点定位(例如,定位眼睛、鼻子、嘴巴等的位置)。这个训练器主要使用的是一种回归树的方法,具体是基于梯度提升回归树
Gradient Boosting Regression Trees,GBRT)的算法。

在训练过程中,shape_predictor_trainer会学习如何将图像中的像素值映射到形状点的位置。它首先会初始化一个弱预测器(通常是基于图像特征和形状点位置之间的简单关系),然后迭代地构建更多的回归树来逐步改进这个预测。每棵树都会尝试纠正之前所有树的预测错误,从而形成一个强大的集成预测器。

这种基于梯度提升的方法有几个优点:

准确性:通过构建多棵回归树并集成它们的预测结果,可以得到比单棵树更准确的预测。
效率:在预测时,可以并行地计算每棵树的输出,从而提高了整体预测的速度。
灵活性:回归树可以处理各种类型的特征,包括数值特征和分类特征,因此适用于各种不同的形状预测任务。
当使用shape_predictor_trainer训练形状预测器时,通常需要提供一组带有标注的图像(300W所提供的人脸68位关键点标注pts文件),其中每个图像都包含了一组形状点的位置信息。训练器会学习如何根据图像的像素值来预测这些形状点的位置。训练完成后,可以使用训练得到的形状预测器来对新的图像进行形状预测。

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

1. set_nu(double nu):
作用: 设置正则项。
解释: 正则项,nu越大,表示对训练样本fit越好,当然也越有可能发生过拟合。_nu取值范围(0,1],默认取0.1。

2. be_quiet():
作用: 设置训练器为“静默”模式,禁用输出。
解释: 当调用 be_quiet() 函数时,将禁用所有进度条、日志和其他输出,使得训练过程更加安静。这在进行大规模训练或者在后台运行训练时特别有用。

3. be_verbose():
作用: 设置训练器为“详细”模式,启用输出。
解释: 调用 be_verbose() 函数会启用所有的输出信息,允许查看详细的训练进度和模型评估信息。这对于想要了解训练过程中发生的情况的用户来说是很有用的。

4. set_lambda(double lambda):
• 作用:用于设置控制回归树分裂的像素对(pixel pairs)的阈值。取值(0,1)。
• 解释:回归树中是否分裂节点是通过计算pixel pairs的强度差是否满足阈值来决定的,如果所选的pixel pairs强度大于阈值,则表示回归树需要进一步分裂。pixel pairs是在特征池中随机采样得到的。lambda值越大表示不太在意是否选择邻近的 pixel pairs,值越小倾向于选择更邻近的pixel。

5. set_tree_depth(unsigned long depth):
• 作用:设置决策树的最大深度。
• 解释:决策树的最大深度控制了树的复杂度,较大的深度允许树更加复杂,而较小的深度则限制了树的复杂度。通过设置合适的最大深度,可以控制模型的复杂度,防止过拟合。

6. set_num_threads(unsigned long num_threads):
• 作用:设置用于训练的线程数。
• 解释:多线程训练可以加速训练过程,充分利用计算资源。通过设置合适的线程数,可以提高训练效率。

7. set_random_seed(long seed):
• 作用:设置随机数种子。
• 解释:随机数种子用于初始化随机数生成器,确定了随机数的序列。相同的种子会产生相同的随机数序列,因此设置种子可以使得训练过程具有可重复性。

8. set_padding_mode(shape_predictor_trainer::padding_mode mode):
• 作用:设置填充模式。
• 解释:填充模式用于处理图像边界问题,以确保特征提取和训练过程的稳定性。不同的填充模式可能会影响模型的性能和训练速度。

9. set_cascade_depth(unsigned long depth):
• 作用:设置级联深度。
• 解释:级联深度指的是级联训练中使用的级数。增加级联深度可以提高模型的性能,但也会增加训练时间。
在这里插入图片描述

10. set_num_test_splits(unsigned long num):
• 作用:设置测试分裂点数量。
• 解释:测试分裂点数量用于指定在级联训练中每个级别的测试分裂点数量。增加分裂点数量可以提高模型的灵活性和鲁棒性。

11. set_feature_pool_size(unsigned long size):
• 作用:设置特征池大小。
• 解释:特征池大小指的是在级联训练中用于选择特征的候选特征数量。增加特征池大小可以增加模型的表达能力,但也可能增加训练时间。

12. set_oversampling_amount(unsigned long amount):
• 作用:设置过采样数量。
• 解释:过采样数量用于生成额外样本,增加样本的多样性,有助于提高模型的泛化能力。

13. set_feature_pool_region_padding(double padding):
• 作用:设置特征池区域填充。
• 解释:特征池区域填充用于填充图像边界,在选择特征时防止受到图像边界的影响。

14. set_num_trees_per_cascade_level(unsigned long num):
• 作用:设置每个级联级别的树数量。
• 解释:增加树的数量可以增加模型的复杂度和表达能力。

15. set_oversampling_translation_jitter(double amount):
• 作用:设置过采样平移抖动量。
• 解释:过采样平移抖动量用于增加样本的多样性,有助于提高模型的泛化能力。

4.模型训练(PTS)

1. 交叉验证

交叉验证是一种常用的机器学习技术,用于评估模型的性能和泛化能力。其基本思想是将数据集划分为多个子集,在每个子集上轮流进行训练和验证,以评估模型在未见过的数据上的表现。
具体步骤如下:

1.数据集划分: 将数据集分为K个近似大小的子集,通常采用随机抽样的方式。
2.训练和验证: 对于每个子集,将其作为验证集,其余K-1个子集作为训练集,训练模型并在验证集上评估性能。
3.性能评估: 对每次验证的结果进行性能评估,通常使用准确率、精确度、召回率、F1分数等指标。
4.模型选择: 根据交叉验证的结果选择最佳的模型参数或模型架构。
5.模型评估: 使用选定的模型在测试集上进行最终评估,评估模型在未见过的数据上的泛化能力。

交叉验证的主要优点包括:

1.充分利用数据:通过多次训练和验证,充分利用了数据集中的信息。
2.准确评估模型:由于每个样本都被用于训练和验证,因此评估结果更加可靠。
3.模型选择:可以根据交叉验证的结果选择最佳的模型参数或模型架构,提高模型的性能。

2. 数据集划分
在这里插入图片描述划定n个图片到测试集以评估性能
PNG(人脸图片)+PTS(人脸关键点标注文件)
(pts格式如下,包含人脸68位关键点标注)

version: 1
n_points: 68
{
446.000 91.000
449.459 119.344
450.957 150.614
460.552 176.986
471.486 202.157
488.087 226.842
506.016 246.438
524.662 263.865
553.315 271.435
578.732 266.260
599.361 248.966
615.947 220.651
627.439 197.999
635.375 179.064
642.063 156.371
647.302 124.753
646.518 92.944
470.271 117.870
486.218 109.415
503.097 114.454
519.714 120.090
533.680 127.609
571.937 123.590
585.702 117.155
602.344 109.070
620.077 103.951
633.964 111.236
554.931 145.072
554.589 161.106
554.658 177.570
554.777 194.295
532.717 197.930
543.637 202.841
555.652 205.483
565.441 202.069
576.368 197.061
487.474 136.436
499.184 132.337
513.781 133.589
527.594 143.047
513.422 144.769
499.117 144.737
579.876 140.815
590.901 130.008
605.648 128.376
618.343 132.671
606.771 140.525
593.466 141.419
519.040 229.040
536.292 221.978
547.001 221.192
557.161 224.381
568.172 219.826
579.144 222.233
589.098 224.410
581.071 239.804
570.103 251.962
558.241 254.844
547.661 254.621
534.085 247.772
524.758 230.477
547.684 231.663
557.304 230.805
568.172 229.159
585.417 225.992
569.211 237.777
557.473 240.542
547.989 240.014
}

3. 训练程序:

INCLUDEPATH += /usr/local/include /path/to/libjpeg/include /path/to/libpng/include
LIBS += /usr/local/lib/libopencv_* -ldlib -L/path/to/libjpeg/lib -ljpeg -L/path/to/libpng/lib -lpng
#include "widget.h"
#include "ui_widget.h"
#include <dlib/dir_nav.h>
#include <dlib/image_processing.h>
#include <dlib/image_io.h>
#include <iostream>

using namespace dlib;
using namespace std;

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    try {
        // 数据集文件夹路径
        std::string datasetPath = "/home/tj/face_database/300W/";

        // 读取训练集和测试集的文件列表
        directory trainDatasetDir(datasetPath + "train_faces");
        std::vector<file> trainFiles = trainDatasetDir.get_files();

        directory testDatasetDir(datasetPath + "test_faces");
        std::vector<file> testFiles = testDatasetDir.get_files();

        // 用于存储训练集图像和对应的关键点标注
        std::vector<matrix<rgb_pixel>> trainImages;
        std::vector<std::vector<full_object_detection>> trainFaceLandmarks;

        // 用于存储测试集图像和对应的关键点标注
        std::vector<matrix<rgb_pixel>> testImages;
        std::vector<std::vector<full_object_detection>> testFaceLandmarks;

        // 遍历训练集文件列表
        for (const auto& file : trainFiles) {
            if (file.name().find(".png") == std::string::npos)
                continue;

            // 读取图像文件
            matrix<rgb_pixel> img;
            load_image(img, file.full_name());

            // 读取对应的关键点标注文件
            std::string landmarkPath = file.full_name();
            landmarkPath.replace(landmarkPath.find(".png"), 4, ".pts");
            std::ifstream ifs(landmarkPath);
            std::vector<point> landmarks;
            if (ifs.is_open()) {
                // 跳过文件头
                std::string line;
                std::getline(ifs, line); // 版本行
                std::getline(ifs, line); // 关键点数量行
                int numLandmarks = std::stoi(line.substr(line.find(":") + 1)); // 解析关键点数量
//                 cout << "图像 " << file.name() << " 中的预期关键点数量为:" << 68 << ",实际读取到的数量为:" << numLandmarks << endl; //关键点总数
                // 读取关键点
                // 跳过大括号之前的内容
                std::string temp;
                std::getline(ifs, temp, '{');
                for (int i = 0; i < numLandmarks; ++i) {
                    point pt;
                    // 读取关键点坐标
                    double x, y;
                    if (!(ifs >> x >> y)) {
                        cerr << "无法读取关键点坐标。" << endl;
                        break;
                    }
                    pt.x() = x;
                    pt.y() = y;
                    landmarks.push_back(pt);
//                     cout << "关键点 " << i << " 的坐标为:(" << pt.x() << ", " << pt.y() << ")" << endl; // 输出读取到的关键点

                }

                ifs.close();

                // 创建一个包含图像大小的矩形
                rectangle rect(0, 0, img.nc() - 1, img.nr() - 1);
                // 使用 dlib 提供的函数将关键点和矩形位置信息转换为完整的对象检测数据
                std::vector<full_object_detection> detections;
                detections.push_back(full_object_detection(rect, landmarks));

                // 将图像和关键点添加到 vectors 中
                trainImages.push_back(img);
                trainFaceLandmarks.push_back(detections);
            } else {
                cerr << "无法打开关键点标注文件:" << landmarkPath << endl;
            }
               traincount++;
        }
        cout << "读取到有效训练图片:"<<traincount<<endl;

        // 遍历测试集文件列表
        for (const auto& file : testFiles) {
            if (file.name().find(".png") == std::string::npos)
                continue;

            // 读取图像文件
            matrix<rgb_pixel> img;
            load_image(img, file.full_name());

            // 读取对应的关键点标注文件
            std::string landmarkPath = file.full_name();
            landmarkPath.replace(landmarkPath.find(".png"), 4, ".pts");
            std::ifstream ifs(landmarkPath);
            std::vector<point> landmarks;
            if (ifs.is_open()) {
                // 跳过文件头
                std::string line;
                std::getline(ifs, line); // 版本行
                std::getline(ifs, line); // 关键点数量行
                int numLandmarks = std::stoi(line.substr(line.find(":") + 1)); // 解析关键点数量
//                cout << "图像 " << file.name() << " 中的预期关键点数量为:" << 68 << ",实际读取到的数量为:" << numLandmarks << endl; //关键点总数
                // 读取关键点
                // 跳过大括号之前的内容
                std::string temp;
                std::getline(ifs, temp, '{');
                for (int i = 0; i < numLandmarks; ++i) {
                    point pt;
                    // 读取关键点坐标
                    double x, y;
                    if (!(ifs >> x >> y)) {
                        cerr << "无法读取关键点坐标。" << endl;
                        break;
                    }
                    pt.x() = x;
                    pt.y() = y;
                    landmarks.push_back(pt);
//                      cout << "关键点 " << i << " 的坐标为:(" << pt.x() << ", " << pt.y() << ")" << endl; // 输出读取到的关键点

                }

                ifs.close();

                // 创建一个包含图像大小的矩形
                rectangle rect(0, 0, img.nc() - 1, img.nr() - 1);
                // 使用 dlib 提供的函数将关键点和矩形位置信息转换为完整的对象检测数据
                std::vector<full_object_detection> detections;
                detections.push_back(full_object_detection(rect, landmarks));

                // 将图像和关键点添加到 vectors 中
                testImages.push_back(img);
                testFaceLandmarks.push_back(detections);
            } else {
                cerr << "无法打开关键点标注文件:" << landmarkPath << endl;
            }
            testcount++;
        }
             cout << "读取到有效测试图片:"<<testcount<<endl;

        // 训练人脸关键点检测器
        shape_predictor_trainer trainer;
        // 设置训练参数
        trainer.set_num_threads(6); // 设置线程数
        trainer.set_nu(0.05);       //设置正则值
        trainer.set_tree_depth(4);   //设置决策树的最大深度。
        trainer.set_cascade_depth(10); // 设置级联深度
//        trainer.set_num_test_splits(100); // 设置测试分裂点数量
//        trainer.set_feature_pool_size(400); // 设置特征池大小
        trainer.set_oversampling_amount(300); // 设置过采样数量
//        trainer.set_num_trees_per_cascade_level(500); // 设置每个级联级别的树数量

        // 开始训练
        shape_predictor sp;
        trainer.be_verbose(); // 设置为详细模式,以输出训练进度
        sp = trainer.train(trainImages, trainFaceLandmarks);

        // 输出在测试集上的性能指标
        cout << "在测试集上的性能指标:" << endl;
        cout << "平均欧氏距离:" << test_shape_predictor(sp, testImages, testFaceLandmarks) << endl;

        // 将训练好的模型保存到文件
        serialize("/home/tj/model/trained_model.dat") << sp;

        cout << "训练完成!" << endl;

    }
    catch (std::exception& e) {
        cerr << "发生异常: " << e.what() << endl;
    }

}

Widget::~Widget()
{
    delete ui;
}



在这里插入图片描述

平均欧氏距离 是用于评估人脸关键点检测器性能的一种指标。它表示在交叉验证中对每个测试样本的预测关键点与真实关键点之间的平均欧氏距离。欧氏距离是指在n维空间中两点之间的距离,通常用于衡量两个点之间的相似程度或差异程度。
在人脸关键点检测中,欧氏距离用于衡量预测的关键点与真实关键点之间的差异。如果预测的关键点与真实关键点之间的平均欧氏距离较小,则说明检测器的性能较好,预测结果与真实情况较接近;反之,如果平均欧氏距离较大,则说明检测器的性能较差,预测结果与真实情况有较大的偏差。

这里我们就简单完成了自己的模型的训练,可以设置多次训练提升模型准确性
训练次数与模型效果之间的关系取决于许多因素,包括数据质量、模型复杂度、参数设置等。一般来说,增加训练次数可能会提高模型的性能,但并不是简单地增加训练次数就能够得到更好的结果。

5.模型训练(XML)

相比PTS解析,Dlib官方提供了便捷的XML解析工具(函数)用于训练模型。

这是在我查看相关文献以及Dlib官方所提供的的文档后,又选择的其他大数据集进行的训练,经过验证,效果要远远大于上述数据集训练的模型(与官方预载模型相差不大)。
数据集下载链接

  1. XML记录的人脸关键点信息如下图所示(00等表示人脸68位关键点的具体坐标):
    在这里插入图片描述

  2. 同样需要划分训练集和测试集
    在这里插入图片描述

  3. 代码:

#include "widget.h"
#include "ui_widget.h"


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    try {
        // 数据集目录路径
        const std::string faces_directory = "/home/tj/face_database/ibug_300W";
        dlib::array<array2d<unsigned char> > images_train, images_test;
        std::vector<std::vector<full_object_detection> > faces_train, faces_test;

        // 加载训练和测试数据集
        load_image_dataset(images_train, faces_train, faces_directory + "/labels_ibug_300W_train.xml");
        load_image_dataset(images_test, faces_test, faces_directory + "/labels_ibug_300W_test.xml");
        // 初始化训练器
        shape_predictor_trainer trainer;

        // 设置训练参数
        trainer.set_num_threads(4);       // 设置线程数
        trainer.set_nu(0.1);             // 设置正则值
        trainer.set_tree_depth(4);        // 设置决策树的最大深度。
        trainer.set_cascade_depth(15);    // 设置级联深度
        trainer.set_oversampling_amount(5); // 设置过采样数量
        trainer.set_feature_pool_size(400); // 设置特征池大小

        // 设置为详细模式,以输出训练进度
        trainer.be_verbose();
        // 开始训练
        shape_predictor sp = trainer.train(images_train, faces_train);

        // 打印训练和测试的平均误差
        cout << "训练平均误差: " <<
                test_shape_predictor(sp, images_train, faces_train, get_interocular_distances(faces_train)) << endl;

        cout << "测试平均误差:  " <<
                test_shape_predictor(sp, images_test, faces_test, get_interocular_distances(faces_test)) << endl;

        // 保存模型
        serialize("sp.dat") << sp;
    }

    catch (exception& e)
    {
        cout << "\n异常抛出!" << endl;
        cout << e.what() << endl;
    }
}

Widget::~Widget()
{
    delete ui;
}

// 计算两眼之间的距离
double Widget::interocular_distance(const full_object_detection &det)
{
    dlib::vector<double, 2> l, r;
    double cnt = 0;
    // 计算左眼的平均位置
    for (unsigned long i = 36; i <= 41; ++i)
    {
        l += det.part(i);
        ++cnt;
    }
    l /= cnt;

    cnt = 0;
    // 计算右眼的平均位置
    for (unsigned long i = 42; i <= 47; ++i)
    {
        r += det.part(i);
        ++cnt;
    }
    r /= cnt;
    // 计算两眼之间的距离
    return length(l - r);
}

// 获取所有对象的两眼之间的距离
std::vector<std::vector<double> > Widget::get_interocular_distances(const std::vector<std::vector<full_object_detection> > &objects)
{
    std::vector<std::vector<double> > temp(objects.size());
    // 遍历所有对象
    for (unsigned long i = 0; i < objects.size(); ++i)
    {
        for (unsigned long j = 0; j < objects[i].size(); ++j)
        {
            // 计算当前对象的两眼之间的距离
            temp[i].push_back(interocular_distance(objects[i][j]));
        }
    }
    return temp;
}


这里我对代码进行了详细的注释,并使用模型检验的两眼间距与xml文件标注的两眼间距进行对比测试误差具体流程如下:
1.计算眼间距:首先定义了一个函数interocular_distance来计算给定面部检测对象(full_object_detection)中眼睛之间的距离。这里使用了眼睛的若干关键点来计算眼间距离。
2.计算所有对象的眼间距:接着,使用get_interocular_distances函数计算所有面部检测对象的眼间距。这个函数会遍历每一个面部检测对象,并调用上面定义的interocular_distance函数。
3.测试误差:最后,在训练完成后,调用了test_shape_predictor函数来评估模型在训练集和测试集上的性能。

通过比较模型预测的关键点与真实关键点之间的眼间距误差,可以评估模型的准确性和鲁棒性。如果眼间距误差较小,表示模型预测的关键点位置与真实位置更为接近,模型的性能也更好。

load_image_dataset函数是dlib库中data_io.h头文件中的一个函数,用于从XML文件中加载图像和对象位置(人脸关键点)数据集。函数原型如下:

std::vector<std::vector<rectangle>> load_image_dataset (
    array_type& images,
    std::vector<std::vector<full_object_detection>>& object_locations,
    const std::string& filename
);
//        images:用于存储加载的图像的数组。
//        object_locations:用于存储图像中对象(如人脸)的位置或关键点。
//        filename:包含图像和对象位置信息的XML文件的路径。

6.模型验证

  1. 我们先查看自己训练的模型文件
    在这里插入图片描述
  2. 模型验证程序
INCLUDEPATH += /usr/local/include /path/to/libjpeg/include /path/to/libpng/include
LIBS += /usr/local/lib/libopencv_* -ldlib -L/path/to/libjpeg/lib -ljpeg -L/path/to/libpng/lib -lpng
#include "widget.h"
#include "ui_widget.h"
#include <QWidget>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing.h>
#include <dlib/image_io.h>
#include <dlib/opencv.h>
#include <opencv2/opencv.hpp>
using namespace dlib;
using namespace cv;
using namespace std;
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    try {
          // 加载训练好的模型
          dlib::shape_predictor sp;
          dlib::deserialize("/home/tj/model/trained_model.dat") >> sp;

          // 加载测试图像
          cv::Mat test_image = cv::imread("/home/tj/face_database/300W/01_Indoor/indoor_002.png", cv::IMREAD_COLOR);
          dlib::cv_image<dlib::bgr_pixel> dlib_img(test_image);

          // 进行人脸检测
          dlib::frontal_face_detector detector = dlib::get_frontal_face_detector();
          std::vector<dlib::rectangle> dets = detector(dlib_img);

          // 获取人脸关键点
          dlib::full_object_detection shape = sp(dlib_img, dets[0]);

          cout <<"关键点个数为:"<<shape.num_parts()<<endl;

          // 在图像上绘制关键点
          for (unsigned long i = 0; i < shape.num_parts(); ++i) {
              cout << "绘制第 " << i << " 个关键点" << endl;
              cout << "关键点 " << i << " 的坐标为:(" << shape.part(i).x() << ", " << shape.part(i).y() << ")" << endl;
              cv::circle(test_image, cv::Point(shape.part(i).x(), shape.part(i).y()), 3, cv::Scalar(255, 0, 0), -1);
          }


          // 显示图像
          cv::imshow("Result", test_image);
          cv::waitKey(0);

      }
       catch (std::exception& e) {
          std::cerr << "发生异常: " << e.what() << std::endl;
      }
}

Widget::~Widget()
{
    delete ui;
}

在这里插入图片描述

效果一般,这里只是验证一下,可以调整参数(根据自己的时间和硬件资源,可以用NVIDIA的GPU-CUDA加速,不过我不太了解),以提升模型的鲁棒性和防止过拟合等。

下面是使用xml数据集训练的模型效果
在这里插入图片描述

效果很好!

三.模型调优记录

官方:shape_predictor_68_face_landmarks.dat
训练平均误差: 0.0365915
测试平均误差: 0.0356526
模型大小:99.7MB

1.第一次

数据集train_xml,用时约1h(8CPU 12G内存 8Gswap)

        trainer.set_num_threads(4);       // 设置线程数
        trainer.set_nu(0.1);             // 设置正则值
        trainer.set_tree_depth(4);        // 设置决策树的最大深度。
        trainer.set_cascade_depth(15);    // 设置级联深度
        trainer.set_oversampling_amount(5); // 设置过采样数量
        trainer.set_feature_pool_size(400); // 设置特征池大小

训练平均误差: 0.03
测试平均误差: 0.058
模型大小:99.6MB

2.第二次

数据集train_xml,用时约1.3h(8CPU 12G内存 8Gswap)

        trainer.set_num_threads(6);       // 设置线程数
        trainer.set_nu(0.05);             // 设置正则值
        trainer.set_tree_depth(5);        // 设置决策树的最大深度。
        trainer.set_cascade_depth(10);    // 设置级联深度
        trainer.set_oversampling_amount(10); // 设置过采样数量
        trainer.set_feature_pool_size(720); // 设置特征池大小

训练平均误差: 0.042
测试平均误差: 0.059
模型大小:132.9MB

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值