原文:

http://blog.csdn.net/zhaocj/article/details/50764093

一、原理

K近邻算法(KNN,K-NearestNeighbors)是一种非常简单的机器学习方法,它既可以处理分类问题,也可以处理回归问题,而且它的执行效果非常好。

KNN是一种懒惰学习算法(lazy learningalgorithm)。所谓懒惰算法指的是,直到有了新的测试样本,该算法才开始依据训练样本进行样本的预测处理工作,也就是说该算法事先不会对训练样本进行任何的处理,只会“懒散”的等待测试样本的到来,然后才开始工作。与懒惰学习算法相对应的是渴望学习算法(eager learning algorithm),它会在测试之前利用训练样本建立一个统一的、与输入量相独立的目标函数,测试样本就是利用该函数进行预测,因此一旦目标函数创建完成,训练样本数据就可以完全抛弃掉。与渴望学习算法相比,懒惰学习算法所构建的目标函数能够更近似测试样本数据本身,但同时它需要更大的存储空间用于存储训练样本数据。总之,懒惰学习算法非常适用于那些需要处理具有较少特征属性的大型数据库的问题。

下面我们给出KNN算法的原理。

在训练样本集中,每个样本都是一个具有n个特征属性的向量,即x = (x1,x2,…,xn),因此可以认为每个样本在n维特征空间,或度量空间内分布。每个样本还有一个唯一属于它的标签y,机器学习的目的就是找到这样的一个函数f,使y=f(x),这样当有一个新的样本u时,我们就可以通过该目标函数确定它的标签。

既然样本可以被认为是在度量空间内分布,那么我们就可以用距离测度来衡量它们的相似程度。常用的距离测度包括欧氏距离和曼哈顿距离,以及更一般的明氏距离,式1~式3分别给出了测试样本u和训练样本x的这三种距离公式:

(1)

(2)

(3)

上述三个距离测度只适用于特征属性是连续变量的情况,当特征属性是离散变量时,如对文本进行分类,我们就需要用汉明距离:

(4)

KNN的任务就是在训练样本集中,依据距离测度找到与测试样本u最相似的那K个训练样本x。对于分类问题,我们采用“多数表决”的方式来确定u的最终分类,即这K个训练样本中,哪个分类的样本数多,u就属于哪个分类。而对于回归问题,u的预测值v为:

(5)

对于KNN来说,有一个最重要的参数需要事先确定,那就是K值。选择不同的K值,最终的预测结果可能会不同。K值选取的过小,会引入误差,而K值过大,虽然更准确,但会使在特征空间内明确的边界变得模糊。因此K值既要足够的大,以保证预测结果的正确性,又要足够的小,以使K个训练样本与测试样本具有一定的相似性。目前选择K值比较常用的方法是交叉验证方法(cross-validation)。

另一个需要讨论的问题是,在前面的计算中,我们认为这K个与测试样本距离最相似的训练样本对测试样本具有相同的影响,也就是我们并没有考虑训练样本与测试样本之间距离大小的影响。对于这个问题,我们可以使用距离权值来解决,即让距离更小的样本具有更大的权值。K个最邻近样本中第i个样本xi与测试样本u的权值可以定义为:

(6)

式中,D(xi,u)表示式1~式4中的任一距离。对于回归问题,最终的预测结果v为:

(7)

而对于分类问题,最终的分类结果为权值最大的那个类。

需要说明的是,OpenCV并没有采用交叉验证和距离权值的方法。

二、源码分析

CvKNearest类的两个构造函数:

CvKNearest::CvKNearest()
{samples = 0;    //表示训练样本数据clear();    //主要是清空变量samples
}
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 );
}

其中,_train_data表示训练样本数据,_responses表示训练样本数据的响应值,_sample_idx表示训练样本数据的索引,即只使用该索引所指定的那些样本,_is_regression表示KNN的类型,即是分类问题还是回归问题,默认为false,是分类问题,_max_k表示近邻的最大数量,默认为32。

KNN算法的训练函数train。因为KNN是懒惰学习算法,因此该函数的作用是初始化训练样本数据,为真正的测试样本做准备。

bool CvKNearest::train( const CvMat* _train_data, const CvMat* _responses,const CvMat* _sample_idx, bool _is_regression,int _max_k, bool _update_base )
//_update_base表示该KNN模型是不是仅仅需要更新样本数据,_update_base为true表示使用新的样本数据更新原有的KNN模型,_update_base为false(默认)表示应用该样本数据构建新的KNN模型
{bool ok = false;    //该函数返回标识变量,表示是否训练成功CvMat* responses = 0;    //表示样本响应值CV_FUNCNAME( "CvKNearest::train" );    __BEGIN__;CvVectors* _samples = 0;float** _data = 0;    //表示完整的样本数据//_count表示训练样本的数量,_dims和_dims_all都表示特征属性的数量,_rsize表示样本响应值的大小int _count = 0, _dims = 0, _dims_all = 0, _rsize = 0;if( !_update_base )    //重新构建KNN模型,则清空原有样本数据samplesclear();// Prepare training data and related parameters.// Treat categorical responses as ordered - to prevent class label compression and// to enable entering new classes in the updates//调用cvPrepareTrainData函数,为KNN算法准备样本数据,即初始化样本。首先检查样本_train_data,该变量矩阵一定要是CV_ROW_SAMPLE,即矩阵的行表示样本,列表示特征属性,并且不能有缺失的属性;然后根据_sample_idx确定那些真正要用到的样本数据;再确定样本的响应值,它必须为CV_VAR_ORDERED;最终得到完整的样本数据_data。CV_CALL( cvPrepareTrainData( "CvKNearest::train", _train_data, CV_ROW_SAMPLE,_responses, CV_VAR_ORDERED, 0, _sample_idx, true, (const float***)&_data,&_count, &_dims, &_dims_all, &responses, 0, 0 ));//确保变量responses正确if( !responses )CV_ERROR( CV_StsNoMem, "Could not allocate memory for responses" );//确保应用所有的特征属性if( _update_base && _dims != var_count )CV_ERROR( CV_StsBadArg, "The newly added data have different dimensionality" );if( !_update_base )    //如果是重新构建KNN模型{if( _max_k < 1 )    //确保参数_max_k必须是正数CV_ERROR( CV_StsOutOfRange, "max_k must be a positive number" );//赋值regression = _is_regression;var_count = _dims;max_k = _max_k;}_rsize = _count*sizeof(float);    //定义样本响应值的大小,CV_CALL( _samples = (CvVectors*)cvAlloc( sizeof(*_samples) + _rsize ));    //分配空间//为样本数据赋值_samples->next = samples;_samples->type = CV_32F;_samples->data.fl = _data;_samples->count = _count;total += _count;samples = _samples;//复制样本响应值responsesmemcpy( _samples + 1, responses->data.fl, _rsize );ok = true;__END__;if( responses && responses->data.ptr != _responses->data.ptr )cvReleaseMat(&responses);return ok;
}

实现KNN算法的find_nearest函数:

float CvKNearest::find_nearest( const CvMat* _samples, int k, CvMat* _results,const float** _neighbors, CvMat* _neighbor_responses, CvMat* _dist ) const
//_samples表示待测试的样本数据,该变量必须是矩阵形式,行表示样本,列表示特征属性,数据类型为CV_32FC1,而且该样本的特征属性的数量必须与训练样本的特征属性的数量一致
//k表示KNN算法的参数K,它的值应该在1和max_k之间
//_results表示测试样本的预测结果,为向量的形式,向量中元素的数量必须为测试样本的数量,即如果是一次预测多个样本,则预测结果用该变量表示。如果是分类问题,数据类型为CV_32SC1,如果是回归问题,则为CV_32FC1
//_neighbors表示返回测试样本的k个最邻近的训练样本
//_neighbor_responses表示返回测试样本的k个最邻近训练样本的响应值,该参数为矩阵形式,数据类型为CV_32FC1,矩阵的行表示测试样本,矩阵的列表示k个响应值
//_dist表示返回测试样本的k个最邻近训练样本与测试样本的距离,该参数为矩阵形式,数据类型为CV_32FC1,矩阵的行表示测试样本,矩阵的列表示k个距离
{//该变量为该函数的返回值,它表示如果只有一个测试样本,则该函数就用该变量返回预测结果float result = 0.f; //用于开辟一块内存空间的最大常数const int max_blk_count = 128, max_buf_sz = 1 << 12;//确保训练样本数据已准备好if( !samples )CV_Error( CV_StsError, "The search tree must be constructed first using train method" );//确保参数_samples的正确性if( !CV_IS_MAT(_samples) ||CV_MAT_TYPE(_samples->type) != CV_32FC1 ||_samples->cols != var_count )CV_Error( CV_StsBadArg, "Input samples must be floating-point matrix (<num_samples>x<var_count>)" );//确保参数_results的正确性if( _results && (!CV_IS_MAT(_results) ||(_results->cols != 1 && _results->rows != 1) ||_results->cols + _results->rows - 1 != _samples->rows) )CV_Error( CV_StsBadArg,"The results must be 1d vector containing as much elements as the number of samples" );//确保参数_results的数据类型的正确性if( _results && CV_MAT_TYPE(_results->type) != CV_32FC1 &&(CV_MAT_TYPE(_results->type) != CV_32SC1 || regression))CV_Error( CV_StsUnsupportedFormat,"The results must be floating-point or integer (in case of classification) vector" );//确保参数k在正确区间范围内if( k < 1 || k > max_k )CV_Error( CV_StsOutOfRange, "k must be within 1..max_k range" );//确保参数_neighbor_responses的正确性if( _neighbor_responses ){if( !CV_IS_MAT(_neighbor_responses) || CV_MAT_TYPE(_neighbor_responses->type) != CV_32FC1 ||_neighbor_responses->rows != _samples->rows || _neighbor_responses->cols != k )CV_Error( CV_StsBadArg,"The neighbor responses (if present) must be floating-point matrix of <num_samples> x <k> size" );}//确保参数_dist的正确性if( _dist ){if( !CV_IS_MAT(_dist) || CV_MAT_TYPE(_dist->type) != CV_32FC1 ||_dist->rows != _samples->rows || _dist->cols != k )CV_Error( CV_StsBadArg,"The distances from the neighbors (if present) must be floating-point matrix of <num_samples> x <k> size" );}int count = _samples->rows;    //表示测试样本的数量int count_scale = k*2;    int blk_count0 = MIN( count, max_blk_count );int buf_sz = MIN( blk_count0 * count_scale, max_buf_sz );blk_count0 = MAX( buf_sz/count_scale, 1 );blk_count0 += blk_count0 % 2;blk_count0 = MIN( blk_count0, count );buf_sz = blk_count0 * count_scale + k;    //表示开辟内存空间的大小int k1 = get_sample_count();    //得到训练样本的数量//选取k1和k的最小值,一般来说,最终的k1就等于KNN中的参数kk1 = MIN( k1, k );//调用P1函数,并行处理每个测试样本,该语句需要TBB库支持cv::parallel_for_(cv::Range(0, count), P1(this, buf_sz, k, _samples, _neighbors, k1,_results, _neighbor_responses, _dist, &result));return result;
}

结构P1:

struct P1 : cv::ParallelLoopBody {P1(const CvKNearest* _pointer, int _buf_sz, int _k, const CvMat* __samples, const float** __neighbors,int _k1, CvMat* __results, CvMat* __neighbor_responses, CvMat* __dist, float* _result){pointer = _pointer;k = _k;_samples = __samples;_neighbors = __neighbors;k1 = _k1;_results = __results;_neighbor_responses = __neighbor_responses;_dist = __dist;result = _result;buf_sz = _buf_sz;}const CvKNearest* pointer;int k;const CvMat* _samples;const float** _neighbors;int k1;CvMat* _results;CvMat* _neighbor_responses;CvMat* _dist;float* result;int buf_sz;//重载()运算符void operator()( const cv::Range& range ) const{cv::AutoBuffer<float> buf(buf_sz);    //定义一块内存空间for(int i = range.start; i < range.end; i += 1 )    //遍历所有测试样本{float* neighbor_responses = &buf[0];    //定义neighbor_responses所用空间float* dist = neighbor_responses + 1*k;    //定义dist所用空间Cv32suf* sort_buf = (Cv32suf*)(dist + 1*k);    //定义sort_buf所用空间//调用find_neighbors_direct函数,得到k个最邻近的训练样本_neighbors、训练样本响应值neighbor_responses、以及距离distpointer->find_neighbors_direct( _samples, k, i, i + 1,neighbor_responses, _neighbors, dist );//调用write_results函数,得到最终的测试样本的响应值,即预测结果,该函数的返回值为只有一个测试样本时的响应值,如果是多个测试样本,则响应值为_resultsfloat r = pointer->write_results( k, k1, i, i + 1, neighbor_responses, dist,_results, _neighbor_responses, _dist, sort_buf );if( i == 0 )    //如果只有一个测试样本,则赋值该样本的响应值*result = r;}}};

计算测试样本的KNN,得到这k个训练样本neighbors,它们的响应值neighbor_responses,以及距离值dist:

void CvKNearest::find_neighbors_direct( const CvMat* _samples, int k, int start, int end,float* neighbor_responses, const float** neighbors, float* dist ) const
{//count表示测试样本的数量,其实该值为1,d表示特征属性的数量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++ )    //遍历测试样本{// sum表示距离,即式1的结果,这里为了减小误差,并没有对距离进行开方运算,也就是只计算了根号内的值double sum = 0; Cv32suf si;const float* v = s->data.fl[j];    //得到当前训练样本//得到当前测试样本const float* u = (float*)(_samples->data.ptr + _samples->step*(start + i));//dd表示当前测试样本与k个最邻近的训练样本的距离,该dd数组是按照由小到大的距离顺序排列的,即dd[0]距离最小,dd[k-1]最大Cv32suf* dd = (Cv32suf*)(dist + i*k);float* nr;const float** nn;int t, ii, ii1;//应用式1,计算距离,在这里为了加快运算速度,按照每4个特征属性为一组进行一次for循环计算,不够4个的则应用下一个for循环for( t = 0; t <= d - 4; t += 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++ ){double t0 = u[t] - v[t];sum += t0*t0;}si.f = (float)sum;    //赋值//对数组dd由后往前依次与当前计算的距离(sum)比较,由于dd的存储顺序是从小到大的顺序,因此当满足si.i > dd[ii].i时,此时的索引ii所对应的距离正好是小于si.i的最大的值for( ii = k1-1; ii >= 0; ii-- )if( si.i > dd[ii].i )break;//当ii >= k-1时,说明数组dd中已有k个距离值,并且当前计算的距离sum都大于这k个距离值,所以对此次计算的距离不做任何处理,直接舍弃if( ii >= k-1 )continue;//nr表示测试样本的k个最邻近训练样本的响应值nr = neighbor_responses + i*k;//nn表示测试样本的k个最邻近的训练样本nn = neighbors ? neighbors + (start + i)*k : 0;//dd、nr和nn分别对应于参数_dist、_neighbor_responses和_neighbors,它们的排列顺序都是一致的,完全是按照测试样本的k个最邻近训练样本的距离值,从小到大的顺序排列的//把索引ii以后的数组dd、nr和nn的值依次向后移一位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];}//把当前计算的距离si.i放到数组dd中的第ii+1的位置上,则数组dd仍然是按照从小到大的顺序排列的dd[ii+1].i = si.i;//把当前距离所对应的训练样本的响应值放到数组nr中的第ii+1的位置上,与数组dd中的元素相对应nr[ii+1] = ((float*)(s + 1))[j];//把当前距离所对应的训练样本放到数组nn中的第ii+1的位置上,与数组dd中的元素相对应if( nn )nn[ii+1] = v;}//同时逐一更新k1和k2值,即k1等于k2,直至k1大于k时,则k1始终为k,k2始终为k-1k1 = MIN( k1+1, k );k2 = MIN( k1, k-1 );}}
}

计算测试样本的最终响应值,即预测结果:

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;//count表示测试样本的数量,其实该值为1int i, j, j1, count = end - start;// inv_scale表示k的倒数,即式5的分母部分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++ ){//得到当前测试样本的k个最邻近训练样本的响应值const Cv32suf* nr = (const Cv32suf*)(neighbor_responses + i*k);float* dst;float r;//_results有值表示有多个测试样本,start+i为0表示只有一个测试样本if( _results || start+i == 0 ){if( regression )    //回归问题{double s = 0;//得到k个最邻近训练样本的响应值之和,即式5的分子部分for( j = 0; j < k1; j++ )s += nr[j].f;//式5r = (float)(s*inv_scale);}else    //分类问题{int prev_start = 0, best_count = 0, cur_count;Cv32suf best_val;//k个最邻近训练样本的响应值重新赋值给数组sort_buffor( j = 0; j < k1; j++ )sort_buf[j].i = nr[j].i;//对这k个响应值按照由小到大的顺序进行排序for( j = k1-1; j > 0; j-- ){bool swap_fl = false;for( j1 = 0; j1 < j; j1++ )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;    //表示表决结果,即最佳响应值//按照“多数表决”的原则,选择相同的响应值的数量最多的那个响应值//遍历所有k个样本for( j = 1; j <= k1; j++ )// j == k1说明表决结束,应该要给出一个结果来;sort_buf[j].i != sort_buf[j-1].i说明相邻的两个样本的响应值不相同,这时就需要统计与前一个响应值相同的样本数量if( j == k1 || sort_buf[j].i != sort_buf[j-1].i ){//得到与前一个响应值相同的样本数量cur_count = j - prev_start;//比较当前得到样本数量cur_count和以前得到的相同响应值最多的样本数量best_count,如果当前的样本数量多,则更新best_countif( best_count < cur_count ){best_count = cur_count;    //更新best_countbest_val.i = sort_buf[j-1].i;    //得到表决结果}prev_start = j;    //更新prev_start,即计数值的起始索引}r = best_val.f;    //得到最终的表决结果}if( start+i == 0 )    //只有一个测试样本result = r;    //赋值if( _results )    //有不止一个的测试样本,则响应值依次放入_results中_results->data.fl[(start + i)*rstep] = r;}//把k个最邻近的训练样本的响应值放入dst中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;for( ; j < k; j++ )dst[j] = 0.f;}//把k个最邻近的训练样本与测试样本的距离放入dst中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;    //返回只有一个测试样本时的响应值
}

三、应用实例

下面我们就给出一个具体的应用实例。

有两种指标可以用于评价纸巾的好坏:耐酸性和强度。下面给出一组调查数据:

耐酸性(单位:秒)

强度(单位:公斤/每平方米)

分类(好:G;差:B)

4

3

B

1

3

B

3

3

B

3

7

B

3

4

B

4

1

B

1

4

B

5

6

B

3

7

B

6

2

B

4

6

G

4

4

G

5

8

G

7

8

G

7

7

G

10

5

G

7

6

G

4

10

G

9

7

G

5

4

G

8

5

G

6

6

G

7

4

G

8

8

G

则我们预测某一样本,它的耐酸性和强度分别为4和5,具体程序如下所示:

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/ml/ml.hpp"#include <iostream>
using namespace cv;
using namespace std;int main( int argc, char** argv )
{   float trainingData[24][2]={    {4, 3},{1, 3},{3, 3},{3, 7},{3, 4},{4, 1},{1, 4},{5, 6},{3, 7},{6, 2},{4, 6},{4, 4},{5, 8},{7, 8},{7, 7},{10, 5},{7, 6},{4, 10},{9, 7},{5, 4},{8, 5},{6, 6},{7, 4},{8, 8}   };CvMat trainingDataCvMat = cvMat( 24, 2, CV_32FC1, trainingData );float responses[24] = {'B','B','B','B','B','B','B','B','B','B','G','G','G','G','G','G','G','G','G','G','G','G','G','G'};CvMat responsesCvMat = cvMat( 24, 1, CV_32FC1, responses );//参数false表示分类问题CvKNearest knn( &trainingDataCvMat, &responsesCvMat, 0, false, 32 );int K = 7;    //KNN算法的参数K值float myData[2] = {4, 5};CvMat myDataCvMat = cvMat(1, 2, CV_32FC1, myData);//nearests表示K个最邻近样本的响应值CvMat* nearests = cvCreateMat( 1, K, CV_32FC1);float r = knn.find_nearest(&myDataCvMat, K, 0, 0, nearests, 0);cout<<"result:  "<<(char)r<<endl;cout<<K<<" nearest responses:";for (int i=0 ;i<K;i++)cout<<"  "<<(char)nearests->data.fl[i];return 0;}

最终的结果为:

result: G

7 nearest responses:  G G  G  B  B  B  G

KNN(七)--最近邻及OpenCV源码分析相关推荐

  1. 贝叶斯(朴素贝叶斯,正太贝叶斯)及OpenCV源码分析

    一.原理 OpenCV实现的贝叶斯分类器不是我们所熟悉的朴素贝叶斯分类器(Naïve Bayes Classifier),而是正态贝叶斯分类器(Normal Bayes Classifier),两者虽 ...

  2. SVM算法及OpenCV源码分析

    关于SVM原理,请参看: 系统学习机器学习之SVM(一) 系统学习机器学习之SVM(二) 系统学习机器学习之SVM(三)--Liblinear,LibSVM使用整理,总结 系统学习机器学习之SVM(四 ...

  3. 决策树(十)--GBDT及OpenCV源码分析

    一.原理 梯度提升树(GBT,Gradient Boosted Trees,或称为梯度提升决策树)算法是由Friedman于1999年首次完整的提出,该算法可以实现回归.分类和排序.GBT的优点是特征 ...

  4. 决策树(八)--随机森林及OpenCV源码分析

    原文: http://blog.csdn.net/zhaocj/article/details/51580092 一.原理 随机森林(Random Forest)的思想最早是由Ho于1995年首次提出 ...

  5. 决策树(七)--Boost及源码分析

    原文:http://blog.csdn.net/zhaocj/article/details/50536385 一.原理 AdaBoost(Adaptive Boosting,自适应提升)算法是由来自 ...

  6. Spark详解(七):SparkContext源码分析以及整体作业提交流程

    1. SparkContext源码分析 在任何Spark程序中,必须要创建一个SparkContext,在SparkContext中,最主要的就是创建了TaskScheduler和DAGSchedul ...

  7. 决策树(九)--极端随机森林及OpenCV源码分析

    原文: http://blog.csdn.net/zhaocj/article/details/51648966 一.原理 ET或Extra-Trees(Extremely randomized tr ...

  8. 基于边缘的图像分割——分水岭算法(watershed)算法分析(附opencv源码分析)

    最近需要做一个图像分割的程序,查了opencv的源代码,发现opencv里实现的图像分割一共有两个方法,watershed和mean-shift算法.这两个算法的具体实现都在segmentation. ...

  9. 【opencv】goodFeaturesToTrack源码分析-2-Shi-Tomasi角点检测

    本文章是[opencv]goodFeaturesToTrack源码分析-1的后续,主要描述Shi-Tomasi角点检测算法原理及opencv实现. 1.算法原理 Shi-Tomasi算法是Harris ...

最新文章

  1. java016.集合
  2. [翻译] TWRPickerSlider
  3. CodeFrist基础_迁移更新数据
  4. 文字创作类App分享-简书
  5. linux程序 option,long-option.c/解析命令行参数
  6. 从Unreal Engine 3到Unreal Engine 4
  7. react-native 第三方库
  8. Android内核开发 Linux C编程调用内核模块设备驱动
  9. AccelStepper步进电机库简介操控28BYJ-48步进电机
  10. E-mail和IM真的应该被监控么?
  11. Linux抓包(wireshark+tcpdump)
  12. 博科Brocade 300光纤交换机配置zone教程
  13. 错误:Cannot construct instance of `xxxx` (no Creators, like default construct , exist): cannot .......
  14. centos7下载busybox
  15. android 闪屏动态界面,Android实现闪屏欢迎界面
  16. python设置excel单元格格式_Python帮你做Excel——格式设置与画图
  17. 京东商品数据分析,教你一键分析数据
  18. office@microsoft365@官方在线安装@第三方离线下载并安装(word+ppt+excel)
  19. Ant Design Vue DatePicker 日期选择框 限制可选时间
  20. 字符串数组大写小写互相转换

热门文章

  1. ssh X11 Forwarding(本地共享远程服务器界面)
  2. 你看那个人他像一条狗
  3. Android.mk中的LOCAL_OVERRIDES_PACKAGES
  4. 大一python考试知识点_Python基础知识点(精心整理)
  5. 中点击按钮新建widget_如何在iOS14中创建堆叠小组件?
  6. icmp协议_计算机网络基础(七)网络层ICMP协议
  7. python面试题及答案 2019-这些2019年常考的Python面试题你都能答上来吗?
  8. 解决Android的adb命令行报错Permission denied
  9. spring学习--AOP--JDK动态代理
  10. java的linux内核构建,构建一个Docker 的Java编译环境