OpenCV源码解析:多尺度检测的训练trainCascade

(没时间一次性写完,更新中)

该项目在Opencv Application的一部分,项目名称为opencv_trainCascade,它即可以用来训练lbp特征分类器,也可以是haar特征,有人说lbp特征训练起来更快,我没有专门比较过,不作评论;根据个人经验,lbp在很多场合会更稳定,所以我一般会选lbp特征。这里和检测(detectMultiScale)对应,这里我们仍然以汽车外形检测为例。

关于样本收集的注意事项等,可参考此文:https://blog.csdn.net/u014587123/article/details/78507649

本例的汽车正负样本下载: https://download.csdn.net/download/tanmx219/10747369
检测样本下载:
 https://download.csdn.net/download/tanmx219/10623808
注意都是pgm格式,如果你想预览而看图器打不开,请到SpaceSoftwares.com上下载SpaceView看图工具。

 

如何训练?

准备工作:
正本数550
负本数 500
总样本数 1050 ( sample_count )

下载的样本中已经生成possamples.vec,你可以自己生成,命令(createSamples在Opencv-Application中)如下
createSamples.exe -info ../dataCascade/cars.info -vec ../dataCascade/possamples.vec -num 550 -w 100 -h 40
pause

vec文件格式是这样的,具体读取参考源码CvCascadeImageReader::PosReader::create(..)及PosReader::get(),这部分比较简单,后面不再详述。

bool CvCascadeImageReader::PosReader::create( const string _filename )
从vec文件中读取图片码流,其vec文件格式(文件结构)是这样的
图片个数(4字节)
图片尺寸(4字节,灰度图的字节数size=宽x高)
0(4字节)
0(1个字节的图片分隔符)
Data(共计size个字节)
0(1个字节的图片分隔符)
Data(共计size个字节)
0(1个字节的图片分隔符)
Data(共计size个字节)
…

bool CvCascadeImageReader::PosReader::get( Mat &_img )
读取vec文件中的一个image,放入到_img中。
vec文件格式 参考上面(CvCascadeImageReader::PosReader::create)的注释

训练时使用的命令如下
opencv_traincascade.exe  -data ../dataCascade -vec ../dataCascade/possamples.vec -bg ../dataCascade/neg.info -numPos 550 -numNeg 500 -numStages 20 -precalcValbufSize 200 -precalcdxBufSize 1000 -featureType LBP  -w 100 -h 40 -minHitRate 0.995 -maxFalseAlarmRate 0.5 -weightTrimRate 0.95 -maxDepth 1 -maxWeakCount 100 -mode ALL
pause

这里最重要的就是注意目录的存放和路径(根据实际存放的位置,要设成你自己实际使用的路径),如果在VS项目中直接运行,找不到文件的话,建议参考:
https://blog.csdn.net/tanmx219/article/details/81783803

 

trainCascade.exe 参数

-data
    文件夹名,用于保存训练生成的各种xml文件,该文件夹一定要事先创建好,否则系统会报错,在这里,我们定义该文件夹名为data,它已在前面创建好

-vec
    由opencv_createsamples.exe程序生成的正样本vec文件

-bg
    用于表示不含车牌照片的文本文件,即neg.txt

-numPos
    训练级联分类器的每一级分类器(即强分类器)时所用的正样本数目

-numNeg
    训练级联分类器的每一级分类器(即强分类器)时所用的负样本数目

-numStages:
    最终得到的级联分类器的级数,我们设置为12

-precalcValBufSize:
    用于存储预先计算特征值的内存空间大小,单位为MB,越大越好,但分配时不要超过可用内存,否则可能无法完成分配。
    如果空间分配过小,就会根据空间的容纳能力,只计算前面能被容纳的部分特征,具体规则可参考
    CvCascadeBoostTrainData::setData及precalculate函数,这部分能被容纳的特征的个数是
numPrecalcVal
    后面的特征不会被事先计算好,而是在实际使用时才计算,可以参考CvCascadeBoostTrainData::get_cat_var_data函数。

-precalcIdxBufSize
    用于存储预先计算特征索引的内存空间大小,单位为MB,越大越好,但分配时不要超过可用内存,否则可能运行不了。

-stageType
    强分类器的类型,目前只实现了AdaBoost,因此唯一的值(缺省值)为BOOST

-featureType
    特征类型,HAAR(缺省值),LBP或HOG

-w
    正样本图像的宽,必须与opencv_createsamples.exe命令的参数一致,即58

-h
    正样本图像的高,必须与opencv_createsamples.exe命令的参数一致,即18

-bt:
    AdaBoost的类型,DAB,RAB,LB或GAB(缺省值)

-minHitRate:
    原理部分提到的每级分类器的最小识别率

-maxFalseAlarmRate:       
    原理部分提到的每级分类器的最大错误率

-weightTrimRate
    用于决策树的剪枝,缺省值为0.95

-maxDepth
    决策树的最大深度,缺省值为1,即该决策树为二叉树(树墩形)

-maxWeakCount:
    强分类器所包含的最大决策树的数量,该值也与最大错误率有关,我们定义该值为150

-mode:
    
如果特征为HAAR,则该参数决定了使用哪种HAAR状特征(见图1),BASIC(缺省值)、CORE或ALL

 

CvCascadeClassifier(级联分类器)

CvCascadeParams cascadeParams;               // 级联分类器参数
cv::Ptr<CvFeatureParams> featureParams;      // 特征参数
cv::Ptr<CvCascadeBoostParams> stageParams;   // 强分类器参数
cv::Ptr<CvFeatureEvaluator> featureEvaluator;// 特征计算
std::vector< cv::Ptr<CvCascadeBoost> > stageClassifiers;  // 强分类器
CvCascadeImageReader imgReader;  // 样本图像读取
int numStages, curNumSamples;    // 强分类器数目,当前样本数目
int numPos, numNeg;              // 正样本数目,负样本数目
123456789

CvCascadeParams(级联分类器参数)

static const int defaultStageType = BOOST;                   // 默认级联分类器类型
static const int defaultFeatureType = CvFeatureParams::HAAR; // 默认级联分类器特征
int stageType;     // 强分类器类型
int featureType;   // 特征类型
cv::Size winSize;  // 级联分类器窗口信息
123456

CvFeatureParams(特征参数)

int maxCatCount; // 0 in case of numerical features   // 最大特征数目
int featSize; // 1 in case of simple features (HAAR, LBP) and N_BINS(9)*N_CELLS(4) in case of Dalal's HOG features   // 特征尺寸
123

CvCascadeBoostParams(强分类器参数)

float minHitRate; // 最小检测率
float maxFalseAlarm;  // 最大误检率
123

CvFeatureEvaluator(特征计算)

int npos, nneg;     //正样本数目,负样本数目
int numFeatures;    // 特征数目
cv::Size winSize;   // 窗口尺寸
CvFeatureParams *featureParams;   // 特征参数
cv::Mat cls;        // 设置样本类别矩阵
123456

CvCascadeBoost(强分类器)

float threshold;                  // 强分类器阈值
float minHitRate, maxFalseAlarm;  // 强分类器最小检测率,最大误检率
123

CvBoostTree(弱分类器)使用CvDTree实现(ml模块中的随机树实现)

CvDTreeNode* root;                      // 根节点
CvMat* var_importance;                  // 重要变脸
CvDTreeTrainData* data;                 // 训练数据
CvMat train_data_hdr, responses_hdr;    // 训练数据和响应数据
cv::Mat train_data_mat, responses_mat;  // 训练数据矩阵和响应数据矩阵
int pruned_tree_idx;                    // 树裁剪索引
1234567

CvDTreeNode(弱分类器节点信息)

int class_idx;        // 赋给节点的归一化的类别索引(从0到class_count-1),用在分类树和树集成中
int Tn;               // 在有序排列的树中的树索引。用在剪枝过程中和之后。
double value;         // 赋给节点的值。可以是一个类别标签,也可以是估计函数值
​
CvDTreeNode* parent;  // 父节点
CvDTreeNode* left;    // 左子树
CvDTreeNode* right;   // 右子树
​
CvDTreeSplit* split;  // 指向第一个分裂点(初始分裂点)的指针
​
int sample_count;     // 在训练阶段所用到的样本数目
int depth;            // 节点深度。根节点为0,子节点是父节点深度加1
int* num_valid;       // 合法数目
int offset;
int buf_idx;
double maxlr;
​
// global pruning data
// 全局裁剪数据
int complexity;
double alpha;
double node_risk, tree_risk, tree_error;
​
// cross-validation pruning data
// 交叉验证裁剪数据
int* cv_Tn;           // 如果该参数大于1,用叠一交叉验证来进行剪枝
double* cv_node_risk;
double* cv_node_error;// 节点错误率

 

开始训练(trainCascade)

本次调试中使用的参数:

-data ../dataCascade -vec ../dataCascade/possamples.vec -bg ../dataCascade/neg.info -numPos 550 -numNeg 500 -numStages 20 -precalcValBufSize 2048 -precalcIdxBufSize 1024 -featureType LBP -w 100 -h 40 -minHitRate 0.995 -maxFalseAlarmRate 0.5 -weightTrimRate 0.95 -maxDepth 1 -maxWeakCount 100 -mode ALL

这里要注意,如果你碰到问题如,

No.1 OpenCV(3.4.3) Error: Bad argument (> Can not get new positive sample. The most possible reason is insufficient count of samples in given vec-file.那么,请把numPos改小一点,如540,或500,也可以把minHitRate降低,详情可参考下面这两个链接。
http://answers.opencv.org/question/4368/traincascade-error-bad-argument-can-not-get-new-positive-sample-the-most-possible-reason-is-insufficient-count-of-samples-in-given-vec-file/
http://answers.opencv.org/question/776/error-in-parameter-of-traincascade/?answer=792#post-id-792

No.2  Infinite loop in opencv_traincascade CvCascadeClassifier::fillPassedSamples
死循环是因为副本太少!!!请添加副本。
https://stackoverflow.com/questions/22440647/infinite-loop-haar-lbp-hog-traincascade-of-opencv-stuck
https://stackoverflow.com/questions/14554281/infinite-loop-in-opencv-traincascade-cvcascadeclassifierfillpassedsamples
是不是出现死循环,我建议在CvCascadeClassifier::fillPassedSamples中添加简单的判断语句,例如本人添加了一句:
            cout<< "consumed: " << consumed << endl; 
如果你发现consumed不断输出,没有停下来,超过副本总数很多,基本就可以判断为死循环。


int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, double minimumAcceptanceRatio, int64& consumed )
{
    int getcount = 0;
    Mat img(cascadeParams.winSize, CV_8UC1);
    for( int i = first; i < first + count; i++ )
    {
        for( ; ; )
        {
			if( consumed != 0 && ((double)getcount+1)/(double)(int64)consumed <= minimumAcceptanceRatio )
                return getcount;

            bool isGetImg = isPositive ? imgReader.getPos( img ) :
                                           imgReader.getNeg( img );
            if( !isGetImg )
                return getcount;
            consumed++;
			cout<< "consumed: " << consumed << endl; // 这里是本人添加的代码

            featureEvaluator->setImage( img, isPositive ? 1 : 0, i );
            if( predict( i ) == 1 )
            {
                getcount++;
                printf("%s current samples: %d\r", isPositive ? "POS":"NEG", getcount);
                break;
            }
        }		
    }
    return getcount;
}

 

 

 

posted @ 2018-08-24 17:33  SpaceVision  阅读(141)  评论(0编辑  收藏  举报