Opencv研读笔记:haartraining程序之cvCreateCARTClassifier函数详解(CART树状弱分类器创建)~

cvCreateCARTClassifier函数在haartraining程序中用于创建CART树状弱分类器,但一般只采用单一节点的CART分类器,即桩分类器,一个多节点的CART分类器训练耗时很多。根据自己的测试,要等差不多10分钟(2000正样本、2000负样本)才能训练完一个3节点的弱分类器,当然,总体的树状弱分类器的数目可能也会减少1/2。之所以将此函数拿出来说说,主要是因为在网上找不到针对这个函数的详细说明,同时,CART的应用十分广泛,自己也趁这个机会好好学学,把自己的一点理解分享给大家。

1. 先说说CART树的设计问题,也就是CvCARTClassifier这个结构体,结构体中变量的意义着实让我伤了一番脑筋。现添加其变量含义,如下:

typedef struct CvCARTClassifier
{
    CV_CLASSIFIER_FIELDS()
    /* number of internal nodes */
    int count;                      // 非叶子节点个数

    /* internal nodes (each array of <count> elements) */
    int* compidx;                   // 节点所采用的最优Haar特征序号
    float* threshold;               // 节点所采用的最优Haar特征阈值
    int* left;                      // 非叶子节点的左子节点序号(叶子节点为负数,非叶子节点为正数)
    int* right;                     // 非叶子节点的右子节点序号(叶子节点为负数,非叶子节点为正数)

    /* leaves (array of <count>+1 elements) */
    float* val;                     // 叶子节点输出置信度
} CvCARTClassifier;

其中,count就是main主函数中的参数nsplits,用于定义的是非叶子节点数,或者叫做中间节点数。个人认为,这样设计一棵树很科学,将非叶子节点与叶子节点分开表述,结构体十分简洁,只不过当时left的真实含义让我琢磨了挺长时间。

2. cvCreateCARTClassifier中节点的“分类属性”仍旧是Haar特征,“分类准则”是分类错误率的下降程度,在程序中表现为”父节点的左(右)分支error与当前节点基于最优Haar特征的error之和之间的差值(errdrop)的大小“。

3. CART树状分类器的形式多种多样,就3个非叶子节点来说,我调试之后,就遇到了如下两种弱分类器:

4. 可能有童鞋会问,为什么要采用树状的弱分类器,我的理解是,一个树状的分类器在测试过程中,特征比较的次数相对串行的弱分类器要少很多,比如说,3个串行的Haar特征,比较次数是3次,但是如果是一颗3节点的CART树,比较次数可能只需要两次。并且, 一个树状弱分类器中,子节点针对的数据集更加具体,具有针对性,可能精度会更高。

以上就是自己对cvCreateCARTClassifier函数的理解,带有注释的源代码如下所示:

转载请注明:http://blog.csdn.net/wsj998689aa/article/details/43411809

CV_BOOST_IMPL
CvClassifier* cvCreateCARTClassifier( CvMat* trainData,                     // 训练样本特征值矩阵
                                     int flags,                             // 样本按行排列
                                     CvMat* trainClasses,                   // 训练样本类别向量
                                     CvMat* typeMask,
                                     CvMat* missedMeasurementsMask,
                                     CvMat* compIdx,                        // 特征序列向量
                                     CvMat* sampleIdx,                      // 样本序列向量
                                     CvMat* weights,                        // 样本权值向量
                                     CvClassifierTrainParams* trainParams ) // 参数
{
    CvCARTClassifier* cart = NULL;          // CART树状弱分类器
    size_t datasize = 0;
    int count = 0;                          // CART中的节点数目
    int i = 0;
    int j = 0;

    CvCARTNode* intnode = NULL;             // CART节点
    CvCARTNode* list = NULL;                // CART节点列表
    int listcount = 0;                      // CART节点列表元素个数
    CvMat* lidx = NULL;                     // 当前节点左节点样本序列
    CvMat* ridx = NULL;                     // 当前节点右节点样本序列

    float maxerrdrop = 0.0F;
    int idx = 0;

    // 设置节点分裂函数指针
    void (*splitIdxCallback)( int compidx, float threshold,
        CvMat* idx, CvMat** left, CvMat** right,
        void* userdata );
    void* userdata;

    // 设置非叶子节点个数
    count = ((CvCARTTrainParams*) trainParams)->count;

    assert( count > 0 );

    datasize = sizeof( *cart ) + (sizeof( float ) + 3 * sizeof( int )) * count +
        sizeof( float ) * (count + 1);

    cart = (CvCARTClassifier*) cvAlloc( datasize );
    memset( cart, 0, datasize );

    cart->count = count;

    // 输出当前样本的置信度
    cart->eval = cvEvalCARTClassifier;

    cart->save = NULL;
    cart->release = cvReleaseCARTClassifier;

    cart->compidx = (int*) (cart + 1);                      // 当前非叶子节点的最优Haar特征序号
    cart->threshold = (float*) (cart->compidx + count);     // 当前非叶子节点的最优Haar特征阈值
    cart->left  = (int*) (cart->threshold + count);         // 当前节点的左子节点序号,包含叶子节点序号
    cart->right = (int*) (cart->left + count);              // 当前节点的右子节点序号,包含叶子节点序号
    cart->val = (float*) (cart->right + count);             // 叶子节点输出置信度

    datasize = sizeof( CvCARTNode ) * (count + count);
    intnode = (CvCARTNode*) cvAlloc( datasize );
    memset( intnode, 0, datasize );
    list = (CvCARTNode*) (intnode + count);

    // 节点分裂函数指针,一般为icvSplitIndicesCallback函数
    splitIdxCallback = ((CvCARTTrainParams*) trainParams)->splitIdx;
    userdata = ((CvCARTTrainParams*) trainParams)->userdata;

    // R代表样本按行排列,C代表样本按列排列
    if( splitIdxCallback == NULL )
    {
        splitIdxCallback = ( CV_IS_ROW_SAMPLE( flags ) )
            ? icvDefaultSplitIdx_R : icvDefaultSplitIdx_C;
        userdata = trainData;
    }

    // 创建CART根节点
    intnode[0].sampleIdx = sampleIdx;
    intnode[0].stump = (CvStumpClassifier*)
        ((CvCARTTrainParams*) trainParams)->stumpConstructor( trainData, flags,
        trainClasses, typeMask, missedMeasurementsMask, compIdx, sampleIdx, weights,
        ((CvCARTTrainParams*) trainParams)->stumpTrainParams );
    cart->left[0] = cart->right[0] = 0;

    // 创建树状弱分类器,lerror或者rerror不为0代表着当前节点为非叶子节点
    listcount = 0;
    for( i = 1; i < count; i++ )
    {
        // 基于当前节点弱分类器阈值,对当前节点进行分裂
        splitIdxCallback( intnode[i-1].stump->compidx, intnode[i-1].stump->threshold,
            intnode[i-1].sampleIdx, &lidx, &ridx, userdata );

        // 为分裂之后的非叶子节点计算最优特征
        if( intnode[i-1].stump->lerror != 0.0F )
        {
            // 小于阈值的样本集合
            list[listcount].sampleIdx = lidx;

            // 基于新样本集合寻找最优特征
            list[listcount].stump = (CvStumpClassifier*)
                ((CvCARTTrainParams*) trainParams)->stumpConstructor( trainData, flags,
                trainClasses, typeMask, missedMeasurementsMask, compIdx,
                list[listcount].sampleIdx,
                weights, ((CvCARTTrainParams*) trainParams)->stumpTrainParams );

            // 计算信息增益(这里是error的下降程度)
            list[listcount].errdrop = intnode[i-1].stump->lerror
                - (list[listcount].stump->lerror + list[listcount].stump->rerror);
            list[listcount].leftflag = 1;
            list[listcount].parent = i-1;
            listcount++;
        }
        else
        {
            cvReleaseMat( &lidx );
        }

        // 同上,左分支换成右分支,偏向于右分支
        if( intnode[i-1].stump->rerror != 0.0F )
        {
            list[listcount].sampleIdx = ridx;
            list[listcount].stump = (CvStumpClassifier*)
                ((CvCARTTrainParams*) trainParams)->stumpConstructor( trainData, flags,
                trainClasses, typeMask, missedMeasurementsMask, compIdx,
                list[listcount].sampleIdx,
                weights, ((CvCARTTrainParams*) trainParams)->stumpTrainParams );
            list[listcount].errdrop = intnode[i-1].stump->rerror
                - (list[listcount].stump->lerror + list[listcount].stump->rerror);
            list[listcount].leftflag = 0;       // 优先级更高的证据
            list[listcount].parent = i-1;
            listcount++;
        }
        else
        {
            cvReleaseMat( &ridx );
        }

        if( listcount == 0 ) break;

        idx = 0;
        maxerrdrop = list[idx].errdrop;
        for( j = 1; j < listcount; j++ )
        {
            if( list[j].errdrop > maxerrdrop )
            {
                idx = j;
                maxerrdrop = list[j].errdrop;
            }
        }

        // 添加新节点
        intnode[i] = list[idx];

        // 确定当前节点的非叶子子节点的序号
        if( list[idx].leftflag )
        {
            cart->left[list[idx].parent] = i;
        }
        else
        {
            cart->right[list[idx].parent] = i;
        }

        if( idx != (listcount - 1) )
        {
            list[idx] = list[listcount - 1];
        }
        listcount--;
    }

    // 这段代码用于确定树中节点最优特征序号、阈值与叶子节点序号和输出置信度
    // left与right大于等于0,为0代表叶子节点
    // 就算CART中只有一个节点,仍旧需要设置叶子节点
    j = 0;
    cart->count = 0;
    for( i = 0; i < count && (intnode[i].stump != NULL); i++ )
    {
        cart->count++;
        cart->compidx[i] = intnode[i].stump->compidx;
        cart->threshold[i] = intnode[i].stump->threshold;

        // 确定叶子序号与叶子的输出置信度
        if( cart->left[i] <= 0 )
        {
            cart->left[i] = -j;
            cart->val[j] = intnode[i].stump->left;
            j++;
        }
        if( cart->right[i] <= 0 )
        {
            cart->right[i] = -j;
            cart->val[j] = intnode[i].stump->right;
            j++;
        }
    }

    // 后续处理
    for( i = 0; i < count && (intnode[i].stump != NULL); i++ )
    {
        intnode[i].stump->release( (CvClassifier**) &(intnode[i].stump) );
        if( i != 0 )
        {
            cvReleaseMat( &(intnode[i].sampleIdx) );
        }
    }
    for( i = 0; i < listcount; i++ )
    {
        list[i].stump->release( (CvClassifier**) &(list[i].stump) );
        cvReleaseMat( &(list[i].sampleIdx) );
    }

    cvFree( &intnode );

    return (CvClassifier*) cart;
}

这段程序有些细节我可能没有理解正确,比如说左右分支的error同时不为0时,我的解释是程序将右分支的优先级设置的更高些,就是一个可能出错的地方,还想与童鞋们一起探讨,谢谢!

时间: 2024-10-25 00:01:40

Opencv研读笔记:haartraining程序之cvCreateCARTClassifier函数详解(CART树状弱分类器创建)~的相关文章

Opencv研读笔记:haartraining程序之icvCreateCARTStageClassifier函数详解~

之前介绍了haartraining程序中的cvCreateMTStumpClassifier函数,这个函数的功能是计算最优弱分类器,这篇文章介绍一下自己对haartraining中关于强分类器计算的一些理解,也就是程序中的icvCreateCARTStageClassifier函数. 由于haartraining是基于HAAR特征进行adaboost训练,对于HAAR特征的处理比较繁琐,采用了奇数弱分类器补充针对翻转特征最优弱分类器计算的代码,所以代码看起来较为冗长.此外,其采用了较多的中间结构

(笔记)Linux下的ioctl()函数详解

我这里说的ioctl函数是指驱动程序里的,因为我不知道还有没有别的场合用到了它,所以就规定了我们讨论的范围.写这篇文章是因为我前一阵子被ioctl给搞混了,这几天才弄明白它,于是在这里清理一下头脑. 一. 什么是ioctl      ioctl是设备驱动程序中对设备的I/O通道进行管理的函数.所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率.马达的转速等等.它的调用个数如下:  int ioctl(int fd, ind cmd, …):      其中fd是用户程序

《学习opencv》笔记——关于一些绘图的函数

绘图函数 (1)直线cvLine函数 其结构 void cvLine(//画直线 CvArr* array,//画布图像 CvPoint pt1,//起始点 CvPoint pt2,//终点 CvScalar color,//颜色 int thickness = 1,//宽度 int connectivity = 8//反走样 ); 实例代码 #include <cv.h> #include <highgui.h> #include <stdio.h> int main

opencv之adaboost中的cvCreateMTStumpClassifier函数详解~

cvCreateMTStumpClassifier函数出自opencv中的haartraining程序,在adaboost(cvCreateTreeCascadeClassifier)的强分类器(icvCreateCARTStageClassifier)中被两次调用,该函数用于寻找最优弱分类器,或者说成计算最优haar特征.功能很明确,但是大家都知道的,opencv的代码绝大部分写的让人真心看不懂,这个函数算是haartraining中比较难以看懂的函数,局部变量达到20个之多,童鞋我也是不甘心

c++ 虚函数详解

下面是对C++的虚函数的理解. 一,定义 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离:用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略.下面来看一段简单的代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 classA { publi

linux中fork()函数详解[zz]

转载自:http://www.cnblogs.com/york-hust/archive/2012/11/23/2784534.html 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都复制到新的新进程中,只有

CreateFile函数详解

CreateFile函数详解 CreateFile The CreateFile function creates or opens the following objects and returns a handle that can be used to accessthe object: files pipes mailslots communications resources disk devices(Windows NT only) consoles directories(open

Android总结篇系列:Activity中几个主要函数详解

专注Android领域开发. 仰望星空,同时需要脚踏实地. ——好记性不如烂博客 Android总结篇系列:Activity中几个主要函数详解 Activity作为Android系统中四大基本组件之一,包含大量的与其他的各大组件.intent.widget以及系统各项服务等之间的交互的函数.在此,本文主要选取实际项目开发中常用的,但完全理解又需要有一定深入了解的几个函数进行讲解,后续本文会根据需要不断更新. 1. startActivityForResult / onActivityResult

【转】angularjs指令中的compile与link函数详解

这篇文章主要介绍了angularjs指令中的compile与link函数详解,本文同时诉大家complie,pre-link,post-link的用法与区别等内容,需要的朋友可以参考下 通常大家在使用ng中的指令的时候,用的链接函数最多的是link属性,下面这篇文章将告诉大家complie,pre-link,post-link的用法与区别. angularjs里的指令非常神奇,允许你创建非常语义化以及高度重用的组件,可以理解为web components的先驱者. 网上已经有很多介绍怎么使用指令