Нормальный Байесовский классификатор в OpenCV

Байесовский классификатор является самым простым методом обучения и часто достаточным при правильном использовании – при правильном выборе признаков распознавания.

Классическая задача Байесовского классификатора – это определение наиболее вероятного образа из двух возможных. Возьмем два образа нормального распределения (здесь и далее я не вдаюсь в подробности, предполагая, что читатель знает об одномерном распознавании образов).

Образ1: мат. ожидание = 10, среднее квадратичное отклонение = 1

Образ2: мат. ожидание = 12, среднее квадратичное отклонение = 1

Если построить для них распределения, то они будут выглядеть следующим образом:

im1

Область пересечения распределений – это вероятность ошибки при классификации. Пример исходного кода Байесовского классификатора ниже:

#include <opencv2/imgproc/imgproc.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/core/core.hpp>

#include <opencv2/ml/ml.hpp>

 

using namespace cv;

 

const int Num_TrainingData = 1000;

const int Num_TestData = 2000;

 

 

int main()

{

       Mat trainingData1( Num_TrainingData, 1, CV_32FC1 );

       Mat trainingData2( Num_TrainingData, 1, CV_32FC1 );

       Mat testData( Num_TestData, 1, CV_32FC1 );

       Mat testClass( Num_TestData, 1, CV_32FC1 );

 

       // Заполнение обучаемых и тестовых данных     

       randn( trainingData1, 10, 1 );

       randn( trainingData2, 12, 1 );

 

       Mat trainingData( Num_TrainingData * 2, 1, CV_32FC1 );

       Mat trainingClass( Num_TrainingData * 2, 1, CV_32FC1 );

 

       for( int i = 0; i < Num_TrainingData; i++ )

       {

             trainingData.at<float>(i) = trainingData1.at<float>(i);

             trainingData.at<float>(i+Num_TrainingData) = trainingData2.at<float>(i);

             trainingClass.at<float>(i) = 0;

             trainingClass.at<float>(i+Num_TrainingData) = 1;

       }

 

       randn( trainingData1, 10, 1 );

       randn( trainingData2, 12, 1 );

 

       for( int i = 0; i < Num_TestData; i++ )

       {

             if ( i % 2 == 0 )

             {

                    testData.at<float>(i) = trainingData1.at<float>(i/2);

                    testClass.at<float>(i) = 0;

             }

             else

             {                  

                    testData.at<float>(i) = trainingData2.at<float>(i/2);

                    testClass.at<float>(i) = 1;

             }     

       }

 

       // Обучение нормального Байесовского классификатора

       CvNormalBayesClassifier bayes( trainingData, trainingClass );

 

       // Распознавание

       Mat predicted( testClass.rows, 1, CV_32F);

       int all = 0;

       for (int i = 0; i < testData.rows; i++) {

             Mat sample = testData.row( i );

             predicted.at<float>(i) = bayes.predict( sample );

             if ( predicted.at<float>(i) == testClass.at<float>(i) ) all++;

    }

 

       printf( "%.3f\n", (float) all / testData.rows );

 

       return 0;

}

Первоначально заполняются тестовые и тренировочные данные с помощь генератора случайных чисел нормального распределения, который есть в OpenCV – это randn

void randn(     
        InputOutputArray dst, 
        InputArray mean, 
        InputArray stddev
);

Параметры:

  • dst – массив, который будет заполняться случайными данными;
  • mean – математическое ожидание;
  • stddev – среднее квадратичное отклонение.

Обучение нормального Байесовского классификатора производится путем вызова конструктора класса CvNormalBayesClassifier с передачей признаков для обучения trainingData и указания того, к какому образу относится этот признак – trainingClass. Распознавание – это вызов метода класса CvNormalBayesClassifier predict. Программа выдает результат: 0.851 – вероятность правильного решения. Можно поэкспериментировать, передавая другие данные мат. ожидания и среднего квадратичного отклонения, например,

Образ1: мат. ожидание = 10, среднее квадратичное отклонение = 1

Образ2: мат. ожидание = 11, среднее квадратичное отклонение = 1

Результат: 0.707

На практике часто необходимо передавать не один признак в нормальный Байесовский классификатор, а несколько. Ниже приведен пример, в котором для упрощения использовано 2 признака, которые во многих частях программы добавились для быстроты написания простым копированием.

#include <opencv2/imgproc/imgproc.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/core/core.hpp>

#include <opencv2/ml/ml.hpp>

 

using namespace cv;

 

const int Num_TrainingData = 1000;

const int Num_TestData = 2000;

 

 

int main()

{

       Mat trainingData1( Num_TrainingData, 1, CV_32FC1 );

       Mat trainingData2( Num_TrainingData, 1, CV_32FC1 );

       Mat trainingData1a( Num_TrainingData, 1, CV_32FC1 );

       Mat trainingData2a( Num_TrainingData, 1, CV_32FC1 );

       Mat testData( Num_TestData, 2, CV_32FC1 );

       Mat testClass( Num_TestData, 1, CV_32FC1 );

 

       // Заполнение обучаемых и тестовых данных     

       randn( trainingData1, 10, 1 );

       randn( trainingData2, 11, 1 );

       randn( trainingData1a, 10, 1 );

       randn( trainingData2a, 12, 1 );

 

       Mat trainingData( Num_TrainingData * 2, 2, CV_32FC1 );

       Mat trainingClass( Num_TrainingData * 2, 1, CV_32FC1 );

 

       for( int i = 0; i < Num_TrainingData; i++ )

       {

             trainingData.at<float>(i,0) = trainingData1.at<float>(i);

             trainingData.at<float>(i+Num_TrainingData,0) = trainingData2.at<float>(i);            

             trainingData.at<float>(i,1) = trainingData1a.at<float>(i);

             trainingData.at<float>(i+Num_TrainingData,1) = trainingData2a.at<float>(i);           

             trainingClass.at<float>(i) = 0;

             trainingClass.at<float>(i+Num_TrainingData) = 1;

       }

 

       randn( trainingData1, 10, 1 );

       randn( trainingData2, 11, 1 );

       randn( trainingData1a, 10, 1 );

       randn( trainingData2a, 12, 1 );

 

       for( int i = 0; i < Num_TestData; i++ )

       {

             if ( i % 2 == 0 )

             {

                    testData.at<float>(i,0) = trainingData1.at<float>(i/2);

                    testData.at<float>(i,1) = trainingData1a.at<float>(i/2);

                    testClass.at<float>(i) = 0;

             }

             else

             {                  

                    testData.at<float>(i,0) = trainingData2.at<float>(i/2);

                    testData.at<float>(i,1) = trainingData2a.at<float>(i/2);

                    testClass.at<float>(i) = 1;

             }     

       }

 

       // Обучение нормального Байесовского классификатора

       CvNormalBayesClassifier bayes( trainingData, trainingClass );

 

       // Распознавание

       Mat predicted( testClass.rows, 1, CV_32F);

       int all = 0;

       for (int i = 0; i < testData.rows; i++) {

             Mat sample = testData.row( i );

             predicted.at<float>(i) = bayes.predict( sample );

             if ( predicted.at<float>(i) == testClass.at<float>(i) ) all++;

    }

 

       printf( "%.3f\n", (float) all / testData.rows );

 

       return 0;

}

Использованы 2 описанных выше признака, которые дают 0.707 и 0.851 вероятности правильного решения. Т.е. вероятности ошибки 0.149 и 0.293 – по теореме перемножения независимых вероятностей, результат должен бы быть 0.044 вероятность ошибки. Однако наши случайные данные видимо значительно коррелируют между собой, поскольку вероятность правильного решения увеличилась только до 0.868 (вероятность ошибки 0.132).

А если использовать для распознавания по 2 сигнала одного и того же признака с достоверностью 0.851, то общая достоверность повысится до 0.913.

CvNormalBayesClassifier, наивный Байесовский классификатор