📜  训练支持向量机以识别C++中的面部特征

📅  最后修改于: 2021-05-30 03:52:18             🧑  作者: Mango

让我们看看如何训练支持向量机的模型,保存训练后的模型并测试模型以使用OpenCV检查其预测准确性的百分比。

数据组织:

使用imagenetscraperautocrop ,我们收集来自网络,农作物面临数据,并将其调整到批量较小的尺寸。收集的数据需要进行有意义的组织,以便我们可以通过编程方式和手动方式进行访问。使用以下文件夹结构-

FFR_dataset/
|-- Age
|   |-- adult
|   |-- child
|   |-- old
|   |-- teen
|-- Emotion
|   |-- anger
|   |-- contempt
|   |-- happy
|   |-- neutral
|   |-- sad
|   |-- surprise
|-- Gender
    |-- female
    |-- male

我们在代码中使用相同的目录名称来访问它们,以训练保存预测识别结果。每个文件夹至少需要50张图像来训练模型,以获得良好的预测结果。训练更多图像可以改善结果,但不建议这样做,因为执行该过程会花费很多时间,并且无法带来明显的改善。

执行

通过使用官方opencv存储库中提供的样本来训练带有HOG的SVM train_HOG.cpp,我们实现了C++代码来训练,保存和预测具有多张面孔的图像上的面部特征。

共有三种功能类型:年龄情感性别。四个年龄段,六种情绪和两种性别类型。因此,实现了n类分类器以识别面部数据上的每个特征。

步骤#1:对于每种功能类型,即(年龄,情感或性别)循环遍历“ n”个运行时间。

// CTrainTestHOG::Run(int run_times)
for (auto ft : m_FeatureList) {
    DEBUGLW("\tFeature type=[%s]\n", ft.first.c_str());
  
    std::vector predictionAccuracyList;
    predictionAccuracyList.reserve(run_times);
  
    for (int run = 0; run < run_times; ++run) {
        DEBUGLW("\t\tRun=[%d]\n", run);
        vector trainData, predData;
        vector trainLabels, predLabels;
  
        this->get_ft_dataset(ft.first, trainData, predData,
                                  trainLabels, predLabels);
        // ... train, predict and measure the SVM model.
    }
}

步骤#2:在每次运行中,遍历要素类型中的要素值,并将图像获取到向量或数组中,即从“性别”->“男性”和“性别->”女性文件夹中获取所有图像。

// CTrainTestHOG::get_ft_dataset()
std::set& featureValueList = m_FeatureList.find(ft)->second;
  
for (auto fv : featureValueList) {
    DEBUGLW("\t\t\tFeature value=[%s]\n", fv.c_str());
    std::vector _trainData;
    std::vector _predData;
    std::vector _trainLabels;
    std::vector _predLabels;
  
    errCode = this->get_ftfv_dataset(ft, fv, _trainData, _predData,
                                       _trainLabels, _predLabels);
    if (errCode != EXIT_SUCCESS)
        break;
    trainData.insert(trainData.end(), _trainData.begin(), _trainData.end());
    predData.insert(predData.end(), _predData.begin(), _predData.end());
    trainLabels.insert(trainLabels.end(), _trainLabels.begin(), _trainLabels.end());
    predLabels.insert(predLabels.end(), _predLabels.begin(), _predLabels.end());
}

步骤3至6:

  • 将向量中的图像裁剪为脸部矩形,并使用新的脸部列表更新图像向量。
  • 在脸部列表中的每个图像上执行任何预处理任务,例如将尺寸调整为较小的尺寸(64、64)。
  • 随机将向量中经过预处理的人脸图像随机排列以引入随机输入数据。
  • 将数据集分为训练(80%)和预测(20%)数据。
//  CTrainTestHOG::get_ftfv_dataset()
std::vector imgList;
this->get_images(folderName, imgList);
this->get_cropped_faces(imgList);
this->get_preprocessed_faces(imgList);
  
//-- return on empty img list to prevent seg fault
if (imgList.empty()) {
    errCode = EXIT_FAILURE;
    DEBUGLE("Error img list is empty!\n");
    break;
}
DEBUGLD("\t\t\timgList.size()=[%ld]\n", imgList.size());
  
std::random_shuffle(imgList.begin(), imgList.end());
  
// 80% for training
int trainPart = imgList.size() * 0.8;
  
// 20% for predicting
int predPart = imgList.size() - trainPart;
DEBUGLD("\t\t\ttrainPart=[%d], predPart=[%d]\n", trainPart, predPart);
  
trainData.reserve(trainPart);
predData.reserve(predPart);
  
ft_t::iterator ft_iter = m_FeatureList.find(ft);
fv_t::iterator fv_iter = ft_iter->second.find(fv);
int label = std::distance(ft_iter->second.begin(),  fv_iter);
DEBUGLD("\t\t\tlabel=[%d]\n", label);
  
int i = 0;
for (; i < trainPart; ++i) {
    trainData.push_back(imgList.at(i));
    trainLabels.push_back(label);
}
DEBUGLD("\t\t\ti=[%d], trainData.size()=[%ld], 
        trainLabels.size()
        = [% ld]\n ", i, trainData.size(), 
                       trainLabels.size());
  
for (; i < imgList.size(); ++i) {
    predData.push_back(imgList.at(i));
    predLabels.push_back(label);
}
DEBUGLD("\t\t\ti=[%d], predData.size()=[%ld], 
        predLabels.size()
        = [% ld]\n ", i, predData.size(), predLabels.size());

步骤#7:为训练数据中的每个图像计算HOG。

// CTrainTestHOG::computeHOGs()
HOGDescriptor hog;
vector hogMats;
vector descriptors;
for (auto img : imgHogList) {
    hog.winSize = img.size() / 8 * 8;
    hog.compute(img, descriptors);
    cv::Mat descriptors_mat(Mat(descriptors).clone());
    hogMats.push_back(descriptors_mat);
}
imgHogList.swap(hogMats);

步骤#8:将训练数据向量转换为opencv Mat对象以训练SVM。

//  CTrainTestHOG::convert_to_ml()
for (size_t i = 0; i < train_samples.size(); ++i) {
    CV_Assert(train_samples[i].cols == 1 || train_samples[i].rows == 1);
    if (train_samples[i].cols == 1) {
        cv::transpose(train_samples[i], tmp);
        tmp.copyTo(trainData.row((int)i));
    }
    else if (train_samples[i].rows == 1) {
        train_samples[i].copyTo(trainData.row((int)i));
    }
}

步骤#9:将训练数据Mat对象与训练数据标签向量一起传递给svm train函数。

//  CTrainTestHOG::Run()
trainLabels.resize(ml_train_data.rows);
// train svm
DEBUGLW("\t\tTraining SVM - begin\n");
m_pSVM->train(ml_train_data, ROW_SAMPLE, trainLabels);
DEBUGLW("\t\tTraining SVM - end\n");

步骤#10:保存经过训练的模型。

//-- step 10, CTrainTestHOG::Run()
cv::String svmModelFileName = cv::format("%s/cv4_svm_%s_model.xml",
                                         getenv(FFR_DATASET_PATH),
                                         ft.first.c_str());
  
m_pSVM->save(svmModelFileName.c_str());
DEBUGLW("\t\tSaved SVM model=[%s]\n",
        svmModelFileName.c_str());

步骤#11:通过为每个预测图像计算HOG来预测模型,将预测数据集转换为opencv mat对象,并使用标签矢量调用svm预测来存储结果。

//-- step 11, CTrainTestHOG::Run()
// test the model
// compute HOG for each pre-processed face
errCode = this->computeHOGs(predData);
if (errCode != EXIT_SUCCESS) {
    DEBUGLE("Error in computing HOGs for the feature "
            "type=[%s]\n",
            ft.first.c_str());
    break;
}
  
// convert HOG feature vectors to SVM data
Mat ml_pred_data;
vector resultLabels;
errCode = this->convert_to_ml(predData, ml_pred_data);
if (errCode != EXIT_SUCCESS) {
    DEBUGLE("Error in converting to ml for the "
            "feature type=[%s]\n",
            ft.first.c_str());
    break;
}
predLabels.resize(ml_pred_data.rows);
// resultLabels.resize(ml_pred_data.rows);
// test svm
DEBUGLW("\t\tTesting SVM - begin\n");
Mat responses_mat;
m_pSVM->predict(ml_pred_data, responses_mat);
for (size_t i = 0; i < ml_pred_data.rows; ++i) {
    resultLabels.push_back(responses_mat.at(i));
}
DEBUGLW("\t\tTesting SVM - end\n");

步骤#12和#13:通过将预期的预测标签与预测的标签进行比较,计算其准确性的百分比。

//  CTrainTestHOG::Run()
// check the accuracy
float accuracy = 0.0f;
this->get_prediction_accuracy(predLabels, resultLabels, accuracy);
DEBUGLW("\t\tPrediction accuracy=[%lf]\n", accuracy);
predictionAccuracyList.push_back(accuracy);
  
//-- step 13, CTrainTestHOG::Run()
// check the mean accuracy of 'n' runs
float sum_of_accuracies = std::accumulate(
    predictionAccuracyList.begin(),
    predictionAccuracyList.end(), 0.0);
float mean_accuracy = sum_of_accuracies / predictionAccuracyList.size();
DEBUGLW("\t\tMean prediction accuracy=[%lf]\n",
        mean_accuracy);

使用以下命令行参数运行可执行文件。

./train_hog --test --in= --out= --show

输入:
哈利·波特样品输入图像

输出:
哈利·波特结果输出图像

使用OpenCV 2.4的HOG SVM的结果日志
使用OpenCV 4.0的HOG SVM的结果日志

注意:由于图片中所有三个人的头发都很长,因此将性别检测为“女性”,这是假阳性。在机器学习算法中,由于输入样本图像具有歧义特征,误报总是很常见。