The key part of haartraining is to establish a base classifier. Cart (a decision tree) is used in opencv: cvcreatemtstumpclassifier is called.
Here I will discuss how to split a node using the regression method. The classification method is only different from the method used to split a node.
Cvcreatemtstumpclassifier
// Sets the decision tree classification error calculation method stumperror = (INT) (cvmtstumptrainparams *) trainparams)-> error; // sets the class step and ydata = trainclasses-> data. PTR; If (trainclasses-> rows = 1) {M = trainclasses-> Cols; ystep = cv_elem_size (trainclasses-> type);} else {M = trainclasses-> rows; ystep = trainclasses-> step;} // set weight step and wdata = weights-> data. PTR; If (weights-> rows = 1) {assert (weights-> Cols = m ); Wstep = cv_elem_size (weights-> type);} else {assert (weights-> rows = m); wstep = weights-> step;} // set the step size, address, used to obtain the idxcache content if (cvmtstumptrainparams *) trainparams)-> sortedidx! = NULL) {sortedtype = cv_mat_type (cvmtstumptrainparams *) trainparams)-> sortedidx-> type ); assert (sortedtype = cv_16sc1 | sortedtype = cv_32sc1 | sortedtype = cv_32fc1); sorteddata = (cvmtstumptrainparams *) trainparams)-> sortedidx-> data. PTR; sortedsstep = cv_elem_size (sortedtype); sortedcstep = (cvmtstumptrainparams *) trainparams)-> sortedidx-> step; sortedn = (cvmtstumptrainparams *) Trainparams)-> sortedidx-> rows; sortedm = (cvmtstumptrainparams *) trainparams)-> sortedidx-> Cols;} If (traindata = NULL) {assert (cvmtstumptrainparams *) trainparams)-> gettraindata! = NULL); n = (cvmtstumptrainparams *) trainparams)-> numcomp; Assert (n> 0);} // set parameters such as step size and address, used to obtain the datacache content else {assert (cv_mat_type (traindata-> type) = cv_32fc1); Data = traindata-> data. PTR; If (cv_is_row_sample (flags) {cstep = cv_elem_size (traindata-> type); sStep = traindata-> step; Assert (M = traindata-> rows ); datan = n = traindata-> Cols;} else {sStep = cv_elem_size (traindata-> Type); cstep = traindata-> step; Assert (M = traindata-> Cols); datan = n = traindata-> rows;} If (cvmtstumptrainparams *) trainparams) -> gettraindata! = NULL) {n = (cvmtstumptrainparams *) trainparams)-> numcomp ;}}It is possible that the friends who study the code here still do not know the functions of idxcache and valcache. Here is a simple description:
Valcache is used to determine the number of feature values that are stored in the memory before training. idxcache is the serial number of samples in valcache that sort each feature by feature value from small to large. The memory size is set through the command line parameters of the running program. In cvhaartraining. cpp, we can find this sentence. Float and short are the basic types of content stored in valcache and idxcache respectively.
// 1 MB = 1048576b calculate the number of features in a sample that can be pre-computed and put in memory numprecalculated = (INT) (size_t) MEm) * (size_t) 1048576) /(size_t) (NPOs + nneg) * (sizeof (float) + sizeof (short ))));
For ease of understanding, I drew out the memory model of both.
Note that the index arrangement of each row in idxcache is. For example, the first line represents the index order of feature1 from small to large. We can see that the feature value of sample1 feature1 <feature value of sample0 feature1 <... <sample n <sample n-1. Using the idxcache array, we can easily traverse valcache from small to large by feature value and save space. From float-> short.
After understanding the two caches, we will return to the above Code. We can find that we only set the step size and the first address of the cache to prepare for the next traversal.
Skip the initialization steps of some variables, and we came to the part of building the decision tree stump. To facilitate reading the core code, we removed some other porting-based code.
While (t_compidx <n) {// select to calculate the first 100 features t_n = portion; If (t_compidx <datan) {t_n = (t_n <(datan-t_compidx ))? T_n: (datan-t_compidx); t_data = data; t_cstep = cstep; t_sstep = sStep;} else {} If (sorteddata! = NULL) {} else {/* have sorted indices */switch (sortedtype) {Case cv_16sc1: // select a feature value of a sample as the node for (Ti = t_compidx; ti <min (sortedn, t_compidx + t_n); Ti ++) {If (findstumpthreshold_16s [stumperror] (t_data + Ti * t_cstep, t_sstep, wdata, wstep, ydata, ystep, sorteddata + Ti * sortedcstep, sortedsstep, sortedm, & lerror, & rerror, & threshold, & left, & right, & sumw, & sumwy, & sumwyy )) {optcompidx = Ti ;}} break ;}}}}Datan indicates the number of features contained in a detection window, And portion indicates the number of behavior units for calculation. In each loop, the portion row in valcache is selected for computing to take advantage of parallel computing. If the macro of parallel computing is set.
Findstumpthreshold_32 [stumperror] is a function pointer. Using this function, we can select a feature value of a sample as a node of the decision tree. Here, I chose the minimum residual sum method for the node splitting method. That is, the sum of the residual values between classes in the left and right Subtrees after a feature value is used for classification. The minimum residual and the corresponding feature value are the nodes that meet the requirements.
Opencv1.0 implements this function using macro-defined methods
#define ICV_DEF_FIND_STUMP_THRESHOLD( suffix, type, error ) CV_BOOST_IMPL int icvFindStumpThreshold_##suffix( uchar* data, size_t datastep, uchar* wdata, size_t wstep, uchar* ydata, size_t ystep, uchar* idxdata, size_t idxstep, int num, float* lerror, float* rerror, float* threshold, float* left, float* right, float* sumw, float* sumwy, float* sumwyy ) { int found = 0; float wyl = 0.0F; float wl = 0.0F; float wyyl = 0.0F; float wyr = 0.0F; float wr = 0.0F; float curleft = 0.0F; float curright = 0.0F; float* prevval = NULL; float* curval = NULL; float curlerror = 0.0F; float currerror = 0.0F; float wposl; float wposr; int i = 0; int idx = 0; wposl = wposr = 0.0F; if( *sumw == FLT_MAX ) { /* calculate sums */ float *y = NULL; float *w = NULL; float wy = 0.0F; *sumw = 0.0F; *sumwy = 0.0F; *sumwyy = 0.0F; for( i = 0; i < num; i++ ) { idx = (int) ( *((type*) (idxdata + i*idxstep)) ); w = (float*) (wdata + idx * wstep); *sumw += *w; y = (float*) (ydata + idx * ystep); wy = (*w) * (*y); *sumwy += wy; *sumwyy += wy * (*y); } } for( i = 0; i < num; i++ ) { idx = (int) ( *((type*) (idxdata + i*idxstep)) ); curval = (float*) (data + idx * datastep); /* for debug purpose */ if( i > 0 ) assert( (*prevval) <= (*curval) ); wyr = *sumwy - wyl; wr = *sumw - wl; if( wl > 0.0 ) curleft = wyl / wl; else curleft = 0.0F; if( wr > 0.0 ) curright = wyr / wr; else curright = 0.0F; error if( curlerror + currerror < (*lerror) + (*rerror) ) { (*lerror) = curlerror; (*rerror) = currerror; *threshold = *curval; if( i > 0 ) { *threshold = 0.5F * (*threshold + *prevval); } *left = curleft; *right = curright; found = 1; } do { wl += *((float*) (wdata + idx * wstep)); wyl += (*((float*) (wdata + idx * wstep))) * (*((float*) (ydata + idx * ystep))); wyyl += *((float*) (wdata + idx * wstep)) * (*((float*) (ydata + idx * ystep))) * (*((float*) (ydata + idx * ystep))); } while( (++i) < num && ( *((float*) (data + (idx = (int) ( *((type*) (idxdata + i*idxstep))) ) * datastep)) == *curval ) ); --i; prevval = curval; } /* for each value */ return found; }
Here are several key variables: ydata is the type represented by a feature. Positive and negative samples are marked with 1 and-1 respectively. wdata is the weight value corresponding to positive and negative samples, data refers to a row of valcache.
When the program comes in, it determines whether the sumw is initialized. If it is not initialized, a value is assigned. Because each sample in the same training set corresponds to only one ydata and wdata (each sample corresponds to many Haar features, and the two are different), The sumw, sumwyy, sumwy is a definite value. The calculation is done in advance, so you do not have to repeat the calculation in the subsequent iterations.
Next, based on the index of a row in idxcache (depending on the number of iterations), the feature values of the corresponding row in valcache are traversed in ascending order, that is, the same feature value of different samples. Use it as a node and try to divide the samples. Curleft and curright represent the weighted average values of the left and right subtree classes, respectively. Then, the error macro is used to calculate the left and right subtree residual.
#define ICV_DEF_FIND_STUMP_THRESHOLD_SQ( suffix, type ) ICV_DEF_FIND_STUMP_THRESHOLD( sq_##suffix, type, /* calculate error (sum of squares) */ /* err = sum( w * (y - left(rigt)Val)^2 ) */ curlerror = wyyl + curleft * curleft * wl - 2.0F * curleft * wyl; currerror = (*sumwyy) - wyyl + curright * curright * wr - 2.0F * curright * wyr; )
The last do-while loop is used to skip the same feature value as the current node. Although the Left and Right Subtrees are divided by the same values in the future, the sum of squares of the residual values may change, but the minimum unit of decision tree Division is the type of feature values, because when using decision trees for classification, you must make the same decision on the same feature values (whether to insert the left subtree or the right subtree ).
Summary haartraining has 4 to 5 k lines of code, but after studying it carefully, we will get a lot of machine learning algorithms and good code writing habits. I will continue to update my source code research experience as I study it. Although I did not write it in detail, I strive to highlight it and summarize the puzzles I encountered while reading the code, give some help to friends who also learn the Training Algorithm
Opencv haartraining code parsing (2) cvcreatemtstumpclassifier (decision tree creation)