train_cascade 源码阅读之LBP特征

本文深入解读OpenCV的train_cascade源码,聚焦LBP特征。通过CvCascadeBoostTrainData对象的创建,讨论预计算阶段涉及的特征计算过程,特别是LBP特征的提取。在CvLBPEvaluator类中,详述了如何利用积分图计算原始LBP特征,以及在CvFeatureEvaluator初始化时确定像素偏移量的细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文以LBP特征为例,介绍了OpenCV中train_cascade数据初始化的过程。

1 在CvCascadeBoost中,创建了CvCascadeBoostTrainData对象。

bool CvCascadeBoost::
train(
        const CvFeatureEvaluator* _featureEvaluator,
        int _numSamples,
        int _precalcValBufSize, int _precalcIdxBufSize,
        const CvCascadeBoostParams& _params )
{
    bool isTrained = false;
    CV_Assert( !data );
    clear();
    data = new <strong>CvCascadeBoostTrainData</strong>(
                _featureEvaluator, _numSamples,
                _precalcValBufSize, _precalcIdxBufSize, _params )
   ……
}

2 在CvCascadeBoostTrainData中调用setData函数。

CvCascadeBoostTrainData::CvCascadeBoostTrainData( const CvFeatureEvaluator* _featureEvaluator,
                                                  int _numSamples,
                                                  int _precalcValBufSize, int _precalcIdxBufSize,
                                                  const CvDTreeParams& _params )
{
   <strong> setData</strong>( _featureEvaluator, _numSamples, _precalcValBufSize, _precalcIdxBufSize, _params );
}

3 在setData函数中调用了预先计算特征,也就是参数中preCalcValBufSize, preClacIdxBufSize预留的内存初始化的地方。

void CvCascadeBoostTrainData::setData( const CvFeatureEvaluator* _featureEvaluator,
                                       int _numSamples,
                                       int _precalcValBufSize, int _precalcIdxBufSize,
                                       const CvDTreeParams& _params )
{
   
 ……

    // precalculate valCache and set indices in buf
   <strong> precalculate();</strong>
 ……
    
}


4 在precalculate函数中,有初始化时计算一次特征的函数。

void CvCascadeBoostTrainData::precalculate()
{
    int minNum = MIN( numPrecalcVal, numPrecalcIdx);

    double proctime = -TIME( 0 );
    parallel_for_( Range(numPrecalcVal, numPrecalcIdx),
                   FeatureIdxOnlyPrecalc(featureEvaluator, buf, sample_count, is_buf_16u!=0) );
    parallel_for_( Range(0, minNum),
                   FeatureValAndIdxPrecalc(featureEvaluator, buf, &valCache, sample_count, is_buf_16u!=0) );
    parallel_for_( Range(minNum, numPrecalcVal),
                   <strong>FeatureValOnlyPrecalc(featureEvaluator, &valCache, sample_count) );</strong>
    cout << "Precalculation time: " << (proctime + TIME( 0 )) << endl;
}


5 在该函数内,用函数指针调用了featureEvaluator的一个operator(),如果是LBP特征的话,则调用的是LBP子类的对应函数。

struct FeatureValOnlyPrecalc : ParallelLoopBody
{
    FeatureValOnlyPrecalc( const CvFeatureEvaluator* _featureEvaluator, Mat* _valCache, int _sample_count )
    {
        featureEvaluator = _featureEvaluator;
        valCache = _valCache;
        sample_count = _sample_count;
    }
    void operator()( const Range& range ) const
    {
        for ( int fi = range.start; fi < range.end; fi++)
            for( int si = 0; si < sample_count; si++ )
                <strong>valCache->at<float>(fi,si) = (*featureEvaluator)( fi, si );</strong>
    }
    const CvFeatureEvaluator* featureEvaluator;
    Mat* valCache;
    int sample_count;
};


6 在CvLBPEvaluator中定义了operator()虚函数。并且它调用了calc函数,返回计算得到的LBP特征。

class CvLBPEvaluator : public CvFeatureEvaluator
{
public:
    virtual ~CvLBPEvaluator() {}
    virtual void init(
            const CvFeatureParams   *_featureParams,
            int                     _maxSampleCount,
            cv::Size                _winSize );
    virtual void setImage(
            const cv::Mat   & img,
            uchar           clsLabel,
            int             idx);
  <strong>  virtual float operator()(
            int featureIdx,
            int sampleIdx) const
    { return (float)features[featureIdx].calc( sum, sampleIdx); }</strong>
    virtual void writeFeatures(
            cv::FileStorage &fs,
            const cv::Mat   &featureMap ) const;
protected:
    virtual void generateFeatures();

    class Feature
    {
    public:
        Feature();
        Feature(
                int offset,
                int x,
                int y,
                int _block_w,
                int _block_h  );
        <strong>uchar calc(
                const cv::Mat& _sum,
                size_t y ) const;</strong>
        void write( cv::FileStorage &fs ) const;

        cv::Rect rect;
        <strong>int p[16];</strong>
    };
    <strong>std::vector<Feature> features;

    cv::Mat sum;</strong>
};

7 在CvLBPEvaluator初始化的时候,初始化了sum矩阵,它有样本和个数相同多的行。

void CvLBPEvaluator::init(
        const CvFeatureParams *_featureParams,
        int _maxSampleCount, Size _winSize)
{
    CV_Assert( _maxSampleCount > 0);
    <strong>sum.create((int)_maxSampleCount,
               (_winSize.width + 1) * (_winSize.height + 1),
               CV_32SC1);</strong>
    CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize );
}


8 创建积分图矩阵,将矩阵的数据指向刚才创建好的sum的对应样本行。

void CvLBPEvaluator::setImage(const Mat &img, uchar clsLabel, int idx)
{
    CV_DbgAssert( !sum.empty() );
    CvFeatureEvaluator::setImage( img, clsLabel, idx );
    Mat <strong>innSum</strong>(winSize.height + 1,
               winSize.width + 1,
               sum.type(),
               <strong>sum.ptr<int>((int)idx)</strong>);
    <strong>integral</strong>( img, innSum );
}


9 调用积分图,返回LBP特征,从函数中可以看到,作者使用的是原始的LBP特征,并没有归一化或者合并等等操作。


        0    |      1    |    2    |    3   

   -------------------------------------

        4    |      5    |    6    |    7   

   -------------------------------------

        8    |      9    |   10 (cval)  |    11   

   -------------------------------------

       12   |     13   |  14   |    15  

inline uchar CvLBPEvaluator::Feature::calc(const cv::Mat &_sum, size_t y) const
{
    const int* psum = _sum.ptr<int>((int)y);
    int cval = psum[p[5]] - psum[p[6]] - psum[p[9]] + psum[p[10]];

    return (uchar)(
            (psum[p[0]] - psum[p[1]] - psum[p[4]] + psum[p[5]] >= cval ? 128 : 0) |   // 0
            (psum[p[1]] - psum[p[2]] - psum[p[5]] + psum[p[6]] >= cval ? 64 : 0) |    // 1
            (psum[p[2]] - psum[p[3]] - psum[p[6]] + psum[p[7]] >= cval ? 32 : 0) |    // 2
            (psum[p[6]] - psum[p[7]] - psum[p[10]] + psum[p[11]] >= cval ? 16 : 0) |  // 5
            (psum[p[10]] - psum[p[11]] - psum[p[14]] + psum[p[15]] >= cval ? 8 : 0) | // 8
            (psum[p[9]] - psum[p[10]] - psum[p[13]] + psum[p[14]] >= cval ? 4 : 0) |  // 7
            (psum[p[8]] - psum[p[9]] - psum[p[12]] + psum[p[13]] >= cval ? 2 : 0) |   // 6
            (psum[p[4]] - psum[p[5]] - psum[p[8]] + psum[p[9]] >= cval ? 1 : 0));     // 3
}


10 需要说明的是,上段代码中的p[?]保存的是图像像素在sum矩阵中的偏移量。这些地址是固定的,在CvFeatureEvaluator初始化的时候创建。

void CvFeatureEvaluator::init(const CvFeatureParams *_featureParams,
                              int _maxSampleCount, Size _winSize )
{
    CV_Assert(_maxSampleCount > 0);
    featureParams = (CvFeatureParams *)_featureParams;
    winSize = _winSize;
    numFeatures = 0;
    cls.create( (int)_maxSampleCount, 1, CV_32FC1 );
    generateFeatures();
}

void CvLBPEvaluator::generateFeatures()
{
    int offset = winSize.width + 1;
    for( int x = 0; x < winSize.width; x++ )
        for( int y = 0; y < winSize.height; y++ )
            for( int w = 1; w <= winSize.width / 3; w++ )
                for( int h = 1; h <= winSize.height / 3; h++ )
                    if ( (x+3*w <= winSize.width) && (y+3*h <= winSize.height) )
                        features.push_back( Feature(offset, x, y, w, h ) );
    numFeatures = (int)features.size();
}
从这里看,其实现还是和原始的LBP有些不同的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值