OpenCV 级联检测器Cascadedetect人脸检测源码理解与分析

OpenCV的级联分类器分为两部分,训练与检测。训练那块代码在apps目录下,有旧分类器haartraining和新分类器traincascade各自的代码。目前没去看,训练用OpenCV给的程序目前就足够了。

检测这块代码在以下3个文件中

E:\opencv\sources\modules\objdetect\include\opencv2\objdetect\objdetect.hpp;

定义了旧分类器计算Haar特征值的结构体:

CvHaarFeatureHaar特征结构体),

CvHaarClassifierHaar特征分类器结构体),

CvHaarStageClassifierHaar强分类器结构体),

CvHaarClassifierCascadeHaar强分类器级联结构体)

定义了类:

FeatureEvaluator(特征值计算类,新分类器使用),

CascadeClassifier(级联分类器)类

E:\opencv\sources\modules\objdetect\src\cascadedetect.hpp

定义了类,以下几个类都是继承自FeatureEvaluator:

HaarEvaluatorHaar特征值计算类),

LBPEvaluatorLBP特征值计算类),

HOGEvaluatorHOG特征值计算类)

定义了函数:

predictOrdered(计算并验证是否通过级联强分类器)

predictCategorical

predictOrderedStump

predictCategoricalStump

E:\opencv\sources\modules\objdetect\src\cascadedetect.cpp

检测时首先调用CascadeClassifier::load(const string& filename)函数载入分类器xml文件,如果是旧分类器,xml中的信息读取到Ptr oldCascade保护变量中,否则读入到强级联分类器数据类对象Data中,根据特征创建对应的特征计算类,得到指针Ptr featureEvaluator的值,Data对象定义如下

//CascadeClassifierProtected保护子类

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; //BOOSTboostType:GABRAB
        int featureType; //HAARHOGLBP
        int ncategories; //maxCatCount,LBP256,其余为0
        Size origWinSize;

        vector stages;
        vector classifiers;
        vector nodes;
        vector leaves;
        vector subsets;
    };

Data data; //CascadeClassifier的保护子类对象成员,存放强级联分类器数据。

 

CascadeClassifier有两个多尺度检测函数的实现(如下),读取xml后可以调用这两个函数进行级联强分类器检测(使用不同的xml就是不同的检测,如人脸检测,眼睛,鼻子,嘴巴等检测)

CV_WRAP virtual void detectMultiScale( const Mat& image,//图像,

        CV_OUT vector& objects,//输出矩形框

        double scaleFactor=1.1, //缩放比例,必须大于1

        int minNeighbors=3 ,//合并窗口最小间距,每个候选矩阵至少需要包含的附近元素个数(一个目标至少要检测多少次)
        int 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& objects,
       vector& rejectLevels, //一个检测目标未通过的最后几个级数
       vector& levelWeights,//未通过的那个强分类器CART树叶子节点值累加和(小于该强分类器阈值)
       double scaleFactor=1.1,
       int minNeighbors=3, int flags=0,
       Size minSize=Size(),
       Size maxSize=Size(),
       bool outputRejectLevels=false );//只有该值为true时,才能输出前两个新参数

 

调用时一般不需要返回通过的级联分类器级数,首先调用该无输出级数形式函数

void CascadeClassifier::detectMultiScale( const Mat& image, vector& objects,
                                          double scaleFactor, int minNeighbors,
                                          int flags, Size minObjectSize, Size maxObjectSize)
{
    vector fakeLevels;
    vector fakeWeights;
    detectMultiScale( image, objects, fakeLevels, fakeWeights, scaleFactor,
     minNeighbors, flags, minObjectSize, maxObjectSize, false );//构造输出级数容器,统一调用有级数输出的函数
}

void CascadeClassifier::detectMultiScale( const Mat& image, vector& objects,
                                          vector& rejectLevels,
                                          vector& levelWeights,
                                          double scaleFactor, int minNeighbors,
                                          int flags, Size minObjectSize, Size maxObjectSize,//这个flags只有使用旧分类器xml文件时才有效,现在一般使用新分类器格式的xml
                                          bool outputRejectLevels )//需要输出检测级数
{
    const double GROUP_EPS = 0.2; //相似矩形框聚类参数
   CV_Assert( scaleFactor > 1 && image.depth() == CV_8U ); //非真即为0,即这两个条件必须为真,检查缩放参数是否大于1,图像位深度是否为8
    if( empty() )//检测是否融入参数,即使创建了对象没有初始化加载也是空
        return;


    if( isOldFormatCascade() )//是否是旧格式的分类器
    {
        MemStorage storage(cvCreateMemStorage(0));
        CvMat _image = image;
        CvSeq* _objects = cvHaarDetectObjectsForROC( &_image, oldCascade, storage, rejectLevels, levelWeights, scaleFactor,
                                              minNeighbors, flags, minObjectSize, maxObjectSize, outputRejectLevels );
        vector vecAvgComp;
        Seq(_objects).copyTo(vecAvgComp);
        objects.resize(vecAvgComp.size());
        std::transform(vecAvgComp.begin(), vecAvgComp.end(), objects.begin(), getRect());
        return;
    }


//新格式的分类器检测流程看下面


    objects.clear();//清空一下检测结果存放容器
    if (!maskGenerator.empty()) {//class CV_EXPORTS MaskGenerator在类CascadeClassifier中定义,用于生成mask,当检测窗口中的第一个点在mask矩阵中时跳过检测。Ptr maskGenerator为其保护成员变量,后面再分析Mask如何使用
        maskGenerator->initializeMask(image);//初始化mask生成器
    }
    if( maxObjectSize.height == 0 || maxObjectSize.width == 0 )
        maxObjectSize = image.size();//默认检测目标最大为图像本身大小
    Mat grayImage = image;
    if( grayImage.channels() > 1 )//通道数大于1
    {
        Mat temp;
        cvtColor(grayImage, temp, CV_BGR2GRAY);//转为灰度图
        grayImage = temp;
    }

    Mat imageBuffer(image.rows + 1, image.cols + 1, CV_8U);//图像缓存,多加一行一列?
    vector candidates;//存放可能的目标
    for( double factor = 1; ; factor *= scaleFactor )//多尺度,每次放大ScaleFactor
    {
        Size originalWindowSize = getOriginalWindowSize();//  20  20    xml文件中的着两行即为训练的目标窗口大小
        Size windowSize( cvRound(originalWindowSize.width*factor), cvRound(originalWindowSize.height*factor) );//检测窗口每次放大ScaleFactor
        Size scaledImageSize( cvRound( grayImage.cols/factor ), cvRound( grayImage.rows/factor ) );
        Size processingRectSize( scaledImageSize.width - originalWindowSize.width, scaledImageSize.height - originalWindowSize.height );//可供原始检测窗口左上顶点移动的矩形区域

        if( processingRectSize.width <= 0 || processingRectSize.height <= 0 )//检测窗口可移动区域小于0,说明图像比20*20还小,退出
            break;
        if( windowSize.width > maxObjectSize.width || windowSize.height > maxObjectSize.height )//窗口大于最大检测目标,退出,如果没有设置最大检测窗口,当originalWindowSize*factor > image.size时退出,如20*20一直放大到比输入图像大时退出。
            break;
        if( windowSize.width < minObjectSize.width || windowSize.height < minObjectSize.height )//窗口小于于最小检测目标,跳过
            continue;
        Mat scaledImage( scaledImageSize, CV_8U, imageBuffer.data );//imageBuffer不是还没有数据?
        resize( grayImage, scaledImage, scaledImageSize, 0, 0, CV_INTER_LINEAR );//灰度图缩小后赋给scaledImage

        int yStep;//y步长
        if( getFeatureType() == cv::FeatureEvaluator::HOG )//HOG特征步长设置为4
        {
            yStep = 4;
        }
        else
        {
            yStep = factor > 2. ? 1 : 2;//缩放倍数大于2时,步长为1,否则为2
        }
        int stripCount, stripSize;// 并行计算线程个数以及大小,分行并行处理
        const int PTS_PER_THREAD = 1000;//预定动作时间标准系统也称预定时间标准系统(Predetermined Time System
        stripCount = ((processingRectSize.width/yStep)*(processingRectSize.height + yStep-1)/yStep + PTS_PER_THREAD/2)/PTS_PER_THREAD;//后面TBB加速要生成的并行线程数
        stripCount = std::min(std::max(stripCount, 1), 100);
        stripSize = (((processingRectSize.height + stripCount - 1)/stripCount + yStep-1)/yStep)*yStep;
        if( !detectSingleScale( scaledImage, stripCount, processingRectSize, stripSize, yStep, factor, candidates,
            rejectLevels, levelWeights, outputRejectLevels ) )//调用单尺度检测函数
            break;
    }
    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 );//合并检测结果,一个人脸可能被多处检测到,根据minNeighbors进行合并
    }
}

 

单尺度检测函数

bool CascadeClassifier::detectSingleScale( const Mat& image, int stripCount, Size processingRectSize,
                                           int stripSize, int yStep, double factor, vector& candidates,
                                           vector& levels, vector& weights, bool outputRejectLevels )
{
    if( !featureEvaluator->setImage( image, data.origWinSize ) )//对图像做积分图,Ptr featureEvaluatorCascadeClassifier类成员指针对象,当采用不同的特征时,使用不同的类的setImage()
        return false;


#if defined (LOG_CASCADE_STATISTIC)
    logger.setImage(image);
#endif
    Mat currentMask;
    if (!maskGenerator.empty()) {
        currentMask=maskGenerator->generateMask(image);//生成mask
    }
    vector candidatesVector;
    vector rejectLevels;
    vector levelWeights;
    Mutex mtx;
    if( outputRejectLevels )
    {
        parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yStep, factor,
            candidatesVector, rejectLevels, levelWeights, true, currentMask, &mtx));//class CascadeClassifierInvoker定义在单尺度检测函数之前
        levels.insert( levels.end(), rejectLevels.begin(), rejectLevels.end() );
        weights.insert( weights.end(), levelWeights.begin(), levelWeights.end() );
    }
    else
    {
    //这里是检测过程中的关键,使用parallel_for是为了TBB加速中使用,生成stripCount个平行线程(每个线程生成一个CascadeClassifierInvoker),在每个CascadeClassifierInvoker中分配当前缩放图像的N行做检测,这是TBB利用多线程做的加速计算
         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() );

#if defined (LOG_CASCADE_STATISTIC)
    logger.write();
#endif


    return true;
}

 

CascadeClassifierInvoker级联分类器调用器类,实现了具体的单尺度目标检测

class CascadeClassifierInvoker : public ParallelLoopBody
{
public:
    CascadeClassifierInvoker( CascadeClassifier& _cc, Size _sz1, int _stripSize, int _yStep, double _factor,
        vector& _vec, vector& _levels, vector& _weights, bool outputLevels, const Mat& _mask, Mutex* _mtx)
    {
        classifier = &_cc;
        processingRectSize = _sz1;
        stripSize = _stripSize;
        yStep = _yStep;
        scalingFactor = _factor;
        rectangles = &_vec;
        rejectLevels = outputLevels ? &_levels : 0;
        levelWeights = outputLevels ? &_weights : 0;
        mask = _mask;
        mtx = _mtx;
    }

//没有并行时,range.start = 0,range.end =1;
    void operator()(const Range& range) const//重载了()操作符
    {
        Ptr evaluator = classifier->featureEvaluator->clone();//根据确认的不同特征指针类型调用不同的特征类型的克隆函数


        Size winSize(cvRound(classifier->data.origWinSize.width * scalingFactor), cvRound(classifier->data.origWinSize.height * scalingFactor));


        int y1 = range.start * stripSize;
        int y2 = min(range.end * stripSize, processingRectSize.height);

//并行计算应该是用range将图像检测按几行为一个线程进行并行计算的
        for( int y = y1; y < y2; y += yStep )
        {
            for( int x = 0; x < processingRectSize.width; x += yStep )
            {
                if ( (!mask.empty()) && (mask.at(Point(x,y))==0)) {//检测该点是否在maks内,是则跳过检测
                    continue;
                }


                double gypWeight;
                int result = classifier->runAt(evaluator, Point(x, y), gypWeight);//result =1表示通过所有分类器,result<0表示失败的级数。

//输出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;
            }
        }
    }

CascadeClassifier* classifier;

    vector* rectangles;

    Size processingRectSize;

    int stripSize, yStep;

    double scalingFactor;

    vector *rejectLevels;

    vector *levelWeights;

    Mat mask;

    Mutex* mtx;

};

 

 

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


    assert( data.featureType == FeatureEvaluator::HAAR ||
            data.featureType == FeatureEvaluator::LBP ||
            data.featureType == FeatureEvaluator::HOG );


    if( !evaluator->setWindow(pt) )//设置当前检测窗口,改变了计算特征值需要的offset
        return -1;
    if( data.isStumpBased )//CART树只有树桩
    {
        if( data.featureType == FeatureEvaluator::HAAR )
            return predictOrderedStump( *this, evaluator, weight );
        else if( data.featureType == FeatureEvaluator::LBP )
            return predictCategoricalStump( *this, evaluator, weight );
        else if( data.featureType == FeatureEvaluator::HOG )
            return predictOrderedStump( *this, evaluator, weight );
        else
            return -2;
    }
    else //根据不同的特征类型调用CascadeClassifier的友元函数predictOrdered()
    {
        if( data.featureType == FeatureEvaluator::HAAR )
            return predictOrdered( *this, evaluator, weight );
        else if( data.featureType == FeatureEvaluator::LBP )
            return predictCategorical( *this, evaluator, weight );
        else if( data.featureType == FeatureEvaluator::HOG )
            return predictOrdered( *this, evaluator, weight );
        else
            return -2;
    }
}

 

//----------------------------------------------predictOrdered 计算特征值并预测函数-------------------------------------


template //这里的FEvalHaarEvaluator || LBPEvaluator || HOGEvaluator
inline int predictOrdered( CascadeClassifier& cascade, Ptr &_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:rootindex初始化为0,即第一个node
                CascadeClassifier::Data::DTreeNode& node = cascadeNodes[root + idx];
                double val = featureEvaluator(node.featureIdx);//计算特征池编号下的值,featureIdxxml文件取出来的所有特征池的编号,每个弱分类器都会指明使用哪个特征。这里的featureEvaluator使用不同类型的特征,则调用不同特征特征值计算器的实现函数
                idx = val < node.threshold ? node.left : node.right;//如果val小于node阈值则选择左子树,否则右子树
            }
            while( idx > 0 );//CART树找到了最终的叶子节点
            sum += cascadeLeaves[leafOfs - idx];//累加最终的叶子节点值,强分类器阈值由多个弱分类器的阈值累加得到,
            nodeOfs += weak.nodeCount;
            leafOfs += weak.nodeCount + 1;
        }
        if( sum < stage.threshold )//判断是否小于强分类器阈值,是则表示失败了,返回失败的强分类器级数,因此需要通过全部强分类器才算成功。
            return -si;
    }
    return 1;
}

 

不同的特征类型特征值计算器,这里只看Haar特征值的计算

//----------------------------------------------  HaarEvaluator ---------------------------------------
class HaarEvaluator : public FeatureEvaluator//继承自FeatureEvaluator
{
public:
    struct Feature
    {
        Feature();
        float calc( int offset ) const;//计算特征值
        void updatePtrs( const Mat& sum );//调用setImage时,调用该函数更新组成该特征的矩形框(A+BB,在xml中可以看到相应的rect)在积分图存储内存的起始位置指针
        bool read( const FileNode& node );//读取特征池的特征到节点
        bool tilted;//是否是标准特征,还是旋转45度的特征
        enum { RECT_NUM = 3 };//Haar特征最多只要3个矩形即可描述
        struct
        {
            Rect r;
            float weight;//矩形的权重
        } rect[RECT_NUM];
        const int* p[RECT_NUM][4];//用于存储第一个rect、第二个rect、第三个rect4个顶点积分值在积分图内存区域的起始指针,每个rect的像素和由4个顶点的积分值计算得到
    };
    HaarEvaluator();
    virtual ~HaarEvaluator();
    virtual bool read( const FileNode& node );
    virtual Ptr clone() const;
    virtual int getFeatureType() const { return FeatureEvaluator::HAAR; }
    virtual bool setImage(const Mat&, Size origWinSize);//计算积分图
    virtual bool setWindow(Point pt);
    double operator()(int featureIdx) const//重载()操作符
    { return featuresPtr[featureIdx].calc(offset) * varianceNormFactor; }
//调用calc函数进行特征值计算,offset在调用setWindow移动窗口时已确定
    virtual double calcOrd(int featureIdx) const
    { return (*this)(featureIdx); }
protected:
    Size origWinSize;
    Ptr > features;
    Feature* featuresPtr; // optimization
    bool hasTiltedFeatures;
    Mat sum0, sqsum0, tilted0;
    Mat sum, sqsum, tilted;
    Rect normrect;
    const int *p[4];
    const double *pq[4];
    int offset;
    double varianceNormFactor;
};

 

//检测窗口下Haar特征值的计算

 

#define CALC_SUM_(p0, p1, p2, p3, offset)((p0)[offset] - (p1)[offset] - (p2)[offset] + (p3)[offset])
#define CALC_SUM(rect,offset) CALC_SUM_((rect)[0], (rect)[1], (rect)[2], (rect)[3], offset)

//计算方法:不同的特征用统一的格式来描述特征值的计算,左边为统一格式,右边为不同特征下白色区域减去黑色区域的像素和表达式,如下

A+B*-1+B*2=B-A   //------A+Bxml中特征的第一个rectB为第二个rect-1,2为各自的权重

A+B+C*-1+B*3 = 2*B-A-C //---------A+B+C、B为两个rect,-1,3为各自权重

A+B+C+D*-1+2*B + 2*C = B+C-(A+D) //-------A+B+C+D、B、C为三个rect,-1,2,2为各自权重,可以到xml特征池看看具体的特征矩形框

inline float HaarEvaluator::Feature :: calc( int _offset ) const
{
    float ret = rect[0].weight * CALC_SUM(p[0], _offset) + rect[1].weight * CALC_SUM(p[1], _offset);//const int* p[RECT_NUM][4] 指针数组在这里用到
    if( rect[2].weight != 0.0f )
        ret += rect[2].weight * CALC_SUM(p[2], _offset);
    return ret;
}

/********************计算完毕**************************/

//补充

bool HaarEvaluator::setImage( const Mat &image, Size _origWinSize )
{
    int rn = image.rows+1, cn = image.cols+1;
    origWinSize = _origWinSize;
    normrect = Rect(1, 1, origWinSize.width-2, origWinSize.height-2);//这是要去掉边框作为计算内容?
    if (image.cols < origWinSize.width || image.rows < origWinSize.height)
        return false;
    if( sum0.rows < rn || sum0.cols < cn )
    {
        sum0.create(rn, cn, CV_32S);
        sqsum0.create(rn, cn, CV_64F);
        if (hasTiltedFeatures)
            tilted0.create( rn, cn, CV_32S);
    }
    sum = Mat(rn, cn, CV_32S, sum0.data);
    sqsum = Mat(rn, cn, CV_64F, sqsum0.data);


    if( hasTiltedFeatures )//旋转特征
    {
        tilted = Mat(rn, cn, CV_32S, tilted0.data);
        integral(image, sum, sqsum, tilted);
    }
    else
        integral(image, sum, sqsum);//计算正常Haar特征积分图sum,像素平方和积分图sqsum
    const int* sdata = (const int*)sum.data;
    const double* sqdata = (const double*)sqsum.data;
    size_t sumStep = sum.step/sizeof(sdata[0]);//一行的数据长度/数据类型大小=每行的数据个数?
    size_t sqsumStep = sqsum.step/sizeof(sqdata[0]);
    CV_SUM_PTRS( p[0], p[1], p[2], p[3], sdata, normrect, sumStep );//存储normrect窗口的四个顶点积分图数据指针
    CV_SUM_PTRS( pq[0], pq[1], pq[2], pq[3], sqdata, normrect, sqsumStep );
    size_t fi, nfeatures = features->size();//特征池下的Haar特征个数
    for( fi = 0; fi < nfeatures; fi++ )
        featuresPtr[fi].updatePtrs( !featuresPtr[fi].tilted ? sum : tilted );//更新每个特征组成的矩形框(A+BB)在积分图存储内存的起始位置指针,后面再加上offset移动该特征窗口
    return true;
}

 

bool  HaarEvaluator::setWindow( Point pt )
{
    if( pt.x < 0 || pt.y < 0 ||
        pt.x + origWinSize.width >= sum.cols ||
        pt.y + origWinSize.height >= sum.rows )
        return false;
    size_t pOffset = pt.y * (sum.step/sizeof(int)) + pt.x;
    size_t pqOffset = pt.y * (sqsum.step/sizeof(double)) + pt.x;
    int valsum = CALC_SUM(p, pOffset);
    double valsqsum = CALC_SUM(pq, pqOffset);
    double nf = (double)normrect.area() * valsqsum - (double)valsum * valsum;
    if( nf > 0. )
        nf = sqrt(nf);
    else
        nf = 1.;
    varianceNormFactor = 1./nf;
    offset = (int)pOffset; //更新了offset
    return true;
}

 


class CV_EXPORTS MaskGenerator //编写子类继承,重写虚函数,实现Mask
    {
    public:
        virtual ~MaskGenerator() {}
        virtual cv::Mat generateMask(const cv::Mat& src)=0;//单尺度检测函数调用该函数生成新的Mask
        virtual void initializeMask(const cv::Mat& /*src*/) {};//多尺度检测函数调用该函数初始化MaskGenerator
    };

 void setMaskGenerator(Ptr maskGenerator);//CascadeClassifier类函数成员

 Ptr getMaskGenerator();
 void setFaceDetectionMaskGenerator();


你可能感兴趣的:(OpenCV,Cascadedetect,人脸检测,adaboost)