[关闭]
@dragonfive 2015-10-29T15:58:20.000000Z 字数 12198 阅读 494

adaboost算法

计算机视觉


共享特征点:用高效的加速程序做多级目标检测
在不同级之共用特征点来减少运算量和样本复杂度。
在单独训练的分类器里面选择的特征点是特定于某一级的,而级联分类器使用的特征点更通用。比如线和边。

简介

现在大部分的做法是用一个窗口在图像上滑动,然后对每个这样的窗口应用一个二值分类器。这种分类器可以区分目标和背景,是由标准的机器学习的方法训练的,比如boosting或支持向量机。但是这种方法不适用于千万级别目标的识别因为每个分类器是单独训练和运行的。

本文中使用一种新的结构在多类目标里共享特征点。最基本的思想是boosting算法的扩展。我们这里把不同级的目标的分类器放在一起训练。这样我们就可以使用更少的特征点更快达到和单独训练相同的效果。

共享特征点

模型

看不太懂,共享特征点的向量

H(v,c)=m=1Mhm(v,c)

c是级别的标签,v是输入的特征点向量。m是轮次。hm是弱的学习器。
我们建议在不同的级别之间共享弱的学习器。

关于级联分类器的分解方法是多样的,我们希望得到的是运算量最小的分解。也就是所需要的函数量最小。nMn=M,如果我们单独地训练分类器,所需要的函数的数量与训练的级别数成正比,如果用级联的方法训练,则函数的数量要少一些。

级联加速算法

我们在每一轮次的训练中,选取那些对于所有的级别有最低错误率的子集,然后最合适的弱学习器加到所有类的强学习器里面。并且它们的权重分布更新了。

法本身是改变数据分布实现的,它根据每次训练集之中的每个样本的分类是否正确,以及上次的总体分类的准确率,来确定每个样本的权值。将修改权值的新数据送给下层分类器进行训练,然后将每次训练得到的分类器融合起来,作为最后的决策分类器。

这些公式也太难看了吧。

浅谈 Adaboost 算法

adaboost算法是一种迭代算法,针对同一样本集,训练不同的弱分类器,然后将这些弱分类器整合起来成为一个强分类器。
是通过改变数据的分布实现的,每次判断样本集里的分类是否正确来改变样本的权重ci,然后改变该层分类器的权重aiaici的关系是这样的 ai=12ln1cici=12ln(1ci1)
每次分错的样本的权值会增大,这样会导致这一层的分类器的权重会减小。结果就是最最终整合成强分类器时占的权重比较小。
  

参考博文

adaboost算法的原理与推导
AdaBoost--从原理到实现
基于haar特征的Adaboost人脸检测技术
它的核心思想是在初始的权重数据分布下训练得到一个弱分类器(2类分类器),之后通过这个弱分类器判断准确率,对那些错判(即原本标签是1的因计算得到的0,或者相反情况)的样本的加大权重,而对于分类正确的样本,降低其权重,这样被分错的样本就被突出出来,下次训练就会更多考虑这些被错分的样本,因此得到一个新的样本分布(样本权重都被更新了)。在新的分布下,再进行训练得到一个弱分类器,周而复始得到N个检测能力一般的弱检测器。这些弱检测器只比随机猜测好一点,对于二类问题来说只是比50%的猜测好一点。但是通过一定算法把这些检测能力很弱的分类器融合起来,就会得到一个分类能力很强的强分类器。使用Adaboost分类器可以把那些不重要训练数据权重减弱甚至删除,而把那些关键的数据放在训练的最上层。理论证明,只要每个简单分类器的分类能力比随机猜测好点,当简单分类器数量趋向于无穷时,强分类器的错误率也将趋于零。

Cv模式识别

基础分类器是至少有两个叶结点的决策树分类器。 Haar特征是基础分类器的输入。每个特定分类器所使用的特征用形状、感兴趣区域中的位置以及比例系数来定义。
CvHaarFeature:haar特征点的结构由3个带权值的矩形组成的结构体,如果最后一个矩形的权值是0,那么就只有两个矩形。

CvHaarClassifier:分类器中有决策树,如果特征值小于设定的树枝阈值,那么就选择左边的分支,否则就选择右边的分支。

CvHaarStageClassifier:阶段分类器,
CvHidHaarClassifierCascade :级联分类器

级联分类器的等级结构。

cvHaarDetectObjects

cvHaarDetectObjects,先将图像灰度化,根据传入参数判断是否进行canny边缘处理(默认不使用),再进行匹配。匹配后收集找出的匹配块,过滤噪声,计算相邻个数如果超过了规定值(传入的min_neighbors)就当成输出结果,否则删去。
匹配循环:将匹配分类器放大scale(传入值)倍,同时原图缩小scale倍,进行匹配,直到匹配分类器的大小大于原图,则返回匹配结果。匹配的时候调用cvRunHaarClassifierCascade来进行匹配,将所有结果存入CvSeq* Seq (可动态增长元素序列),将结果传给cvHaarDetectObjects。
cvRunHaarClassifierCascade函数整体是根据传入的图像和cascade来进行匹配。并且可以根据传入的cascade类型不同(树型、stump(不完整的树)或其他的),进行不同的匹配方式。

源码

  1. CV_IMPL CvSeq* cvHaarDetectObjects(
  2. const CvArr* _img, //图像
  3. CvHaarClassifierCascade* cascade,//分类器
  4. CvMemStorage* storage, double scale_factor,//窗口缩放比例;
  5. int min_neighbors, //设置的阈值,只要超过这个阈值就行。
  6. int flags, CvSize min_size )
  7. {
  8. int split_stage = 2;
  9. CvMat stub, *img = (CvMat*)_img; //CvMat多通道矩阵 *img=_img指针代换传入图
  10. CvMat *temp = 0, *sum = 0, *tilted = 0, *sqsum = 0, *norm_img = 0, *sumcanny = 0, *img_small = 0;
  11. CvSeq* seq = 0;
  12. CvSeq* seq2 = 0; //CvSeq可动态增长元素序列
  13. CvSeq* idx_seq = 0;
  14. CvSeq* result_seq = 0;
  15. CvMemStorage* temp_storage = 0;
  16. CvAvgComp* comps = 0;
  17. int i;
  18. #ifdef _OPENMP
  19. CvSeq* seq_thread[CV_MAX_THREADS] = {0};
  20. int max_threads = 0;
  21. #endif
  22. CV_FUNCNAME( cvHaarDetectObjects );
  23. __BEGIN__;
  24. double factor;
  25. int npass = 2, coi; //npass=2
  26. int do_canny_pruning = flags & CV_HAAR_DO_CANNY_PRUNING; //true做canny边缘处理
  27. // 判断是否是正确
  28. if( !CV_IS_HAAR_CLASSIFIER(cascade) )
  29. CV_ERROR( !cascade ? CV_StsNullPtr : CV_StsBadArg, Invalid classifier cascade );
  30. if( !storage )
  31. CV_ERROR( CV_StsNullPtr, Null storage pointer );
  32. CV_CALL( img = cvGetMat( img, &stub, &coi ));
  33. if( coi )
  34. CV_ERROR( CV_BadCOI, COI is not supported ); //一些出错代码
  35. if( CV_MAT_DEPTH(img->type) != CV_8U )
  36. CV_ERROR( CV_StsUnsupportedFormat, Only 8-bit images are supported );
  37. CV_CALL( temp = cvCreateMat( img->rows, img->cols, CV_8UC1 ));
  38. CV_CALL( sum = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ));
  39. CV_CALL( sqsum = cvCreateMat( img->rows + 1, img->cols + 1, CV_64FC1 ));
  40. CV_CALL( temp_storage = cvCreateChildMemStorage( storage ));
  41. #ifdef _OPENMP
  42. max_threads = cvGetNumThreads();
  43. for( i = 0; i < max_threads; i++ )
  44. {
  45. CvMemStorage* temp_storage_thread;
  46. CV_CALL( temp_storage_thread = cvCreateMemStorage(0)); //CV_CALL就是运行,假如出错就报错。
  47. CV_CALL( seq_thread[i] = cvCreateSeq( 0, sizeof(CvSeq), //CvSeq可动态增长元素序列
  48. sizeof(CvRect), temp_storage_thread ));
  49. }
  50. #endif
  51. if( !cascade->hid_cascade )
  52. CV_CALL( icvCreateHidHaarClassifierCascade(cascade) );
  53. if( cascade->hid_cascade->has_tilted_features )
  54. tilted = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ); //多通道矩阵 图像长宽+1 4通道
  55. seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvRect), temp_storage ); //创建序列seq 矩形
  56. seq2 = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), temp_storage ); //创建序列seq2 矩形和邻近
  57. result_seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), storage ); //创建序列result_seq 矩形和邻近
  58. if( min_neighbors == 0 )
  59. seq = result_seq;
  60. if( CV_MAT_CN(img->type) > 1 )
  61. {
  62. cvCvtColor( img, temp, CV_BGR2GRAY ); //img转为灰度
  63. img = temp;
  64. }
  65. if( flags & CV_HAAR_SCALE_IMAGE ) //flag && 匹配图
  66. {
  67. CvSize win_size0 = cascade->orig_window_size; //CvSize win_size0为分类器的原始大小
  68. int use_ipp = cascade->hid_cascade->ipp_stages != 0 &&
  69. icvApplyHaarClassifier_32s32f_C1R_p != 0; //IPP相关函数
  70. if( use_ipp )
  71. CV_CALL( norm_img = cvCreateMat( img->rows, img->cols, CV_32FC1 )); //图像的矩阵化 4通道.
  72. CV_CALL( img_small = cvCreateMat( img->rows + 1, img->cols + 1, CV_8UC1 )); //小图矩阵化 单通道 长宽+1
  73. for( factor = 1; ; factor *= scale_factor ) //成scale_factor倍数匹配
  74. {
  75. int positive = 0;
  76. int x, y;
  77. CvSize win_size = { cvRound(win_size0.width*factor),
  78. cvRound(win_size0.height*factor) }; //winsize 分类器行列(扩大factor倍)
  79. CvSize sz = { cvRound( img->cols/factor ), cvRound( img->rows/factor ) }; //sz 图像行列(缩小factor倍) 三个Cvsize
  80. CvSize sz1 = { sz.width win_size0.width, sz.height win_size0.height }; //sz1 图像 减分类器行列
  81. CvRect rect1 = { icv_object_win_border, icv_object_win_border,
  82. win_size0.width icv_object_win_border*2, //icv_object_win_border (int) 初始值=1
  83. win_size0.height icv_object_win_border*2 }; //矩形框rect1
  84. CvMat img1, sum1, sqsum1, norm1, tilted1, mask1; //多通道矩阵
  85. CvMat* _tilted = 0;
  86. if( sz1.width <= 0 || sz1.height <= 0 ) //图片宽或高小于分类器–>跳出
  87. break;
  88. if( win_size.width < min_size.width || win_size.height < min_size.height ) //分类器高或宽小于给定的mini_size的高或宽–>继续
  89. continue;
  90. //CV_8UC1见定义.
  91. //#define CV_MAKETYPE(depth,cn) ((depth) + (((cn)-1) << CV_CN_SHIFT))
  92. //深度+(cn-1)左移3位 depth,depth+8,depth+16,depth+24.
  93. img1 = cvMat( sz.height, sz.width, CV_8UC1, img_small->data.ptr ); //小图的矩阵化 img1 单通道
  94. sum1 = cvMat( sz.height+1, sz.width+1, CV_32SC1, sum->data.ptr ); //长宽+1 4通道8位 多通道矩阵
  95. sqsum1 = cvMat( sz.height+1, sz.width+1, CV_64FC1, sqsum->data.ptr ); //长宽+1 4通道16位
  96. if( tilted )
  97. {
  98. tilted1 = cvMat( sz.height+1, sz.width+1, CV_32SC1, tilted->data.ptr ); //长宽+1 4通道8位
  99. _tilted = &tilted1; //长宽+1 4通道8位
  100. }
  101. norm1 = cvMat( sz1.height, sz1.width, CV_32FC1, norm_img ? norm_img->data.ptr : 0 ); //norm1 图像 减 分类器行列 4通道
  102. mask1 = cvMat( sz1.height, sz1.width, CV_8UC1, temp->data.ptr ); //mask1 灰度图
  103. cvResize( img, &img1, CV_INTER_LINEAR ); //img双线性插值 输出到img1
  104. cvIntegral( &img1, &sum1, &sqsum1, _tilted ); //计算积分图像
  105. if( use_ipp && icvRectStdDev_32s32f_C1R_p( sum1.data.i, sum1.step,
  106. sqsum1.data.db, sqsum1.step, norm1.data.fl, norm1.step, sz1, rect1 ) < 0 )
  107. use_ipp = 0;
  108. if( use_ipp ) //如果ipp=true (intel视频处理加速等的函数库)
  109. {
  110. positive = mask1.cols*mask1.rows; //mask1长乘宽–>positive
  111. cvSet( &mask1, cvScalarAll(255) ); //mask1赋值为255
  112. for( i = 0; i < cascade->count; i++ )
  113. {
  114. if( icvApplyHaarClassifier_32s32f_C1R_p(sum1.data.i, sum1.step,
  115. norm1.data.fl, norm1.step, mask1.data.ptr, mask1.step,
  116. sz1, &positive, cascade->hid_cascade->stage_classifier[i].threshold,
  117. cascade->hid_cascade->ipp_stages[i]) < 0 )
  118. {
  119. use_ipp = 0; //ipp=false;
  120. break;
  121. }
  122. if( positive <= 0 )
  123. break;
  124. }
  125. }
  126. if( !use_ipp ) //如果ipp=false
  127. {
  128. cvSetImagesForHaarClassifierCascade( cascade, &sum1, &sqsum1, 0, 1. );
  129. for( y = 0, positive = 0; y < sz1.height; y++ )
  130. for( x = 0; x < sz1.width; x++ )
  131. {
  132. mask1.data.ptr[mask1.step*y + x] =
  133. cvRunHaarClassifierCascade( cascade, cvPoint(x,y), 0 ) > 0; //匹配图像.
  134. positive += mask1.data.ptr[mask1.step*y + x];
  135. }
  136. }
  137. if( positive > 0 )
  138. {
  139. for( y = 0; y < sz1.height; y++ )
  140. for( x = 0; x < sz1.width; x++ )
  141. if( mask1.data.ptr[mask1.step*y + x] != 0 )
  142. {
  143. CvRect obj_rect = { cvRound(y*factor), cvRound(x*factor),
  144. win_size.width, win_size.height };
  145. cvSeqPush( seq, &obj_rect ); //将匹配块放到seq中
  146. }
  147. }
  148. }
  149. }
  150. else //!(flag && 匹配图)
  151. {
  152. cvIntegral( img, sum, sqsum, tilted );
  153. if( do_canny_pruning )
  154. {
  155. sumcanny = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ); //如果 做canny边缘检测
  156. cvCanny( img, temp, 0, 50, 3 );
  157. cvIntegral( temp, sumcanny );
  158. }
  159. if( (unsigned)split_stage >= (unsigned)cascade->count ||
  160. cascade->hid_cascade->is_tree )
  161. {
  162. split_stage = cascade->count;
  163. npass = 1;
  164. }
  165. for( factor = 1; factor*cascade->orig_window_size.width < img->cols 10 && //匹配
  166. factor*cascade->orig_window_size.height < img->rows 10;
  167. factor *= scale_factor )
  168. {
  169. const double ystep = MAX( 2, factor );
  170. CvSize win_size = { cvRound( cascade->orig_window_size.width * factor ),
  171. cvRound( cascade->orig_window_size.height * factor )};
  172. CvRect equ_rect = { 0, 0, 0, 0 };
  173. int *p0 = 0, *p1 = 0, *p2 = 0, *p3 = 0;
  174. int *pq0 = 0, *pq1 = 0, *pq2 = 0, *pq3 = 0;
  175. int pass, stage_offset = 0;
  176. int stop_height = cvRound((img->rows win_size.height) / ystep);
  177. if( win_size.width < min_size.width || win_size.height < min_size.height ) //超边跳出
  178. continue;
  179. cvSetImagesForHaarClassifierCascade( cascade, sum, sqsum, tilted, factor ); //匹配
  180. cvZero( temp ); //清空temp数组
  181. if( do_canny_pruning ) //canny边缘检测
  182. {
  183. equ_rect.x = cvRound(win_size.width*0.15);
  184. equ_rect.y = cvRound(win_size.height*0.15);
  185. equ_rect.width = cvRound(win_size.width*0.7);
  186. equ_rect.height = cvRound(win_size.height*0.7);
  187. p0 = (int*)(sumcanny->data.ptr + equ_rect.y*sumcanny->step) + equ_rect.x;
  188. p1 = (int*)(sumcanny->data.ptr + equ_rect.y*sumcanny->step)
  189. + equ_rect.x + equ_rect.width;
  190. p2 = (int*)(sumcanny->data.ptr + (equ_rect.y + equ_rect.height)*sumcanny->step) + equ_rect.x;
  191. p3 = (int*)(sumcanny->data.ptr + (equ_rect.y + equ_rect.height)*sumcanny->step)
  192. + equ_rect.x + equ_rect.width;
  193. pq0 = (int*)(sum->data.ptr + equ_rect.y*sum->step) + equ_rect.x;
  194. pq1 = (int*)(sum->data.ptr + equ_rect.y*sum->step)
  195. + equ_rect.x + equ_rect.width;
  196. pq2 = (int*)(sum->data.ptr + (equ_rect.y + equ_rect.height)*sum->step) + equ_rect.x;
  197. pq3 = (int*)(sum->data.ptr + (equ_rect.y + equ_rect.height)*sum->step)
  198. + equ_rect.x + equ_rect.width;
  199. }
  200. cascade->hid_cascade->count = split_stage; //分裂级
  201. for( pass = 0; pass < npass; pass++ )
  202. {
  203. #ifdef _OPENMP
  204. #pragma omp parallel for num_threads(max_threads), schedule(dynamic)
  205. #endif
  206. for( int _iy = 0; _iy < stop_height; _iy++ )
  207. {
  208. int iy = cvRound(_iy*ystep);
  209. int _ix, _xstep = 1;
  210. int stop_width = cvRound((img->cols win_size.width) / ystep);
  211. uchar* mask_row = temp->data.ptr + temp->step * iy;
  212. for( _ix = 0; _ix < stop_width; _ix += _xstep )
  213. {
  214. int ix = cvRound(_ix*ystep); // it really should be ystep
  215. if( pass == 0 ) //第一次循环 做
  216. {
  217. int result;
  218. _xstep = 2;
  219. if( do_canny_pruning ) //canny边缘检测
  220. {
  221. int offset;
  222. int s, sq;
  223. offset = iy*(sum->step/sizeof(p0[0])) + ix;
  224. s = p0[offset] p1[offset] p2[offset] + p3[offset];
  225. sq = pq0[offset] pq1[offset] pq2[offset] + pq3[offset];
  226. if( s < 100 || sq < 20 )
  227. continue;
  228. }
  229. result = cvRunHaarClassifierCascade( cascade, cvPoint(ix,iy), 0 ); //匹配结果存到result里
  230. if( result > 0 )
  231. {
  232. if( pass < npass 1 )
  233. mask_row[ix] = 1;
  234. else
  235. {
  236. CvRect rect = cvRect(ix,iy,win_size.width,win_size.height);
  237. #ifndef _OPENMP //如果用OpenMP
  238. cvSeqPush( seq, &rect ); //result 放到seq中
  239. #else //如果不用OpenMP
  240. cvSeqPush( seq_thread[omp_get_thread_num()], &rect ); //result放到seq_thread里
  241. #endif
  242. }
  243. }
  244. if( result < 0 )
  245. _xstep = 1;
  246. }
  247. else if( mask_row[ix] ) //不是第一次
  248. {
  249. int result = cvRunHaarClassifierCascade( cascade, cvPoint(ix,iy),
  250. stage_offset );
  251. if( result > 0 )
  252. {
  253. if( pass == npass 1 ) //如果是最后一次
  254. {
  255. CvRect rect = cvRect(ix,iy,win_size.width,win_size.height);
  256. #ifndef _OPENMP
  257. cvSeqPush( seq, &rect );
  258. #else
  259. cvSeqPush( seq_thread[omp_get_thread_num()], &rect );
  260. #endif
  261. }
  262. }
  263. else
  264. mask_row[ix] = 0;
  265. }
  266. }
  267. }
  268. stage_offset = cascade->hid_cascade->count;
  269. cascade->hid_cascade->count = cascade->count;
  270. }
  271. }
  272. }
  273. #ifdef _OPENMP
  274. // gather the results //收集结果
  275. for( i = 0; i < max_threads; i++ )
  276. {
  277. CvSeq* s = seq_thread[i];
  278. int j, total = s->total;
  279. CvSeqBlock* b = s->first;
  280. for( j = 0; j < total; j += b->count, b = b->next )
  281. cvSeqPushMulti( seq, b->data, b->count ); //结果输出到seq
  282. }
  283. #endif
  284. if( min_neighbors != 0 )
  285. {
  286. // group retrieved rectangles in order to filter out noise 收集找出的匹配块,过滤噪声
  287. int ncomp = cvSeqPartition( seq, 0, &idx_seq, is_equal, 0 );
  288. CV_CALL( comps = (CvAvgComp*)cvAlloc( (ncomp+1)*sizeof(comps[0])));
  289. memset( comps, 0, (ncomp+1)*sizeof(comps[0]));
  290. // count number of neighbors 计算相邻个数
  291. for( i = 0; i < seq->total; i++ )
  292. {
  293. CvRect r1 = *(CvRect*)cvGetSeqElem( seq, i );
  294. int idx = *(int*)cvGetSeqElem( idx_seq, i );
  295. assert( (unsigned)idx < (unsigned)ncomp );
  296. comps[idx].neighbors++;
  297. comps[idx].rect.x += r1.x;
  298. comps[idx].rect.y += r1.y;
  299. comps[idx].rect.width += r1.width;
  300. comps[idx].rect.height += r1.height;
  301. }
  302. // calculate average bounding box 计算重心
  303. for( i = 0; i < ncomp; i++ )
  304. {
  305. int n = comps[i].neighbors;
  306. if( n >= min_neighbors )
  307. {
  308. CvAvgComp comp;
  309. comp.rect.x = (comps[i].rect.x*2 + n)/(2*n);
  310. comp.rect.y = (comps[i].rect.y*2 + n)/(2*n);
  311. comp.rect.width = (comps[i].rect.width*2 + n)/(2*n);
  312. comp.rect.height = (comps[i].rect.height*2 + n)/(2*n);
  313. comp.neighbors = comps[i].neighbors;
  314. cvSeqPush( seq2, &comp ); //结果输入到seq2
  315. }
  316. }
  317. // filter out small face rectangles inside large face rectangles 在大的面块中找出小的面块
  318. for( i = 0; i < seq2->total; i++ ) //在seq2中寻找
  319. {
  320. CvAvgComp r1 = *(CvAvgComp*)cvGetSeqElem( seq2, i ); //r1指向结果
  321. int j, flag = 1;
  322. for( j = 0; j < seq2->total; j++ )
  323. {
  324. CvAvgComp r2 = *(CvAvgComp*)cvGetSeqElem( seq2, j );
  325. int distance = cvRound( r2.rect.width * 0.2 );
  326. if( i != j &&
  327. r1.rect.x >= r2.rect.x distance &&
  328. r1.rect.y >= r2.rect.y distance &&
  329. r1.rect.x + r1.rect.width <= r2.rect.x + r2.rect.width + distance &&
  330. r1.rect.y + r1.rect.height <= r2.rect.y + r2.rect.height + distance &&
  331. (r2.neighbors > MAX( 3, r1.neighbors ) || r1.neighbors < 3) )
  332. {
  333. flag = 0;
  334. break;
  335. }
  336. }
  337. if( flag )
  338. {
  339. cvSeqPush( result_seq, &r1 ); //添加r1到返回结果.
  340. }
  341. }
  342. }
  343. __END__;

   
      
      
  
   
   
     
 
未完

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注