HaarTraining关键的部分是建立基分类器classifier,OpenCV中所採用的是CART(决策树的一种):通过调用cvCreateMTStumpClassifier来完毕。

这里我讨论利用回归的方法来分裂结点。分类的方法仅仅是在分裂结点的方法与之不同而已。

cvCreateMTStumpClassifier

  1. //设置决策树分类误差计算方法
  2. stumperror = (int) ((CvMTStumpTrainParams*) trainParams)->error;
  3.  
  4. //设置class step和ydata
  5. ydata = trainClasses->data.ptr;
  6. if( trainClasses->rows == 1 )
  7. {
  8. m = trainClasses->cols;
  9. ystep = CV_ELEM_SIZE( trainClasses->type );
  10. }
  11. else
  12. {
  13. m = trainClasses->rows;
  14. ystep = trainClasses->step;
  15. }
  16. //设置weight step和wdata
  17. wdata = weights->data.ptr;
  18. if( weights->rows == 1 )
  19. {
  20. assert( weights->cols == m );
  21. wstep = CV_ELEM_SIZE( weights->type );
  22. }
  23. else
  24. {
  25. assert( weights->rows == m );
  26. wstep = weights->step;
  27. }
  28.  
  29. //设置步长,地址等參数,用于获取idxCache内容
  30. if( ((CvMTStumpTrainParams*) trainParams)->sortedIdx != NULL )
  31. {
  32. sortedtype =
  33. CV_MAT_TYPE( ((CvMTStumpTrainParams*) trainParams)->sortedIdx->type );
  34. assert( sortedtype == CV_16SC1 || sortedtype == CV_32SC1
  35. || sortedtype == CV_32FC1 );
  36. sorteddata = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->data.ptr;
  37. sortedsstep = CV_ELEM_SIZE( sortedtype );
  38. sortedcstep = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->step;
  39. sortedn = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->rows;
  40. sortedm = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->cols;
  41. }
  42.  
  43. if( trainData == NULL )
  44. {
  45. assert( ((CvMTStumpTrainParams*) trainParams)->getTrainData != NULL );
  46. n = ((CvMTStumpTrainParams*) trainParams)->numcomp;
  47. assert( n > 0 );
  48. }
  49. //设置步长,地址等參数,用于获取dataCache内容
  50. else
  51. {
  52. assert( CV_MAT_TYPE( trainData->type ) == CV_32FC1 );
  53. data = trainData->data.ptr;
  54. if( CV_IS_ROW_SAMPLE( flags ) )
  55. {
  56. cstep = CV_ELEM_SIZE( trainData->type );
  57. sstep = trainData->step;
  58. assert( m == trainData->rows );
  59. datan = n = trainData->cols;
  60. }
  61. else
  62. {
  63. sstep = CV_ELEM_SIZE( trainData->type );
  64. cstep = trainData->step;
  65. assert( m == trainData->cols );
  66. datan = n = trainData->rows;
  67. }
  68. if( ((CvMTStumpTrainParams*) trainParams)->getTrainData != NULL )
  69. {
  70. n = ((CvMTStumpTrainParams*) trainParams)->numcomp;
  71. }
  72. }

可能研究代码到这里的朋友仍然不清楚idxCache和valCache的作用。

我这里做一点简单的说明:

valCache是设置在训练前有多少特征值被提前算出存放在内存中。idxCache是valCache中每种特征按特征值从小到大排列的样本的序号。

内存大小是通过执行程序的命令行參数设置的。在cvHaartraining.cpp中我们能够找到这句话,当中float和short,各自是valCache和idxCache存放内容的基本类型。

  1. //1MB == 1048576B 计算一个样本中有多少个特征能被pre计算放在内存中
  2. numprecalculated = (int) ( ((size_t) mem) * ((size_t) 1048576) /
  3. ( ((size_t) (npos + nneg)) * (sizeof( float ) + sizeof( short )) ) );

为了方便理解,我把两者的内存模型画了出来

要注意idxCache中每行的index排列是示意图。

比方第一行代表feature1从小到大的index顺序。从图中能够看出。sample1的特征值feature1 < sample0的特征值feature1<...<sample n < sample n-1。

利用idxCache数组我们能够方便按特征值的从小到大遍历valCache,而且节省了空间。从float->short。

理解了这两个cache之后我们再回到上面的代码,能够发现这里做得仅仅是设置步长和cache首地址的一些操作。为以下開始的遍历做好准备。

跳过一些变量的初始化步骤,我们来到构建决策树stump的部分,而且为了方便阅读核心代码。去掉了其它一些基于移植的代码

  1. while( t_compidx < n )
  2. {
  3. //选择计算前100种特征
  4. t_n = portion;
  5. if( t_compidx < datan )
  6. {
  7. t_n = ( t_n < (datan - t_compidx) ) ? t_n : (datan - t_compidx);
  8. t_data = data;
  9. t_cstep = cstep;
  10. t_sstep = sstep;
  11. }
  12. else
  13. {
  14.  
  15. }
  16.  
  17. if( sorteddata != NULL )
  18. {
  19.  
  20. }
  21. else
  22. {
  23. /* have sorted indices */
  24. switch( sortedtype )
  25. {
  26. case CV_16SC1:
  27.  
  28. //选择某个样本的某个特征值作为结点
  29. for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
  30. {
  31. if( findStumpThreshold_16s[stumperror](
  32. t_data + ti * t_cstep, t_sstep,
  33. wdata, wstep, ydata, ystep,
  34. sorteddata + ti * sortedcstep, sortedsstep, sortedm,
  35. &lerror, &rerror,
  36. &threshold, &left, &right,
  37. &sumw, &sumwy, &sumwyy ) )
  38. {
  39. optcompidx = ti;
  40. }
  41. }
  42. break;
  43.  
  44. }
  45. }
  46. }}

这里datan代表的是一个检測窗体包括的特征数目。portion代表以多少的行为单位进行计算。每一个循环选取valCache中的portion行进行计算,应该是为了发挥并行计算的优势,假如设置了并行计算的宏的话。

findStumpThreshold_32[stumperror]是一个函数指针,利用这个函数我们能够选择某个样本的某个特征值作为决策树的一个结点。

这里结点分裂方法我选择的是最小残差和的方法。即统计利用某个特征值进行分类后,左右子树中类间的残差之和。最小残差和相应的特征值就是满足要求的结点。

OpenCV1.0中利用宏定义的方式实现了这个函数

  1. #define ICV_DEF_FIND_STUMP_THRESHOLD( suffix, type, error ) \
  2. CV_BOOST_IMPL int icvFindStumpThreshold_##suffix( \
  3. uchar* data, size_t datastep, \
  4. uchar* wdata, size_t wstep, \
  5. uchar* ydata, size_t ystep, \
  6. uchar* idxdata, size_t idxstep, int num, \
  7. float* lerror, \
  8. float* rerror, \
  9. float* threshold, float* left, float* right, \
  10. float* sumw, float* sumwy, float* sumwyy ) \
  11. { \
  12. int found = 0; \
  13. float wyl = 0.0F; \
  14. float wl = 0.0F; \
  15. float wyyl = 0.0F; \
  16. float wyr = 0.0F; \
  17. float wr = 0.0F; \
  18. \
  19. float curleft = 0.0F; \
  20. float curright = 0.0F; \
  21. float* prevval = NULL; \
  22. float* curval = NULL; \
  23. float curlerror = 0.0F; \
  24. float currerror = 0.0F; \
  25. float wposl; \
  26. float wposr; \
  27. \
  28. int i = 0; \
  29. int idx = 0; \
  30. \
  31. wposl = wposr = 0.0F; \
  32. if( *sumw == FLT_MAX ) \
  33. { \
  34. /* calculate sums */ \
  35. float *y = NULL; \
  36. float *w = NULL; \
  37. float wy = 0.0F; \
  38. \
  39. *sumw = 0.0F; \
  40. *sumwy = 0.0F; \
  41. *sumwyy = 0.0F; \
  42. for( i = 0; i < num; i++ ) \
  43. { \
  44. idx = (int) ( *((type*) (idxdata + i*idxstep)) ); \
  45. w = (float*) (wdata + idx * wstep); \
  46. *sumw += *w; \
  47. y = (float*) (ydata + idx * ystep); \
  48. wy = (*w) * (*y); \
  49. *sumwy += wy; \
  50. *sumwyy += wy * (*y); \
  51. } \
  52. } \
  53. \
  54. for( i = 0; i < num; i++ ) \
  55. { \
  56. idx = (int) ( *((type*) (idxdata + i*idxstep)) ); \
  57. curval = (float*) (data + idx * datastep); \
  58. /* for debug purpose */ \
  59. if( i > 0 ) assert( (*prevval) <= (*curval) ); \
  60. \
  61. wyr = *sumwy - wyl; \
  62. wr = *sumw - wl; \
  63. \
  64. if( wl > 0.0 ) curleft = wyl / wl; \
  65. else curleft = 0.0F; \
  66. \
  67. if( wr > 0.0 ) curright = wyr / wr; \
  68. else curright = 0.0F; \
  69. \
  70. error \
  71. \
  72. if( curlerror + currerror < (*lerror) + (*rerror) ) \
  73. { \
  74. (*lerror) = curlerror; \
  75. (*rerror) = currerror; \
  76. *threshold = *curval; \
  77. if( i > 0 ) { \
  78. *threshold = 0.5F * (*threshold + *prevval); \
  79. } \
  80. *left = curleft; \
  81. *right = curright; \
  82. found = 1; \
  83. } \
  84. \
  85. do \
  86. { \
  87. wl += *((float*) (wdata + idx * wstep)); \
  88. wyl += (*((float*) (wdata + idx * wstep))) \
  89. * (*((float*) (ydata + idx * ystep))); \
  90. wyyl += *((float*) (wdata + idx * wstep)) \
  91. * (*((float*) (ydata + idx * ystep))) \
  92. * (*((float*) (ydata + idx * ystep))); \
  93. } \
  94. while( (++i) < num && \
  95. ( *((float*) (data + (idx = \
  96. (int) ( *((type*) (idxdata + i*idxstep))) ) * datastep)) \
  97. == *curval ) ); \
  98. --i; \
  99. prevval = curval; \
  100. } /* for each value */ \
  101. \
  102. return found; \
  103. }

这里几个关键的变量:ydata是某个特征所代表的类别,正负样本分别以1。-1进行标注,wdata是正负样本相应的权值,data指的就是valCache的某一行。

程序进来的时候推断sumw是否初始化,没有初始化就进行赋值。因为同一个训练集每一个样本都仅仅相应一个ydata和wdata(每一个样本相应非常多个Haar特征,两者有差别),因此这里的sumw,sumwyy,sumwy都是一个确定的值。提前计算好,在后面的迭代中就不必反复计算。

接下来,依据idxCache中某一行(视迭代次数而定)的index,按从小到大的顺序遍历ValCache中相应行的特征值。也就是不相同本的同一特征值。并将其作为结点,尝试对样本进行划分。curleft和curright分别代表左右子树的类别的加权平均值。然后利用error宏计算左右子树的残差

  1. #define ICV_DEF_FIND_STUMP_THRESHOLD_SQ( suffix, type ) \
  2. ICV_DEF_FIND_STUMP_THRESHOLD( sq_##suffix, type, \
  3. /* calculate error (sum of squares) */ \
  4. /* err = sum( w * (y - left(rigt)Val)^2 ) */ \
  5. curlerror = wyyl + curleft * curleft * wl - 2.0F * curleft * wyl; \
  6. currerror = (*sumwyy) - wyyl + curright * curright * wr - 2.0F * curright * wyr; \
  7. )

最后的一个do-while循环。就是用来跳过和当前结点同样的特征值。尽管以兴许的、同样的值作为结点划分左右子树,残差平方和可能会改变,可是决策树划分的最小单位是特征值的种类,由于在利用决策树进行分类的时候,必须对同样的特征值做出一样的决策(该划入左子树还是该划入右子树)。

总结

HaarTraining的代码是有4,5K行。可是认真学习之后会收获非常多机器学习的算法和优秀代码的书写习惯。我会随着学习的深入不断更新自己的源代码研究体会,写的尽管不是非常具体,可是力求把重点突出来,将自己在阅读代码时碰到的困惑总结出来,给相同学习Training算法的朋友一点点帮助

OpenCV HaarTraining代码解析(二)cvCreateMTStumpClassifier(建立决策树)的更多相关文章

  1. java代码解析二维码

    java代码解析二维码一般步骤 本文采用的是google的zxing技术进行解析二维码技术,解析二维码的一般步骤如下: 一.下载zxing-core的jar包: 二.创建一个BufferedImage ...

  2. GraphSAGE 代码解析(二) - layers.py

    原创文章-转载请注明出处哦.其他部分内容参见以下链接- GraphSAGE 代码解析(一) - unsupervised_train.py GraphSAGE 代码解析(三) - aggregator ...

  3. GraphSAGE 代码解析(四) - models.py

    原创文章-转载请注明出处哦.其他部分内容参见以下链接- GraphSAGE 代码解析(一) - unsupervised_train.py GraphSAGE 代码解析(二) - layers.py ...

  4. GraphSAGE 代码解析(三) - aggregators.py

    原创文章-转载请注明出处哦.其他部分内容参见以下链接- GraphSAGE 代码解析(一) - unsupervised_train.py GraphSAGE 代码解析(二) - layers.py ...

  5. GraphSAGE 代码解析(一) - unsupervised_train.py

    原创文章-转载请注明出处哦.其他部分内容参见以下链接- GraphSAGE 代码解析(二) - layers.py GraphSAGE 代码解析(三) - aggregators.py GraphSA ...

  6. asp.net C#生成和解析二维码代码

    类库文件我们在文件最后面下载 [ThoughtWorks.QRCode.dll 就是类库] 使用时需要增加: using ThoughtWorks.QRCode.Codec;using Thought ...

  7. JavaScript “跑马灯”抽奖活动代码解析与优化(二)

    既然是要编写插件.那么叫做"插件"的东西肯定是具有的某些特征能够满足我们平时开发的需求或者是提高我们的开发效率.那么叫做插件的东西应该具有哪些基本特征呢?让我们来总结一下: 1.J ...

  8. RobHess的SIFT代码解析步骤三

    平台:win10 x64 +VS 2015专业版 +opencv-2.4.11 + gtk_-bundle_2.24.10_win32 主要参考:1.代码:RobHess的SIFT源码 2.书:王永明 ...

  9. Fixflow引擎解析(二)(模型) - BPMN2.0读写

    Fixflow引擎解析(四)(模型) - 通过EMF扩展BPMN2.0元素 Fixflow引擎解析(三)(模型) - 创建EMF模型来读写XML文件 Fixflow引擎解析(二)(模型) - BPMN ...

随机推荐

  1. android 从其他app接收分享的内容

    Receiving Content from Other Apps[从其他app接收分享的内容] 就像你的程序能够发送数据到其他程序一样,其他程序也能够简单的接收发送过来的数据.需要考虑的是用户与你的 ...

  2. Join的实现步骤 以及连接的概念

    Join的实现步骤  以及连接的概念 我们常说连接有三种,即 交叉连接.内连接.外连接,这三者的概念很容易模糊,现在我们先放下概念,搞清楚完整连接实现的步骤: 一个完整的连接有三个步骤:.做笛卡儿积: ...

  3. Ubuntu 挂载ISO文件的方法

    1.在终端中输入:sudo mkdir /media/iso 在/media下生成一个iso文件夹用来挂载iso文件2.然后输入:sudo mount -o loop /home/X/X/XXXX.i ...

  4. Hough变换在opencv中的应用

    霍夫曼变换(Hough Transform)的原理 霍夫曼变换是一种可以检测出某种特殊形状的算法,OpenCV中用霍夫曼变换来检测出图像中的直线.椭圆和其他几何图形.由它改进的算法,可以用来检测任何形 ...

  5. jQuery.localStorage() - jQuery SDK API

    jQuery.localStorage() - jQuery SDK API jQuery.localStorage() From jQuery SDK API   Jump to: navigati ...

  6. ASP.NET - 匹配标签中的内容

    string str = @"<td>Csdn</td>\r\n<td>V1.0</td>\r\n<td>2014-10-23&l ...

  7. 编写自定义的JDBC框架与策略模式

    本篇根据上一篇利用数据库的几种元数据来仿造Apache公司的开源DbUtils工具类集合来编写自己的JDBC框架.也就是说在本篇中很大程度上的代码都和DbUtils中相似,学完本篇后即更容易了解DbU ...

  8. Windows Azure入门教学系列 (八):使用Windows Azure Drive

    我们知道,由于云端的特殊性,通常情况下,对文件系统的读写建议使用Blob Storage来代替.这就产生了一个问题:对于一个已经写好的本地应用程序,其中使用了NTFS API对本地文件系统读写的代码是 ...

  9. 在webx.ml中 配置struts2 后 welcome-file-list 失效的解决办法

    struts2 <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>*.a ...

  10. Swift - 给游戏添加背景音乐和音效(SpriteKit游戏开发)

    游戏少不了背景音乐和音效.下面我们通过创建一个管理音效的类,来实现背景音乐的播放,同时点击屏幕可以播放相应的音效. 声音管理类 SoundManager.swift 1 2 3 4 5 6 7 8 9 ...