opencv2.4.9中KNN算法理解
2015-11-26 20:07
357 查看
KNN算法
opencv中文版原文描述是:K近邻可能是最简单的分类器。训练数据跟类别标签放在一起,离测试数据最近的(欧氏距离最近)K个样本进行投票,确定测试数据的分类结果。这可能是你想到的最贱的方法,该方法比较有效,但是速度比较慢且对内存的需求比较大(因为它需要存储所有训练集)。离测试数据是否最近要求计算测试样本与所有点的距离,可以把这个过程看成搜索过程,是个求top-K的问题。由于要进行投票,那么就可以分为带权投票还是不带权投票,不带权投票可能会出现近邻中不足K个或K个距离和非常大,从而使得分类出错。另外,参数K该怎么取应该就很关键了,我看到的大部分程序时人为规定参数K值,应该存在自动确定K值的算法。K近邻中的K含义是在将测试样本分到某个类别时要知道其K个近邻样本的类别,把测试样本归类到样本占多数的类别,这说明在分类新样本之前需要至少有K个样本且知道其类别,因此可以说KNN是监督学习算法。
Opencv2.4.9中实现的KNN是在CvKNearest类中,继承于CvStatModel,放在了机器学习模块。既可以用来进行分类,又可以用来做回归,并且支持增量学习,可以用新样本来更新模型。但是opencv的KNN算法不像决策树一样支持变量子集选择和属性缺失的情况。
-使用方法可以采用定义CvKNearest类的对象方法如下:
CvKNearest knn(trainData, trainClasses, 0, false, K);
,该方法会在构造函数中执行训练过程,训练的过程就是给CvMat *smaple赋予数据的过程,将样本有序的放在内存的前面,label放在内存的后面。构造代码如下:
CvKNearest::CvKNearest( const CvMat* _train_data, const CvMat* _responses, const CvMat* _sample_idx, bool _is_regression, int _max_k ) { samples = 0; train( _train_data, _responses, _sample_idx, _is_regression, _max_k, false ); }
来了一个新样本只需要调用response = knn.find_nearest(&sample, K, 0, 0, nearests,
0);函数就可以得到类别了。
opencv中的实现过程
假如已经将一批带标签的数据通过构造函数的方式存入到CvKNearest的成员变量samples所指内存区,现在来了一个新的测试样本,首先计算测试样本与所有训练样本的欧氏距离,调用find_neighbors_direct计算所有测试样本到所有训练集的距离。void CvKNearest::find_neighbors_direct( const CvMat* _samples, int k, int start, int end, float* neighbor_responses, const float** neighbors, float* dist ) const { int i, j, count = end - start, k1 = 0, k2 = 0, d = var_count; CvVectors* s = samples; for( ; s != 0; s = s->next ) { int n = s->count;//训练集中样本个数 for( j = 0; j < n; j++ ) { for( i = 0; i < count; i++ )//count表示测试样本个数,无标签 { double sum = 0; Cv32suf si; const float* v = s->data.fl[j];//指向已带标签样本j const float* u = (float*)(_samples->data.ptr + _samples->step*(start + i));//指向测试样本 Cv32suf* dd = (Cv32suf*)(dist + i*k);//保存样本i的到k个近邻的距离(Cv32suf的f域)和近邻的类别(Cv32suf的i域) float* nr; const float** nn; int t, ii, ii1; for( t = 0; t <= d - 4; t += 4 ) {//计算样本i与样本j的欧式距离,最小维数为4 double t0 = u[t] - v[t], t1 = u[t+1] - v[t+1]; double t2 = u[t+2] - v[t+2], t3 = u[t+3] - v[t+3]; sum += t0*t0 + t1*t1 + t2*t2 + t3*t3; } for( ; t < d; t++ ) {//计算样本i与样本j的欧式距离,维数小于4 double t0 = u[t] - v[t]; sum += t0*t0; } si.f = (float)sum; for( ii = k1-1; ii >= 0; ii-- )//将类别i从小到大排序,插入到ii+1位置 if( si.i > dd[ii].i ) break; if( ii >= k-1 ) continue; nr = neighbor_responses + i*k; nn = neighbors ? neighbors + (start + i)*k : 0; for( ii1 = k2 - 1; ii1 > ii; ii1-- )//插入前数据后移 { dd[ii1+1].i = dd[ii1].i; nr[ii1+1] = nr[ii1]; if( nn ) nn[ii1+1] = nn[ii1]; } dd[ii+1].i = si.i;//给ii+1位置的样本赋予插入的样本的类别编号,由于是union结构,距离也保存了 nr[ii+1] = ((float*)(s + 1))[j]; if( nn ) nn[ii+1] = v; } k1 = MIN( k1+1, k ); k2 = MIN( k1, k-1 ); } } }
得多所有距离后,采用冒泡排序方式得到topK近邻,并计算样本数最多的类别。
float CvKNearest::write_results( int k, int k1, int start, int end, const float* neighbor_responses, const float* dist, CvMat* _results, CvMat* _neighbor_responses, CvMat* _dist, Cv32suf* sort_buf ) const { float result = 0.f; int i, j, j1, count = end - start; double inv_scale = 1./k1; int rstep = _results && !CV_IS_MAT_CONT(_results->type) ? _results->step/sizeof(result) : 1; for( i = 0; i < count; i++ )//count=1 { const Cv32suf* nr = (const Cv32suf*)(neighbor_responses + i*k); float* dst; float r; if( _results || start+i == 0 ) { if( regression ) {//不执行 double s = 0; for( j = 0; j < k1; j++ ) s += nr[j].f; r = (float)(s*inv_scale); } else { int prev_start = 0, best_count = 0, cur_count; Cv32suf best_val; for( j = 0; j < k1; j++ )//复制前K1个数据 sort_buf[j].i = nr[j].i; for( j = k1-1; j > 0; j-- )//表示排序次数k1-1次,使类别标签有序 { bool swap_fl = false; for( j1 = 0; j1 < j; j1++ )//c从前往后,比较浅j个数 if( sort_buf[j1].i > sort_buf[j1+1].i )//从小到大排序 { int t; CV_SWAP( sort_buf[j1].i, sort_buf[j1+1].i, t ); swap_fl = true; } if( !swap_fl )//如果已经有序,则跳出循环 break; } best_val.i = 0;//记录样本数最多的类别 for( j = 1; j <= k1; j++ )//冒泡排序k1-1次 if( j == k1 || sort_buf[j].i != sort_buf[j-1].i ) {//遇到新的类别 cur_count = j - prev_start;//类别计算 if( best_count < cur_count ) { best_count = cur_count; best_val.i = sort_buf[j-1].i; } prev_start = j; } r = best_val.f; } if( start+i == 0 ) result = r; if( _results ) _results->data.fl[(start + i)*rstep] = r; } if( _neighbor_responses ) { dst = (float*)(_neighbor_responses->data.ptr + (start + i)*_neighbor_responses->step); for( j = 0; j < k1; j++ ) dst[j] = nr[j].f;//从nr到dst for( ; j < k; j++ ) dst[j] = 0.f; } if( _dist ) { dst = (float*)(_dist->data.ptr + (start + i)*_dist->step); for( j = 0; j < k1; j++ ) dst[j] = dist[j + i*k]; for( ; j < k; j++ ) dst[j] = 0.f; } } return result; }
(转载请注明作者和出处:http://blog.csdn.net/CHIERYU 未经允许请勿用于商业用途)
相关文章推荐
- python中使用OpenCV进行人脸检测的例子
- opencv 做人脸识别 opencv 人脸匹配分析
- 使用opencv拉伸图像扩大分辨率示例
- OpenCV 2.4.3 C++ 平滑处理分析
- 利用Python和OpenCV库将URL转换为OpenCV格式的方法
- python结合opencv实现人脸检测与跟踪
- 在树莓派2或树莓派B+上安装Python和OpenCV的教程
- opencv-python学习一--人脸检测
- 在Ubuntu上安装OpenCV3.0和Python-openCV的经历
- OpenCV配置,从来没有这么简单!
- ubuntu下opencv和qt的安装配置
- 数据挖掘分类算法的评价指标
- OpenCV学习笔记(二十五)——OpenCV图形界面设计Qt+VS2008
- 分享一些OpenCV实现立体视觉的经验
- 关于OpenCv图像变换与基本图形检测
- "应用程序正常初始化失败"-0xc0150002 解决办法
- OpenCV->HSV色彩空间
- opencv 内存泄露
- OpenCV函数cvFindContours
- OpenCV 2.3.1图像文件的读入和显示