Entrenar una máquina de vectores de soporte para reconocer rasgos faciales en C++

Veamos cómo entrenar un modelo de máquina de vectores de soporte, guardar el modelo entrenado y probar el modelo para verificar el porcentaje de su precisión de predicción usando OpenCV.

Organización de datos:

Usando imagenetscraper y autocrop , recopilamos datos de la web, recortamos rostros y los redimensionamos a tamaños más pequeños de forma masiva. Los datos recopilados deben organizarse de manera significativa para que podamos acceder a ellos de forma programática y manual. Use la siguiente estructura de carpetas-

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

Usamos los mismos nombres de directorio en el código para acceder a ellos para entrenar , guardar y predecir los resultados del reconocimiento. Se requiere un mínimo de 50 imágenes en cada carpeta para entrenar los modelos y obtener buenos resultados de predicción. Entrenar más imágenes puede mejorar los resultados, pero no se recomienda, ya que lleva mucho tiempo ejecutarlo y no brinda mejoras significativas.

Implementación

Al hacer uso de la muestra provista en el repositorio oficial de opencv para entrenar el SVM con HOG, train_HOG.cpp, implementamos el código c++ para entrenar, guardar y predecir las características faciales en una imagen con múltiples caras.

Hay tres tipos de características: edad , emoción y género . Cuatro grupos de edad, seis emociones y dos tipos de género. Por lo tanto , se implementa un clasificador de clase n para reconocer cada característica en los datos de una cara.

Paso #1: Para cada tipo de función, es decir (Edad, Emoción o Sexo), recorra los tiempos de ejecución ‘n’.

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

Paso n.º 2: en cada ejecución, itere a través de los valores de característica en el tipo de característica y obtenga las imágenes en un vector o array, es decir, obtenga todas las imágenes de las carpetas Género->Masculino y Género->Femenino.

// CTrainTestHOG::get_ft_dataset()
std::set<cv::String>& featureValueList = m_FeatureList.find(ft)->second;
  
for (auto fv : featureValueList) {
    DEBUGLW("\t\t\tFeature value=[%s]\n", fv.c_str());
    std::vector<cv::Mat> _trainData;
    std::vector<cv::Mat> _predData;
    std::vector<int> _trainLabels;
    std::vector<int> _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());
}

Paso #3 a #6:

  • Recorta las imágenes en el vector a los rectángulos de caras y actualiza el vector de imágenes con la nueva lista de caras.
  • Realice cualquier tarea de procesamiento previo, como cambiar el tamaño a un tamaño más pequeño (64, 64) en cada imagen en la lista de caras.
  • Mezcla aleatoriamente las imágenes de caras preprocesadas en vectores para introducir datos de entrada aleatorios.
  • Divida el conjunto de datos en datos de entrenamiento (80 %) y predicción (20 %).
//  CTrainTestHOG::get_ftfv_dataset()
std::vector<cv::Mat> 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());

Paso #7: Calcule HOG para cada imagen en los datos de entrenamiento.

// CTrainTestHOG::computeHOGs()
HOGDescriptor hog;
vector<Mat> hogMats;
vector<float> 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);

Paso #8: Convierta el vector de datos de entrenamiento en un objeto Mat de opencv para entrenar 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));
    }
}

Paso #9: Pase el objeto Mat de datos de entrenamiento a la función de entrenamiento svm junto con un vector de etiquetas para los datos de entrenamiento.

//  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");

Paso #10: Guarde el modelo entrenado.

//-- 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());

Paso #11: Prediga el modelo calculando el HOG para cada imagen de predicción, convierta el conjunto de datos de predicción en un objeto mat de opencv y llame a svm predict con un vector de etiquetas para almacenar el resultado.

//-- 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<int> 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<int>(i));
}
DEBUGLW("\t\tTesting SVM - end\n");

Paso 12 y 13: Calcule el porcentaje de su precisión comparando las etiquetas de predicción esperadas con las etiquetas predichas.

//  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);

Ejecute el ejecutable con los siguientes argumentos de la línea de comandos.

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

Aporte:
Imagen de entrada de muestra de Harry Potter

Producción:
Imagen de salida del resultado de Harry Potter

Registro de resultados para HOG SVM usando OpenCV 2.4
Registro de resultados para HOG SVM usando OpenCV 4.0

Nota: Debido al cabello largo de las tres personas en la imagen, se detecta el género como ‘femenino’, lo cual es un falso positivo. En los algoritmos de aprendizaje automático, los falsos positivos siempre son comunes dado que la imagen de muestra de entrada tiene características ambiguas.

Publicación traducida automáticamente

Artículo escrito por manid2 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *