converted some more samples to C++
[profile/ivi/opencv.git] / samples / c / letter_recog.cpp
1 #include "opencv2/core/core_c.h"
2 #include "opencv2/ml/ml.hpp"
3
4 #include <stdio.h>
5
6 /*
7 The sample demonstrates how to train Random Trees classifier
8 (or Boosting classifier, or MLP - see main()) using the provided dataset.
9
10 We use the sample database letter-recognition.data
11 from UCI Repository, here is the link:
12
13 Newman, D.J. & Hettich, S. & Blake, C.L. & Merz, C.J. (1998).
14 UCI Repository of machine learning databases
15 [http://www.ics.uci.edu/~mlearn/MLRepository.html].
16 Irvine, CA: University of California, Department of Information and Computer Science.
17
18 The dataset consists of 20000 feature vectors along with the
19 responses - capital latin letters A..Z.
20 The first 16000 (10000 for boosting)) samples are used for training
21 and the remaining 4000 (10000 for boosting) - to test the classifier.
22 */
23
24 // This function reads data and responses from the file <filename>
25 static int
26 read_num_class_data( const char* filename, int var_count,
27                      CvMat** data, CvMat** responses )
28 {
29     const int M = 1024;
30     FILE* f = fopen( filename, "rt" );
31     CvMemStorage* storage;
32     CvSeq* seq;
33     char buf[M+2];
34     float* el_ptr;
35     CvSeqReader reader;
36     int i, j;
37
38     if( !f )
39         return 0;
40
41     el_ptr = new float[var_count+1];
42     storage = cvCreateMemStorage();
43     seq = cvCreateSeq( 0, sizeof(*seq), (var_count+1)*sizeof(float), storage );
44
45     for(;;)
46     {
47         char* ptr;
48         if( !fgets( buf, M, f ) || !strchr( buf, ',' ) )
49             break;
50         el_ptr[0] = buf[0];
51         ptr = buf+2;
52         for( i = 1; i <= var_count; i++ )
53         {
54             int n = 0;
55             sscanf( ptr, "%f%n", el_ptr + i, &n );
56             ptr += n + 1;
57         }
58         if( i <= var_count )
59             break;
60         cvSeqPush( seq, el_ptr );
61     }
62     fclose(f);
63
64     *data = cvCreateMat( seq->total, var_count, CV_32F );
65     *responses = cvCreateMat( seq->total, 1, CV_32F );
66
67     cvStartReadSeq( seq, &reader );
68
69     for( i = 0; i < seq->total; i++ )
70     {
71         const float* sdata = (float*)reader.ptr + 1;
72         float* ddata = data[0]->data.fl + var_count*i;
73         float* dr = responses[0]->data.fl + i;
74
75         for( j = 0; j < var_count; j++ )
76             ddata[j] = sdata[j];
77         *dr = sdata[-1];
78         CV_NEXT_SEQ_ELEM( seq->elem_size, reader );
79     }
80
81     cvReleaseMemStorage( &storage );
82     delete el_ptr;
83     return 1;
84 }
85
86 static
87 int build_rtrees_classifier( char* data_filename,
88     char* filename_to_save, char* filename_to_load )
89 {
90     CvMat* data = 0;
91     CvMat* responses = 0;
92     CvMat* var_type = 0;
93     CvMat* sample_idx = 0;
94
95     int ok = read_num_class_data( data_filename, 16, &data, &responses );
96     int nsamples_all = 0, ntrain_samples = 0;
97     int i = 0;
98     double train_hr = 0, test_hr = 0;
99     CvRTrees forest;
100     CvMat* var_importance = 0;
101
102     if( !ok )
103     {
104         printf( "Could not read the database %s\n", data_filename );
105         return -1;
106     }
107
108     printf( "The database %s is loaded.\n", data_filename );
109     nsamples_all = data->rows;
110     ntrain_samples = (int)(nsamples_all*0.8);
111
112     // Create or load Random Trees classifier
113     if( filename_to_load )
114     {
115         // load classifier from the specified file
116         forest.load( filename_to_load );
117         ntrain_samples = 0;
118         if( forest.get_tree_count() == 0 )
119         {
120             printf( "Could not read the classifier %s\n", filename_to_load );
121             return -1;
122         }
123         printf( "The classifier %s is loaded.\n", data_filename );
124     }
125     else
126     {
127         // create classifier by using <data> and <responses>
128         printf( "Training the classifier ...\n");
129
130         // 1. create type mask
131         var_type = cvCreateMat( data->cols + 1, 1, CV_8U );
132         cvSet( var_type, cvScalarAll(CV_VAR_ORDERED) );
133         cvSetReal1D( var_type, data->cols, CV_VAR_CATEGORICAL );
134
135         // 2. create sample_idx
136         sample_idx = cvCreateMat( 1, nsamples_all, CV_8UC1 );
137         {
138             CvMat mat;
139             cvGetCols( sample_idx, &mat, 0, ntrain_samples );
140             cvSet( &mat, cvRealScalar(1) );
141
142             cvGetCols( sample_idx, &mat, ntrain_samples, nsamples_all );
143             cvSetZero( &mat );
144         }
145
146         // 3. train classifier
147         forest.train( data, CV_ROW_SAMPLE, responses, 0, sample_idx, var_type, 0,
148             CvRTParams(10,10,0,false,15,0,true,4,100,0.01f,CV_TERMCRIT_ITER));
149         printf( "\n");
150     }
151
152     // compute prediction error on train and test data
153     for( i = 0; i < nsamples_all; i++ )
154     {
155         double r;
156         CvMat sample;
157         cvGetRow( data, &sample, i );
158
159         r = forest.predict( &sample );
160         r = fabs((double)r - responses->data.fl[i]) <= FLT_EPSILON ? 1 : 0;
161
162         if( i < ntrain_samples )
163             train_hr += r;
164         else
165             test_hr += r;
166     }
167
168     test_hr /= (double)(nsamples_all-ntrain_samples);
169     train_hr /= (double)ntrain_samples;
170     printf( "Recognition rate: train = %.1f%%, test = %.1f%%\n",
171             train_hr*100., test_hr*100. );
172
173     printf( "Number of trees: %d\n", forest.get_tree_count() );
174
175     // Print variable importance
176     var_importance = (CvMat*)forest.get_var_importance();
177     if( var_importance )
178     {
179         double rt_imp_sum = cvSum( var_importance ).val[0];
180         printf("var#\timportance (in %%):\n");
181         for( i = 0; i < var_importance->cols; i++ )
182             printf( "%-2d\t%-4.1f\n", i,
183             100.f*var_importance->data.fl[i]/rt_imp_sum);
184     }
185
186     //Print some proximitites
187     printf( "Proximities between some samples corresponding to the letter 'T':\n" );
188     {
189         CvMat sample1, sample2;
190         const int pairs[][2] = {{0,103}, {0,106}, {106,103}, {-1,-1}};
191
192         for( i = 0; pairs[i][0] >= 0; i++ )
193         {
194             cvGetRow( data, &sample1, pairs[i][0] );
195             cvGetRow( data, &sample2, pairs[i][1] );
196             printf( "proximity(%d,%d) = %.1f%%\n", pairs[i][0], pairs[i][1],
197                 forest.get_proximity( &sample1, &sample2 )*100. );
198         }
199     }
200
201     // Save Random Trees classifier to file if needed
202     if( filename_to_save )
203         forest.save( filename_to_save );
204
205     cvReleaseMat( &sample_idx );
206     cvReleaseMat( &var_type );
207     cvReleaseMat( &data );
208     cvReleaseMat( &responses );
209
210     return 0;
211 }
212
213
214 static
215 int build_boost_classifier( char* data_filename,
216     char* filename_to_save, char* filename_to_load )
217 {
218     const int class_count = 26;
219     CvMat* data = 0;
220     CvMat* responses = 0;
221     CvMat* var_type = 0;
222     CvMat* temp_sample = 0;
223     CvMat* weak_responses = 0;
224
225     int ok = read_num_class_data( data_filename, 16, &data, &responses );
226     int nsamples_all = 0, ntrain_samples = 0;
227     int var_count;
228     int i, j, k;
229     double train_hr = 0, test_hr = 0;
230     CvBoost boost;
231
232     if( !ok )
233     {
234         printf( "Could not read the database %s\n", data_filename );
235         return -1;
236     }
237
238     printf( "The database %s is loaded.\n", data_filename );
239     nsamples_all = data->rows;
240     ntrain_samples = (int)(nsamples_all*0.5);
241     var_count = data->cols;
242
243     // Create or load Boosted Tree classifier
244     if( filename_to_load )
245     {
246         // load classifier from the specified file
247         boost.load( filename_to_load );
248         ntrain_samples = 0;
249         if( !boost.get_weak_predictors() )
250         {
251             printf( "Could not read the classifier %s\n", filename_to_load );
252             return -1;
253         }
254         printf( "The classifier %s is loaded.\n", data_filename );
255     }
256     else
257     {
258         // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
259         //
260         // As currently boosted tree classifier in MLL can only be trained
261         // for 2-class problems, we transform the training database by
262         // "unrolling" each training sample as many times as the number of
263         // classes (26) that we have.
264         //
265         // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
266
267         CvMat* new_data = cvCreateMat( ntrain_samples*class_count, var_count + 1, CV_32F );
268         CvMat* new_responses = cvCreateMat( ntrain_samples*class_count, 1, CV_32S );
269
270         // 1. unroll the database type mask
271         printf( "Unrolling the database...\n");
272         for( i = 0; i < ntrain_samples; i++ )
273         {
274             float* data_row = (float*)(data->data.ptr + data->step*i);
275             for( j = 0; j < class_count; j++ )
276             {
277                 float* new_data_row = (float*)(new_data->data.ptr +
278                                 new_data->step*(i*class_count+j));
279                 for( k = 0; k < var_count; k++ )
280                     new_data_row[k] = data_row[k];
281                 new_data_row[var_count] = (float)j;
282                 new_responses->data.i[i*class_count + j] = responses->data.fl[i] == j+'A';
283             }
284         }
285
286         // 2. create type mask
287         var_type = cvCreateMat( var_count + 2, 1, CV_8U );
288         cvSet( var_type, cvScalarAll(CV_VAR_ORDERED) );
289         // the last indicator variable, as well
290         // as the new (binary) response are categorical
291         cvSetReal1D( var_type, var_count, CV_VAR_CATEGORICAL );
292         cvSetReal1D( var_type, var_count+1, CV_VAR_CATEGORICAL );
293
294         // 3. train classifier
295         printf( "Training the classifier (may take a few minutes)...\n");
296         boost.train( new_data, CV_ROW_SAMPLE, new_responses, 0, 0, var_type, 0,
297             CvBoostParams(CvBoost::REAL, 100, 0.95, 5, false, 0 ));
298         cvReleaseMat( &new_data );
299         cvReleaseMat( &new_responses );
300         printf("\n");
301     }
302
303     temp_sample = cvCreateMat( 1, var_count + 1, CV_32F );
304     weak_responses = cvCreateMat( 1, boost.get_weak_predictors()->total, CV_32F ); 
305
306     // compute prediction error on train and test data
307     for( i = 0; i < nsamples_all; i++ )
308     {
309         int best_class = 0;
310         double max_sum = -DBL_MAX;
311         double r;
312         CvMat sample;
313         cvGetRow( data, &sample, i );
314         for( k = 0; k < var_count; k++ )
315             temp_sample->data.fl[k] = sample.data.fl[k];
316
317         for( j = 0; j < class_count; j++ )
318         {
319             temp_sample->data.fl[var_count] = (float)j;
320             boost.predict( temp_sample, 0, weak_responses );
321             double sum = cvSum( weak_responses ).val[0];
322             if( max_sum < sum )
323             {
324                 max_sum = sum;
325                 best_class = j + 'A';
326             }
327         }
328
329         r = fabs(best_class - responses->data.fl[i]) < FLT_EPSILON ? 1 : 0;
330
331         if( i < ntrain_samples )
332             train_hr += r;
333         else
334             test_hr += r;
335     }
336
337     test_hr /= (double)(nsamples_all-ntrain_samples);
338     train_hr /= (double)ntrain_samples;
339     printf( "Recognition rate: train = %.1f%%, test = %.1f%%\n",
340             train_hr*100., test_hr*100. );
341
342     printf( "Number of trees: %d\n", boost.get_weak_predictors()->total );
343
344     // Save classifier to file if needed
345     if( filename_to_save )
346         boost.save( filename_to_save );
347
348     cvReleaseMat( &temp_sample );
349     cvReleaseMat( &weak_responses );
350     cvReleaseMat( &var_type );
351     cvReleaseMat( &data );
352     cvReleaseMat( &responses );
353
354     return 0;
355 }
356
357
358 static
359 int build_mlp_classifier( char* data_filename,
360     char* filename_to_save, char* filename_to_load )
361 {
362     const int class_count = 26;
363     CvMat* data = 0;
364     CvMat train_data;
365     CvMat* responses = 0;
366     CvMat* mlp_response = 0;
367
368     int ok = read_num_class_data( data_filename, 16, &data, &responses );
369     int nsamples_all = 0, ntrain_samples = 0;
370     int i, j;
371     double train_hr = 0, test_hr = 0;
372     CvANN_MLP mlp;
373
374     if( !ok )
375     {
376         printf( "Could not read the database %s\n", data_filename );
377         return -1;
378     }
379
380     printf( "The database %s is loaded.\n", data_filename );
381     nsamples_all = data->rows;
382     ntrain_samples = (int)(nsamples_all*0.8);
383
384     // Create or load MLP classifier
385     if( filename_to_load )
386     {
387         // load classifier from the specified file
388         mlp.load( filename_to_load );
389         ntrain_samples = 0;
390         if( !mlp.get_layer_count() )
391         {
392             printf( "Could not read the classifier %s\n", filename_to_load );
393             return -1;
394         }
395         printf( "The classifier %s is loaded.\n", data_filename );
396     }
397     else
398     {
399         // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
400         //
401         // MLP does not support categorical variables by explicitly.
402         // So, instead of the output class label, we will use
403         // a binary vector of <class_count> components for training and,
404         // therefore, MLP will give us a vector of "probabilities" at the
405         // prediction stage
406         //
407         // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
408
409         CvMat* new_responses = cvCreateMat( ntrain_samples, class_count, CV_32F );
410
411         // 1. unroll the responses
412         printf( "Unrolling the responses...\n");
413         for( i = 0; i < ntrain_samples; i++ )
414         {
415             int cls_label = cvRound(responses->data.fl[i]) - 'A';
416             float* bit_vec = (float*)(new_responses->data.ptr + i*new_responses->step);
417             for( j = 0; j < class_count; j++ )
418                 bit_vec[j] = 0.f;
419             bit_vec[cls_label] = 1.f;
420         }
421         cvGetRows( data, &train_data, 0, ntrain_samples );
422
423         // 2. train classifier
424         int layer_sz[] = { data->cols, 100, 100, class_count };
425         CvMat layer_sizes =
426             cvMat( 1, (int)(sizeof(layer_sz)/sizeof(layer_sz[0])), CV_32S, layer_sz );
427         mlp.create( &layer_sizes );
428         printf( "Training the classifier (may take a few minutes)...\n");
429         mlp.train( &train_data, new_responses, 0, 0,
430             CvANN_MLP_TrainParams(cvTermCriteria(CV_TERMCRIT_ITER,300,0.01),
431 #if 1
432             CvANN_MLP_TrainParams::BACKPROP,0.001));
433 #else
434             CvANN_MLP_TrainParams::RPROP,0.05));
435 #endif
436         cvReleaseMat( &new_responses );
437         printf("\n");
438     }
439
440     mlp_response = cvCreateMat( 1, class_count, CV_32F );
441
442     // compute prediction error on train and test data
443     for( i = 0; i < nsamples_all; i++ )
444     {
445         int best_class;
446         CvMat sample;
447         cvGetRow( data, &sample, i );
448         CvPoint max_loc = {0,0};
449         mlp.predict( &sample, mlp_response );
450         cvMinMaxLoc( mlp_response, 0, 0, 0, &max_loc, 0 );
451         best_class = max_loc.x + 'A';
452
453         int r = fabs((double)best_class - responses->data.fl[i]) < FLT_EPSILON ? 1 : 0;
454
455         if( i < ntrain_samples )
456             train_hr += r;
457         else
458             test_hr += r;
459     }
460
461     test_hr /= (double)(nsamples_all-ntrain_samples);
462     train_hr /= (double)ntrain_samples;
463     printf( "Recognition rate: train = %.1f%%, test = %.1f%%\n",
464             train_hr*100., test_hr*100. );
465
466     // Save classifier to file if needed
467     if( filename_to_save )
468         mlp.save( filename_to_save );
469
470     cvReleaseMat( &mlp_response );
471     cvReleaseMat( &data );
472     cvReleaseMat( &responses );
473
474     return 0;
475 }
476
477
478 int main( int argc, char *argv[] )
479 {
480     char* filename_to_save = 0;
481     char* filename_to_load = 0;
482     char default_data_filename[] = "./letter-recognition.data";
483     char* data_filename = default_data_filename;
484     int method = 0;
485
486     int i;
487     for( i = 1; i < argc; i++ )
488     {
489         if( strcmp(argv[i],"-data") == 0 ) // flag "-data letter_recognition.xml"
490         {
491             i++;
492             data_filename = argv[i];
493         }
494         else if( strcmp(argv[i],"-save") == 0 ) // flag "-save filename.xml"
495         {
496             i++;
497             filename_to_save = argv[i];
498         }
499         else if( strcmp(argv[i],"-load") == 0) // flag "-load filename.xml"
500         {
501             i++;
502             filename_to_load = argv[i];
503         }
504         else if( strcmp(argv[i],"-boost") == 0)
505         {
506             method = 1;
507         }
508         else if( strcmp(argv[i],"-mlp") == 0 )
509         {
510             method = 2;
511         }
512         else
513             break;
514     }
515
516     if( i < argc ||
517         (method == 0 ?
518         build_rtrees_classifier( data_filename, filename_to_save, filename_to_load ) :
519         method == 1 ?
520         build_boost_classifier( data_filename, filename_to_save, filename_to_load ) :
521         method == 2 ?
522         build_mlp_classifier( data_filename, filename_to_save, filename_to_load ) :
523         -1) < 0)
524     {
525         printf("This is letter recognition sample.\n"
526                 "The usage: letter_recog [-data <path to letter-recognition.data>] \\\n"
527                 "  [-save <output XML file for the classifier>] \\\n"
528                 "  [-load <XML file with the pre-trained classifier>] \\\n"
529                 "  [-boost|-mlp] # to use boost/mlp classifier instead of default Random Trees\n" );
530     }
531     return 0;
532 }