首页 > 代码库 > OpenCV中CascadeClassifier类实现多尺度检测源码解析

OpenCV中CascadeClassifier类实现多尺度检测源码解析

级联分类器检测类CascadeClassifier,在2.4.5版本中使用Adaboost的方法+LBP、HOG、HAAR进行目标检测,加载的是使用traincascade进行训练的分类器

class CV_EXPORTS_W CascadeClassifier
{
public:
    CV_WRAP CascadeClassifier(); // 无参数构造函数,new自动调用该函数分配初试内存
    CV_WRAP CascadeClassifier( const string& filename ); // 带参数构造函数,参数为XML的绝对名称
    virtual ~CascadeClassifier(); // 析构函数,无需关心

    CV_WRAP virtual bool empty() const; // 是否导入参数,只创建了该对象而没有加载或者加载失败时都是空的
    CV_WRAP bool load( const string& filename ); // 加载分类器,参数为XML的绝对名称,函数内部调用read读取新格式的分类器,读取成功后直接返回,读取失败后调用cvLoad读取旧格式的分类器,读取成功返回true,否则返回false
    virtual bool read( const FileNode& node );   // load内部调用read解析XML中的内容,也可以自己创建节点然后调用Read即可,但是该函数只能读取新格式的分类器,不能读取旧格式的分类器

    // 多尺度检测函数
    CV_WRAP virtual void detectMultiScale( const Mat& image,        // 图像,cvarrtoMat实现IplImage转换为Mat,必须为8位,内部可自行转换为灰度图像
                                   CV_OUT vector<Rect>& objects,    // 输出矩形,注意vector不是线程安全的
                                   double scaleFactor=1.1,          // 缩放比例,必须大于1
                                   int minNeighbors=3,              // 合并窗口时最小neighbor,每个候选矩阵至少包含的附近元素个数
                                   int flags=0,                     // 检测标记,只对旧格式的分类器有效,与cvHaarDetectObjects的参数flags相同,默认为0,可能的取值为CV_HAAR_DO_CANNY_PRUNING(CANNY边缘检测)、CV_HAAR_SCALE_IMAGE(缩放图像)、CV_HAAR_FIND_BIGGEST_OBJECT(寻找最大的目标)、CV_HAAR_DO_ROUGH_SEARCH(做粗略搜索);如果寻找最大的目标就不能缩放图像,也不能CANNY边缘检测
                                   Size minSize=Size(),             // 最小检测目标
                                   Size maxSize=Size() );           // 最大检测目标
    // 最好不要在这里设置最大最小,可能会影响合并的效果,因此可以在检测完毕后自行判断结果是否满足要求
    CV_WRAP virtual void detectMultiScale( const Mat& image,
                                   CV_OUT vector<Rect>& objects,
                                   vector<int>& rejectLevels,
                                   vector<double>& levelWeights,
                                   double scaleFactor=1.1,
                                   int minNeighbors=3, int flags=0,
                                   Size minSize=Size(),
                                   Size maxSize=Size(),
                                   bool outputRejectLevels=false );
// 上述参数多了rejectLevels和levelWeights以及outputRejectLevels参数,只有在outputRejectLevels为true的时候才可能输出前两个参数
// 还有就是在使用旧分类器的时候必须设置flags为CV_HAAR_SCALE_IMAGE,可以通过haarcascade_frontalface_alt.xml检测人脸尝试


    bool isOldFormatCascade() const;        // 是否是旧格式的分类器
    virtual Size getOriginalWindowSize() const;    // 初始检测窗口大小,也就是训练的窗口
    int getFeatureType() const; // 获取特征类型
    bool setImage( const Mat& );    // 设置图像,计算图像的积分图
    virtual int runAt( Ptr<FeatureEvaluator>& feval, Point pt, double& weight ); // 计算某检测窗口是否为目标
    // 保存强分类器数据
    class Data
    {
    public:
        struct CV_EXPORTS DTreeNode // 节点
        {
            int featureIdx; // 对应的特征编号
            float threshold; // for ordered features only 节点阈值
            int left; // 左子树
            int right; // 右子树
        };

        struct CV_EXPORTS DTree // 弱分类器
        {
            int nodeCount; // 弱分类器中节点个数
        };

        struct CV_EXPORTS Stage // 强分类器
        {
            int first; // 在classifier中的起始位置
            int ntrees; // 该强分类器中的弱分类器数
            float threshold; // 强分类器阈值
        };

        bool read(const FileNode &node); // 读取强分类器

        bool isStumpBased;    // 是否只有树桩

        int stageType;      // BOOST,boostType:GAB、RAB等
        int featureType;    // HAAR、HOG、LBP
        int ncategories;    // maxCatCount,LBP为256,其余为0
        Size origWinSize;

        vector<Stage> stages;
        vector<DTree> classifiers;
        vector<DTreeNode> nodes;
        vector<float> leaves;
        vector<int> subsets;
    };

    Data data;
    Ptr<FeatureEvaluator> featureEvaluator;
    Ptr<CvHaarClassifierCascade> oldCascade;
// 关于mask这块参考《OpenCV目标检测之MaskGenerator》
public:
    class CV_EXPORTS MaskGenerator
    {
    public:
        virtual ~MaskGenerator() {}
        virtual cv::Mat generateMask(const cv::Mat& src)=0;
        virtual void initializeMask(const cv::Mat& /*src*/) {};
    };
    void setMaskGenerator(Ptr<MaskGenerator> maskGenerator);
    Ptr<MaskGenerator> getMaskGenerator();

    void setFaceDetectionMaskGenerator();

protected:
    Ptr<MaskGenerator> maskGenerator;
}

注意:当在不同的分类器之间切换的时候,需要手动释放,因为read内部没有释放上一次读取的分类器数据!

关于新旧格式的分类器参考《OpenCV存储解读之Adaboost分类器》



使用CascadeClassifier检测目标的过程

1) load分类器并调用empty函数检测是否load成功
// 读取stages
bool CascadeClassifier::Data::read(const FileNode &root)
{
    static const float THRESHOLD_EPS = 1e-5f;

    // load stage params
    string stageTypeStr = (string)root[CC_STAGE_TYPE];
    if( stageTypeStr == CC_BOOST )
        stageType = BOOST;
    else
        return false;
    printf("stageType: %s\n", stageTypeStr.c_str());

    string featureTypeStr = (string)root[CC_FEATURE_TYPE];
    if( featureTypeStr == CC_HAAR )
        featureType = FeatureEvaluator::HAAR;
    else if( featureTypeStr == CC_LBP )
        featureType = FeatureEvaluator::LBP;
    else if( featureTypeStr == CC_HOG )
        featureType = FeatureEvaluator::HOG;

    else
        return false;
    printf("featureType: %s\n", featureTypeStr.c_str());

    origWinSize.width = (int)root[CC_WIDTH];
    origWinSize.height = (int)root[CC_HEIGHT];
    CV_Assert( origWinSize.height > 0 && origWinSize.width > 0 );

    isStumpBased = (int)(root[CC_STAGE_PARAMS][CC_MAX_DEPTH]) == 1 ? true : false;
    printf("stumpBased: %d\n", isStumpBased);

    // load feature params
    FileNode fn = root[CC_FEATURE_PARAMS];
    if( fn.empty() )
        return false;
    // LBP的maxCatCount=256,其余特征都等于0
    ncategories = fn[CC_MAX_CAT_COUNT]; // ncategories=256/0
    int subsetSize = (ncategories + 31)/32,// subsetSize=8/0 // 强制类型转换取整,不是四舍五入
        nodeStep = 3 + ( ncategories>0 ? subsetSize : 1 ); //每组数值个数,nodeStep=11/4
    printf("subsetSize: %d, nodeStep: %d\n", subsetSize, nodeStep);
    // load stages
    fn = root[CC_STAGES];
    if( fn.empty() )
        return false;

    stages.reserve(fn.size());
    classifiers.clear();
    nodes.clear();

    FileNodeIterator it = fn.begin(), it_end = fn.end();

    for( int si = 0; it != it_end; si++, ++it )
    {
        FileNode fns = *it;
        Stage stage;
        stage.threshold = (float)fns[CC_STAGE_THRESHOLD] - THRESHOLD_EPS;
        fns = fns[CC_WEAK_CLASSIFIERS];
        if(fns.empty())
            return false;
        stage.ntrees = (int)fns.size();
        stage.first = (int)classifiers.size();
        printf("stage %d: ntrees: %d, first: %d\n", si, stage.ntrees, stage.first);
        stages.push_back(stage);
        classifiers.reserve(stages[si].first + stages[si].ntrees);

        FileNodeIterator it1 = fns.begin(), it1_end = fns.end();
        for( ; it1 != it1_end; ++it1 ) // weak trees
        {
            FileNode fnw = *it1;
            FileNode internalNodes = fnw[CC_INTERNAL_NODES];
            FileNode leafValues = fnw[CC_LEAF_VALUES];
            if( internalNodes.empty() || leafValues.empty() )
                return false;

            // 弱分类器中的节点
            DTree tree;
            tree.nodeCount = (int)internalNodes.size()/nodeStep;
            classifiers.push_back(tree);

            nodes.reserve(nodes.size() + tree.nodeCount);
            leaves.reserve(leaves.size() + leafValues.size());
            if( subsetSize > 0 ) // 针对LBP
                subsets.reserve(subsets.size() + tree.nodeCount*subsetSize);

            FileNodeIterator internalNodesIter = internalNodes.begin(), internalNodesEnd = internalNodes.end();
            // 保存每一个node
            for( ; internalNodesIter != internalNodesEnd; ) // nodes
            {
                DTreeNode node;
                node.left = (int)*internalNodesIter; ++internalNodesIter;
                node.right = (int)*internalNodesIter; ++internalNodesIter;
                node.featureIdx = (int)*internalNodesIter; ++internalNodesIter;
                // 针对LBP,获取8个数值
                if( subsetSize > 0 )
                {
                    for( int j = 0; j < subsetSize; j++, ++internalNodesIter )
                        subsets.push_back((int)*internalNodesIter);
                    node.threshold = 0.f;
                }
                else
                {
                    node.threshold = (float)*internalNodesIter; ++internalNodesIter;
                }
                nodes.push_back(node);
            }
            // 保存叶子节点
            internalNodesIter = leafValues.begin(), internalNodesEnd = leafValues.end();

            for( ; internalNodesIter != internalNodesEnd; ++internalNodesIter ) // leaves
                leaves.push_back((float)*internalNodesIter);
        }
    }

    return true;
}
// 读取stages与features
bool CascadeClassifier::read(const FileNode& root)
{
    // load stages
    if( !data.read(root) )
        return false;

    // load features,参考《图像特征->XXX特征之OpenCV-估计》
    featureEvaluator = FeatureEvaluator::create(data.featureType);
    FileNode fn = root[CC_FEATURES];
    if( fn.empty() )
        return false;

    return featureEvaluator->read(fn);
}
// 外部调用的函数
bool CascadeClassifier::load(const string& filename)
{
    oldCascade.release();
    data = http://www.mamicode.com/Data();
    featureEvaluator.release();
    // 读取新格式的分类器
    FileStorage fs(filename, FileStorage::READ);
    if( !fs.isOpened() )
        return false;

    if( read(fs.getFirstTopLevelNode()) )
        return true;

    fs.release();
    // 读取新格式失败则读取旧格式的分类器
    oldCascade = Ptr<CvHaarClassifierCascade>((CvHaarClassifierCascade*)cvLoad(filename.c_str(), 0, 0, 0));
    return !oldCascade.empty();
}
2) 调用detectMultiScale函数进行多尺度检测,该函数可以使用老分类器进行检测也可以使用新分类器进行检测

2.1 如果load的为旧格式的分类器则使用cvHaarDetectObjectsForROC进行检测,flags参数只对旧格式的分类器有效,参考《OpenCV函数解读之cvHaarDetectObjects》
    if( isOldFormatCascade() )
    {
        MemStorage storage(cvCreateMemStorage(0));
        CvMat _image = image;
        CvSeq* _objects = cvHaarDetectObjectsForROC( &_image, oldCascade, storage, rejectLevels, levelWeights, scaleFactor,
                                              minNeighbors, flags, minObjectSize, maxObjectSize, outputRejectLevels );
        vector<CvAvgComp> vecAvgComp;
        Seq<CvAvgComp>(_objects).copyTo(vecAvgComp);
        objects.resize(vecAvgComp.size());
        std::transform(vecAvgComp.begin(), vecAvgComp.end(), objects.begin(), getRect());
        return;
    }
2.2 新格式分类器多尺度检测
     for( double factor = 1; ; factor *= scaleFactor )
     {
        Size originalWindowSize = getOriginalWindowSize();

        Size windowSize( cvRound(originalWindowSize.width*factor), cvRound(originalWindowSize.height*factor) );
        Size scaledImageSize( cvRound( grayImage.cols/factor ), cvRound( grayImage.rows/factor ) );
        Size processingRectSize( scaledImageSize.width-originalWindowSize.width + 1, scaledImageSize.height-originalWindowSize.height + 1 );

        if( processingRectSize.width <= 0 || processingRectSize.height <= 0 )
            break;
        if( windowSize.width > maxObjectSize.width || windowSize.height > maxObjectSize.height )
            break;
        if( windowSize.width < minObjectSize.width || windowSize.height < minObjectSize.height )
            continue;
        // 缩放图像
        Mat scaledImage( scaledImageSize, CV_8U, imageBuffer.data );
        resize( grayImage, scaledImage, scaledImageSize, 0, 0, CV_INTER_LINEAR );
        // 计算步长
        int yStep;
        if( getFeatureType() == cv::FeatureEvaluator::HOG )
        {
            yStep = 4;
        }
        else
        {
            yStep = factor > 2. ? 1 : 2;
        }
        // 并行个数以及大小,按照列进行并行处理
        int stripCount, stripSize;
        // 是否采用TBB进行优化
    #ifdef HAVE_TBB
        const int PTS_PER_THREAD = 1000;
        stripCount = ((processingRectSize.width/yStep)*(processingRectSize.height + yStep-1)/yStep + PTS_PER_THREAD/2)/PTS_PER_THREAD;
        stripCount = std::min(std::max(stripCount, 1), 100);
        stripSize = (((processingRectSize.height + stripCount - 1)/stripCount + yStep-1)/yStep)*yStep;
    #else
        stripCount = 1;
        stripSize = processingRectSize.height;
    #endif
        // 调用单尺度检测函数进行检测
        if( !detectSingleScale( scaledImage, stripCount, processingRectSize, stripSize, yStep, factor, candidates,
            rejectLevels, levelWeights, outputRejectLevels ) )
            break;
    }

2.3 合并检测结果
    objects.resize(candidates.size());
    std::copy(candidates.begin(), candidates.end(), objects.begin());

    if( outputRejectLevels )
    {
        groupRectangles( objects, rejectLevels, levelWeights, minNeighbors, GROUP_EPS );
    }
    else
    {
        groupRectangles( objects, minNeighbors, GROUP_EPS );
    }


单尺度检测函数流程
2.2.1 根据所载入的特征计算积分图、积分直方图等
    // 计算当前图像的积分图,参考《图像特征->XXX特征之OpenCV-估计》
    if( !featureEvaluator->setImage( image, data.origWinSize ) )
        return false;
2.2.2 根据是否输出检测级数并行目标检测
    vector<Rect> candidatesVector;
    vector<int> rejectLevels;
    vector<double> levelWeights;
    Mutex mtx;
    if( outputRejectLevels )
    {
        parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yStep, factor,
            candidatesVector, rejectLevels, levelWeights, true, currentMask, &mtx));
        levels.insert( levels.end(), rejectLevels.begin(), rejectLevels.end() );
        weights.insert( weights.end(), levelWeights.begin(), levelWeights.end() );
    }
    else
    {
         parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yStep, factor,
            candidatesVector, rejectLevels, levelWeights, false, currentMask, &mtx));
    }
    candidates.insert( candidates.end(), candidatesVector.begin(), candidatesVector.end() );

CascadeClassifierInvoker函数的operator()实现具体的检测过程
    // 对于没有并行时range.start=0,range.end=1
    void operator()(const Range& range) const
    {
        Ptr<FeatureEvaluator> evaluator = classifier->featureEvaluator->clone();

        Size winSize(cvRound(classifier->data.origWinSize.width * scalingFactor), 
                     cvRound(classifier->data.origWinSize.height * scalingFactor));
        // strip=processingRectSize.height
        int y1 = range.start * stripSize; // 0
        int y2 = min(range.end * stripSize, processingRectSize.height); // processSizeRect.height也就是可以处理的高度,已经减去窗口高度
        for( int y = y1; y < y2; y += yStep )
        {
            for( int x = 0; x < processingRectSize.width; x += yStep )
            {
                if ( (!mask.empty()) && (mask.at<uchar>(Point(x,y))==0)) {
                    continue;
                }
                // result=1表示通过了所有的分类器 <=0表示失败的级数
                // gypWeight表示返回的阈值
                double gypWeight;
                int result = classifier->runAt(evaluator, Point(x, y), gypWeight);
// 输出LOG
#if defined (LOG_CASCADE_STATISTIC)
                logger.setPoint(Point(x, y), result);
#endif 
                // 当返回级数的时候可以最后三个分类器不通过
                if( rejectLevels )
                {
                    if( result == 1 )
                        result = -(int)classifier->data.stages.size();
                    // 可以最后三个分类器不通过
                    if( classifier->data.stages.size() + result < 4 )
                    {
                        mtx->lock();
                        rectangles->push_back(Rect(cvRound(x*scalingFactor), cvRound(y*scalingFactor), winSize.width, winSize.height));
                        rejectLevels->push_back(-result);
                        levelWeights->push_back(gypWeight);
                        mtx->unlock();
                    }
                }
                // 不返回级数的时候通过所有的分类器才保存起来
                else if( result > 0 )
                {
                    mtx->lock();
                    rectangles->push_back(Rect(cvRound(x*scalingFactor), cvRound(y*scalingFactor),
                                               winSize.width, winSize.height));
                    mtx->unlock();
                }
                // 如果一级都没有通过那么加大搜索步长
                if( result == 0 )
                    x += yStep;
            }
        }
    }

runAt函数实现某一检测窗口的检测
int CascadeClassifier::runAt( Ptr<FeatureEvaluator>& evaluator, Point pt, double& weight )
{
    CV_Assert( oldCascade.empty() );

    assert( data.featureType == FeatureEvaluator::HAAR ||
            data.featureType == FeatureEvaluator::LBP ||
            data.featureType == FeatureEvaluator::HOG );
    // 设置某一点处的特征,参考《图像特征->XXX特征之OpenCV-估计》
    if( !evaluator->setWindow(pt) )
        return -1;
    // 如果为树桩,没有树枝
    if( data.isStumpBased )
    {
        if( data.featureType == FeatureEvaluator::HAAR )
            return predictOrderedStump<HaarEvaluator>( *this, evaluator, weight );
        else if( data.featureType == FeatureEvaluator::LBP )
            return predictCategoricalStump<LBPEvaluator>( *this, evaluator, weight );
        else if( data.featureType == FeatureEvaluator::HOG )
            return predictOrderedStump<HOGEvaluator>( *this, evaluator, weight );
        else
            return -2;
    }
    // 每个弱分类器不止一个node
    else
    {
        if( data.featureType == FeatureEvaluator::HAAR )
            return predictOrdered<HaarEvaluator>( *this, evaluator, weight );
        else if( data.featureType == FeatureEvaluator::LBP )
            return predictCategorical<LBPEvaluator>( *this, evaluator, weight );
        else if( data.featureType == FeatureEvaluator::HOG )
            return predictOrdered<HOGEvaluator>( *this, evaluator, weight );
        else
            return -2;
    }
}

predictOrdered*函数实现判断当前检测窗口的判断
// HAAR与HOG特征的多node检测
template<class FEval>
inline int predictOrdered( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )
{
    int nstages = (int)cascade.data.stages.size();
    int nodeOfs = 0, leafOfs = 0;
    FEval& featureEvaluator = (FEval&)*_featureEvaluator;
    float* cascadeLeaves = &cascade.data.leaves[0];
    CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];
    CascadeClassifier::Data::DTree* cascadeWeaks = &cascade.data.classifiers[0];
    CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

    // 遍历每个强分类器
    for( int si = 0; si < nstages; si++ )
    {
        CascadeClassifier::Data::Stage& stage = cascadeStages[si];
        int wi, ntrees = stage.ntrees;
        sum = 0;
        // 遍历每个弱分类器
        for( wi = 0; wi < ntrees; wi++ )
        {
            CascadeClassifier::Data::DTree& weak = cascadeWeaks[stage.first + wi];
            int idx = 0, root = nodeOfs;
            // 遍历每个节点
            do
            {
                // 选择一个node:root和idx初始化为0,即第一个node
                CascadeClassifier::Data::DTreeNode& node = cascadeNodes[root + idx];
                // 计算当前node特征池编号下的特征值
                double val = featureEvaluator(node.featureIdx);
                // 如果val小于node阈值则选择左子树,否则选择右子树
                idx = val < node.threshold ? node.left : node.right;
            } while( idx > 0 );
            // 累加最终的叶子节点
            sum += cascadeLeaves[leafOfs - idx];
            nodeOfs += weak.nodeCount;
            leafOfs += weak.nodeCount + 1;
        }
        // 判断所有叶子节点累加和是否小于强分类器阈值,小于强分类器阈值则失败
        if( sum < stage.threshold )
            return -si;
    }
    // 通过了所有的强分类器返回1,否则返回失败的分类器
    return 1;
}

// LBP特征的多node检测
template<class FEval>
inline int predictCategorical( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )
{
    int nstages = (int)cascade.data.stages.size();
    int nodeOfs = 0, leafOfs = 0;
    FEval& featureEvaluator = (FEval&)*_featureEvaluator;
    size_t subsetSize = (cascade.data.ncategories + 31)/32;
    int* cascadeSubsets = &cascade.data.subsets[0];
    float* cascadeLeaves = &cascade.data.leaves[0];
    CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];
    CascadeClassifier::Data::DTree* cascadeWeaks = &cascade.data.classifiers[0];
    CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

    for(int si = 0; si < nstages; si++ )
    {
        CascadeClassifier::Data::Stage& stage = cascadeStages[si];
        int wi, ntrees = stage.ntrees;
        sum = 0;

        for( wi = 0; wi < ntrees; wi++ )
        {
            CascadeClassifier::Data::DTree& weak = cascadeWeaks[stage.first + wi];
            int idx = 0, root = nodeOfs;
            do
            {
                CascadeClassifier::Data::DTreeNode& node = cascadeNodes[root + idx];
                // c为0-255之间的数
                int c = featureEvaluator(node.featureIdx);
                // 获取当前node的subset头位置
                const int* subset = &cascadeSubsets[(root + idx)*subsetSize]; // LBP:subsetSize=8
                // 判断选择左子树还是右子树
                idx = (subset[c>>5] & (1 << (c & 31))) ? node.left : node.right;
                // c>>5表示将c右移5位,选择高3位,0-7之间
                // c&31表示低5位,1<<(c&31)选择低5位后左移1位
                // 将上面的数按位与,如果最后结果不为0表示选择左子树,否则选择右子树
            }
            while( idx > 0 );
            sum += cascadeLeaves[leafOfs - idx];
            nodeOfs += weak.nodeCount;
            leafOfs += weak.nodeCount + 1;
        }
        if( sum < stage.threshold )
            return -si;
    }
    return 1;
}

// HAAR与HOG特征的单node检测
template<class FEval>
inline int predictOrderedStump( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )
{
    int nodeOfs = 0, leafOfs = 0;
    FEval& featureEvaluator = (FEval&)*_featureEvaluator;
    float* cascadeLeaves = &cascade.data.leaves[0];
    CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];
    CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

    int nstages = (int)cascade.data.stages.size();
    // 遍历每个强分类器
    for( int stageIdx = 0; stageIdx < nstages; stageIdx++ )
    {
        CascadeClassifier::Data::Stage& stage = cascadeStages[stageIdx];
        sum = 0.0;

        int ntrees = stage.ntrees;
        // 遍历每个弱分类器
        for( int i = 0; i < ntrees; i++, nodeOfs++, leafOfs+= 2 )
        {
            CascadeClassifier::Data::DTreeNode& node = cascadeNodes[nodeOfs];
            double value = http://www.mamicode.com/featureEvaluator(node.featureIdx);
            sum += cascadeLeaves[ value < node.threshold ? leafOfs : leafOfs + 1 ];
        }

        if( sum < stage.threshold )
            return -stageIdx;
    }

    return 1;
}
// LBP特征的单node检测
template<class FEval>
inline int predictCategoricalStump( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum )
{
    int nstages = (int)cascade.data.stages.size();
    int nodeOfs = 0, leafOfs = 0;
    FEval& featureEvaluator = (FEval&)*_featureEvaluator;
    size_t subsetSize = (cascade.data.ncategories + 31)/32;
    int* cascadeSubsets = &cascade.data.subsets[0];
    float* cascadeLeaves = &cascade.data.leaves[0];
    CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0];
    CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0];

#ifdef HAVE_TEGRA_OPTIMIZATION
    float tmp = 0; // float accumulator -- float operations are quicker
#endif
    for( int si = 0; si < nstages; si++ )
    {
        CascadeClassifier::Data::Stage& stage = cascadeStages[si];
        int wi, ntrees = stage.ntrees;
#ifdef HAVE_TEGRA_OPTIMIZATION
        tmp = 0;
#else
        sum = 0;
#endif

        for( wi = 0; wi < ntrees; wi++ )
        {
            CascadeClassifier::Data::DTreeNode& node = cascadeNodes[nodeOfs];
            int c = featureEvaluator(node.featureIdx);
            const int* subset = &cascadeSubsets[nodeOfs*subsetSize];
#ifdef HAVE_TEGRA_OPTIMIZATION
            tmp += cascadeLeaves[ subset[c>>5] & (1 << (c & 31)) ? leafOfs : leafOfs+1];
#else
            sum += cascadeLeaves[ subset[c>>5] & (1 << (c & 31)) ? leafOfs : leafOfs+1];
#endif
            nodeOfs++;
            leafOfs += 2;
        }
#ifdef HAVE_TEGRA_OPTIMIZATION
        if( tmp < stage.threshold ) {
            sum = (double)tmp;
            return -si;
        }
#else
        if( sum < stage.threshold )
            return -si;
#endif
    }

#ifdef HAVE_TEGRA_OPTIMIZATION
    sum = (double)tmp;
#endif

    return 1;
}
}

OpenCV中CascadeClassifier类实现多尺度检测源码解析