准备知识

在文件”opencv\sources\modules\ml\src\precomp.hpp“中

有cvPrepareTrainData的函数原型。

int
cvPrepareTrainData( const char* /*funcname*/,
const CvMat* train_data, int tflag,
const CvMat* responses, int response_type,
const CvMat* var_idx,
const CvMat* sample_idx,
bool always_copy_data,
const float*** out_train_samples,
int* _sample_count,
int* _var_count,
int* _var_all,
CvMat** out_responses,
CvMat** out_response_map,
CvMat** out_var_idx,
CvMat** out_sample_idx=0 )

从函数原型的参数可以看出主要为:

const char* /funcname/: 函数的名称

const CvMat* train_data, int tflag,: 训练集、训练集样本的布局

const CvMat* responses, int response_type,:训练集标签、对应数据类型

const CvMat* var_idx,: 用了哪些特征

const CvMat* sample_idx,: 用了哪些样本

bool always_copy_data,: 是否复制数据集

const float*** out_train_samples,: 输出处理过的的训练集

int* _sample_count, : 样本的总数

int* _var_count,: 特征的总数

int* _var_all,

CvMat** out_responses,: 输出训练集标签

CvMat** out_response_map,

CvMat** out_var_idx, : 输出用了哪些特征

CvMat** out_sample_idx=0 : 默认输出为使用了所有的样本

在文件”opencv\sources\modules\ml\src\inner_functions.cpp“中有cvPrepareTrainData的函数实现:

int
cvPrepareTrainData( const char* /*funcname*/,
const CvMat* train_data, int tflag,
const CvMat* responses, int response_type,
const CvMat* var_idx,
const CvMat* sample_idx,
bool always_copy_data,
const float*** out_train_samples,
int* _sample_count,
int* _var_count,
int* _var_all,
CvMat** out_responses,
CvMat** out_response_map,
CvMat** out_var_idx,
CvMat** out_sample_idx )
{
int ok = 0;//用于标记该函数是否成功执行
CvMat* _var_idx = 0;//默认使用所有的特征
CvMat* _sample_idx = 0;//默认使用所有的样本
CvMat* _responses = 0;
int sample_all = 0, sample_count = 0, var_all = 0, var_count = 0; CV_FUNCNAME( "cvPrepareTrainData" ); // step 0. clear all the output pointers to ensure we do not try
// to call free() with uninitialized pointers
//第0步,先释放所有输出的指针以确保不会有未初始化的指针。
if( out_responses )
*out_responses = 0; if( out_response_map )
*out_response_map = 0; if( out_var_idx )
*out_var_idx = 0; if( out_sample_idx )
*out_sample_idx = 0; if( out_train_samples )
*out_train_samples = 0; if( _sample_count )
*_sample_count = 0; if( _var_count )
*_var_count = 0; if( _var_all )
*_var_all = 0;
//重置完成
__BEGIN__; if( !out_train_samples )
CV_ERROR( CV_StsBadArg, "output pointer to train samples is NULL" ); CV_CALL( cvCheckTrainData( train_data, tflag, 0, &var_all, &sample_all )); if( sample_idx )
CV_CALL( _sample_idx = cvPreprocessIndexArray( sample_idx, sample_all ));
if( var_idx )
CV_CALL( _var_idx = cvPreprocessIndexArray( var_idx, var_all )); if( responses )
{
if( !out_responses )
CV_ERROR( CV_StsNullPtr, "output response pointer is NULL" ); if( response_type == CV_VAR_NUMERICAL )
{
CV_CALL( _responses = cvPreprocessOrderedResponses( responses,
_sample_idx, sample_all ));
}
else
{
CV_CALL( _responses = cvPreprocessCategoricalResponses( responses,
_sample_idx, sample_all, out_response_map, 0 ));
}
} CV_CALL( *out_train_samples =
cvGetTrainSamples( train_data, tflag, _var_idx, _sample_idx,
&var_count, &sample_count, always_copy_data )); ok = 1; __END__;
//如果上面的操作都结束了,那么检测对应的输出需要的指针,是否已经初始化,然后接着对各自的输出指针指向的对象进行置0初始化
if( ok )
{
if( out_responses )
*out_responses = _responses, _responses = 0; if( out_var_idx )
*out_var_idx = _var_idx, _var_idx = 0; if( out_sample_idx )
*out_sample_idx = _sample_idx, _sample_idx = 0; if( _sample_count )
*_sample_count = sample_count; if( _var_count )
*_var_count = var_count; if( _var_all )
*_var_all = var_all;
}
else
{
if( out_response_map )
cvReleaseMat( out_response_map );
cvFree( out_train_samples );
} if( _responses != responses )
cvReleaseMat( &_responses );
cvReleaseMat( &_var_idx );
cvReleaseMat( &_sample_idx ); return ok;//返回该函数是否成功执行
}

首先在文件“opencv\sources\modules\ml\include\opencv2\ml\ml.hpp”中有:

#define CV_TYPE_NAME_ML_KNN     "opencv-ml-knn"
struct CvVectors
{
int type;
int dims, count;
CvVectors* next;
union
{
uchar** ptr;
float** fl;
double** db;
} data;
}; /*********K-Nearest Neighbour Classifier **********/ class CV_EXPORTS_W CvKNearest : public CvStatModel
{
public: CV_WRAP CvKNearest();//默认构造函数
virtual ~CvKNearest();//虚析构函数
//2个重载的构造函数,参数中一个是CvMat,一个是Mat
//trainData:训练集
//responses:训练集的目标值
//sampleIdx:用来指定使用哪些训练样本,0表示使用所有的训练样本
//isRegression:trueb表示knn作为回归,否则为分类器
//max_k:K的上限
CvKNearest( const CvMat* trainData, const CvMat* responses,
const CvMat* sampleIdx=0, bool isRegression=false, int max_k=32 ); CV_WRAP CvKNearest( const cv::Mat& trainData, const cv::Mat& responses,
const cv::Mat& sampleIdx=cv::Mat(), bool isRegression=false, int max_k=32 );
//训练模型,两个重载函数,一个使用的是CvMat;一个使用的是Mat;Mat的会调用CvMat的train实现训练功能。
//updateBase:用于指定该模型是从头开始训练?(update_base=false),还是使用新的训练数据来进行更新
//(update_base=true).在后者中,参数maxK不能大于原始的值
virtual bool train( const CvMat* trainData, const CvMat* responses,
const CvMat* sampleIdx=0, bool is_regression=false,
int maxK=32, bool updateBase=false ); CV_WRAP virtual bool train( const cv::Mat& trainData, const cv::Mat& responses,
const cv::Mat& sampleIdx=cv::Mat(), bool isRegression=false,
int maxK=32, bool updateBase=false );
//寻找邻居 然后预测输入向量的目标:3个重载函数
virtual float find_nearest( const CvMat* samples, int k, CV_OUT CvMat* results=0,
const float** neighbors=0, CV_OUT CvMat* neighborResponses=0, CV_OUT CvMat* dist=0 ) const; virtual float find_nearest( const cv::Mat& samples, int k, cv::Mat* results=0,
const float** neighbors=0, cv::Mat* neighborResponses=0,
cv::Mat* dist=0 ) const; CV_WRAP virtual float find_nearest( const cv::Mat& samples, int k, CV_OUT cv::Mat& results,
CV_OUT cv::Mat& neighborResponses, CV_OUT cv::Mat& dists) const;
//
virtual void find_neighbors_direct( const CvMat* _samples, int k, int start, int end,
float* neighbor_responses, const float** neighbors, float* dist ) const; virtual void clear();
int get_max_k() const;//返回近邻中最大值的个数
int get_var_count() const;//返回特征的维度(变量的个数)
int get_sample_count() const;//返回训练样本的个数
bool is_regression() const;//返回的true为回归;false为分类
//将结果记录下来
virtual float 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; protected: int max_k, var_count;//最大的k值;特征维度
int total;//
bool regression;//是否是回归
CvVectors* samples;//样本向量
}; typedef CvKNearest KNearest;

对于该文件的实现和上面的类定义一样,为了兼容opencv1.0版本,所以之前的CvMat都是1.0的形式;而在后面Mat的都是opencv2.0的形式。

下面是文件“\opencv\sources\modules\ml\src\knearest.cpp”中实现部分:

#include "precomp.hpp"

/********K-Nearest Neighbors Classifier ***********/
//1、opencv1.0形式
// k Nearest Neighbors
//默认构造函数
CvKNearest::CvKNearest()
{
samples = 0;//指针初始化
clear();
} //析构函数
CvKNearest::~CvKNearest()
{
clear();//调用清除函数,该函数在下面
} //构造函数
//_train_data:训练集合,行为样本,列为特征
// _responses:训练集合对应的类别目标 CvKNearest::CvKNearest( const CvMat* _train_data, const CvMat* _responses,
const CvMat* _sample_idx, bool _is_regression, int _max_k )
{
samples = 0;//指针赋值为0
//调用类成员函数train来训练模型
train( _train_data, _responses, _sample_idx, _is_regression, _max_k, false );
} void CvKNearest::clear()
{
//类似释放链表一样释放所有的样本
while( samples )
{
CvVectors* next_samples = samples->next;
cvFree( &samples->data.fl );
cvFree( &samples );
samples = next_samples;
}
//将对应的类成员置0
var_count = 0;
total = 0;
max_k = 0;
} //返回类成员max_k,表示K的最大上限
int CvKNearest::get_max_k() const { return max_k; }
//返回样本的特征维度
int CvKNearest::get_var_count() const { return var_count; }
//返回当前knn作为分类器,还是用来做回归
bool CvKNearest::is_regression() const { return regression; }
//返回训练集的样本总数
int CvKNearest::get_sample_count() const { return total; } //模型训练函数。主要是对训练数据进行排序,然后采用合适的数据结构来存储数据集。
bool CvKNearest::train( const CvMat* _train_data, const CvMat* _responses,
const CvMat* _sample_idx, bool _is_regression,
int _max_k, bool _update_base )
{
bool ok = false;//用来标记该train函数是否成功执行
CvMat* responses = 0; CV_FUNCNAME( "CvKNearest::train" ); __BEGIN__; CvVectors* _samples = 0;
float** _data = 0;
int _count = 0, _dims = 0, _dims_all = 0, _rsize = 0; if( !_update_base )
clear(); // 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函数来完成
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 )); 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 )
{
//如果用户设定的k小于1,那么报错
if( _max_k < 1 )
CV_ERROR( CV_StsOutOfRange, "max_k must be a positive number" ); regression = _is_regression;//是否用作回归
var_count = _dims;//特征的维度
max_k = _max_k;//k的上限
} _rsize = _count*sizeof(float); _samples->next = samples;
_samples->type = CV_32F;
_samples->data.fl = _data;
_samples->count = _count;
total += _count; samples = _samples;
memcpy( _samples + 1, responses->data.fl, _rsize ); ok = true; __END__; if( responses && responses->data.ptr != _responses->data.ptr )
cvReleaseMat(&responses); return ok;
} 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++ )
{
double sum = 0;
Cv32suf si;
const float* v = s->data.fl[j];
const float* u = (float*)(_samples->data.ptr + _samples->step*(start + i));
Cv32suf* dd = (Cv32suf*)(dist + i*k);
float* nr;
const float** nn;
int t, ii, ii1; 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;
for( ii = k1-1; ii >= 0; ii-- )
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;
nr[ii+1] = ((float*)(s + 1))[j];
if( nn )
nn[ii+1] = v;
}
k1 = 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;
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++ )
{
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++ )
sort_buf[j].i = nr[j].i; 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;
for( j = 1; j <= k1; j++ )
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;
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;
} 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];
float* dist = neighbor_responses + 1*k;
Cv32suf* sort_buf = (Cv32suf*)(dist + 1*k); pointer->find_neighbors_direct( _samples, k, i, i + 1,
neighbor_responses, _neighbors, dist ); float r = pointer->write_results( k, k1, i, i + 1, neighbor_responses, dist,
_results, _neighbor_responses, _dist, sort_buf ); if( i == 0 )
*result = r;
}
} };
//对每个输入向量(表示为_sample矩阵的每一行),该方法找到k(k≤get_max_k() )个最近邻。
//在回归中,预测结果将是响应的近邻向量的均值。在分类中,通过投票决定类别结果。 //对传统分类和回归预测来说,该方法可以有选择的返回近邻向量本身的指针
//(neighbors, array of k*_samples->rows pointers),
//它们相对应的输出值(neighbor_responses, a vector of k*_samples->rows elements) ,
//和输入向量与近邻之间的距离(dist, also a vector of k*_samples->rows elements)。 //对每个输入向量来说,近邻将按照它们到该向量的距离排序。 //对单个输入向量,所有的输出矩阵是可选的,而且预测值将由该方法返回。
float CvKNearest::find_nearest( const CvMat* _samples, int k, CvMat* _results,
const float** _neighbors, CvMat* _neighbor_responses, CvMat* _dist ) const
{
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" ); 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>)" ); 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" ); 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" ); if( k < 1 || k > max_k )
CV_Error( CV_StsOutOfRange, "k must be within 1..max_k range" ); 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" );
} 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 = MIN( k1, k ); cv::parallel_for_(cv::Range(0, count), P1(this, buf_sz, k, _samples, _neighbors, k1,
_results, _neighbor_responses, _dist, &result)
); return result;
} /////////////////////////////////////////////////////
//2、下面是采用opencv2.0的形式写的
using namespace cv;
//构造函数,参数为:训练数据;训练数据的目标值
//_train_data:行表示样本,列表示维度
CvKNearest::CvKNearest( const Mat& _train_data, const Mat& _responses,
const Mat& _sample_idx, bool _is_regression, int _max_k )
{
samples = 0;
//调用对应cpp版本的训练函数
train(_train_data, _responses, _sample_idx, _is_regression, _max_k, false );
}
//训练模型
bool CvKNearest::train( const Mat& _train_data, const Mat& _responses,
const Mat& _sample_idx, bool _is_regression,
int _max_k, bool _update_base )
{
//将训练数据、训练数据的目标值、提取出来。然后调用opencv1。0版本的训练函数来训练。
CvMat tdata = _train_data, responses = _responses, sidx = _sample_idx; return train(&tdata, &responses, sidx.data.ptr ? &sidx : 0, _is_regression, _max_k, _update_base );
} //寻找最近邻
float CvKNearest::find_nearest( const Mat& _samples, int k, Mat* _results,
const float** _neighbors, Mat* _neighbor_responses,
Mat* _dist ) const
{
CvMat s = _samples, results, *presults = 0, nresponses, *pnresponses = 0, dist, *pdist = 0; if( _results )
{
if(!(_results->data && (_results->type() == CV_32F ||
(_results->type() == CV_32S && regression)) &&
(_results->cols == 1 || _results->rows == 1) &&
_results->cols + _results->rows - 1 == _samples.rows) )
_results->create(_samples.rows, 1, CV_32F);
presults = &(results = *_results);
} if( _neighbor_responses )
{
if(!(_neighbor_responses->data && _neighbor_responses->type() == CV_32F &&
_neighbor_responses->cols == k && _neighbor_responses->rows == _samples.rows) )
_neighbor_responses->create(_samples.rows, k, CV_32F);
pnresponses = &(nresponses = *_neighbor_responses);
} if( _dist )
{
if(!(_dist->data && _dist->type() == CV_32F &&
_dist->cols == k && _dist->rows == _samples.rows) )
_dist->create(_samples.rows, k, CV_32F);
pdist = &(dist = *_dist);
}
//调用另一个寻找近邻的函数完成剩下的工作(即之前opencv1.0版本的那个函数)
return find_nearest(&s, k, presults, _neighbors, pnresponses, pdist );
} //寻找最近邻,该方法是传递引用
float CvKNearest::find_nearest( const cv::Mat& _samples, int k, CV_OUT cv::Mat& results,
CV_OUT cv::Mat& neighborResponses, CV_OUT cv::Mat& dists) const
{
//调用另一个寻找最近邻的函数实现(即上面那个函数)
return find_nearest(_samples, k, &results, 0, &neighborResponses, &dists);
} /* End of file */

例子:

#include "ml.h"
#include "highgui.h"
int main( int argc, char** argv ){
const int K = 10;
int i, j, k, accuracy;
float response;
int train_sample_count = 100;//100个训练样本
CvRNG rng_state = cvRNG(-1);//随机数状态
//建立训练集中的数据和类别标签
CvMat* trainData = cvCreateMat( train_sample_count,
2, CV_32FC1 );
CvMat* trainClasses = cvCreateMat( train_sample_count,
1, CV_32FC1 );
//创建一张画布用来呈现结果
IplImage* img = cvCreateImage( cvSize( 500, 500 ), 8, 3 ); float _sample[2]; CvMat sample = cvMat( 1, 2, CV_32FC1, _sample ); cvZero( img );//对画布进行清零操作 CvMat trainData1, trainData2, trainClasses1, trainClasses2; // form the training samples
//将trainData中上部分提取出来,然后填充随机数
cvGetRows( trainData, &trainData1, 0, train_sample_count/2 );
cvRandArr( &rng_state, &trainData1, CV_RAND_NORMAL, cvScalar(200,200), cvScalar(50,50) ); //将trainData下部分提取出来,填充随机数
cvGetRows(trainData, &trainData2, train_sample_count / 2, train_sample_count);
cvRandArr( &rng_state, &trainData2, CV_RAND_NORMAL, cvScalar(300,300), cvScalar(50,50) ); //对于trainClasses的上半部分置为1,表示前50个都是第1类
cvGetRows( trainClasses, &trainClasses1, 0, train_sample_count/2 );
cvSet( &trainClasses1, cvScalar(1) ); //对于trainClasses的下半部分置为2,表示后50个都是第2类
cvGetRows( trainClasses, &trainClasses2, train_sample_count/2, train_sample_count );
cvSet( &trainClasses2, cvScalar(2) ); //训练knn用作分类
CvKNearest knn( trainData, trainClasses, 0, false, K );
//建立一个K矩阵,用来存储K个最近邻
CvMat* nearests = cvCreateMat( 1, K, CV_32FC1);
//将画布上每个点都进行分类
for( i = 0; i < img->height; i++ ){
for( j = 0; j < img->width; j++ ){
sample.data.fl[0] = (float)j;
sample.data.fl[1] = (float)i;
// estimate the response and get the neighbors’ labels
//该函数返回该点的类别
response = knn.find_nearest(&sample,K,0,0,nearests,0);
// compute the number of neighbors representing the majority
//计算这K个近邻中有多少是支持第response类的。即准确度
for( k = 0, accuracy = 0; k < K; k++ ){
if( nearests->data.fl[k] == response)
accuracy++;
}
// highlight the pixel depending on the accuracy (or confidence)
//按照不同的类别赋值不同的颜色,并根据准确度的多少赋值颜色的混合区域,即
//最后的结果的图中中间层就是有争议的部分。
cvSet2D( img, i, j, response == 1 ?
(accuracy > 5 ? CV_RGB(180,0,0) : CV_RGB(180,120,0)) :
(accuracy > 5 ? CV_RGB(0,180,0) : CV_RGB(120,120,0)) );
}
}
// display the original training samples
//显示原始的100训练样本,用小点标记出来,以区分画布中的其他点
for( i = 0; i < train_sample_count/2; i++ ){
CvPoint pt;
pt.x = cvRound(trainData1.data.fl[i*2]);
pt.y = cvRound(trainData1.data.fl[i*2+1]);
cvCircle( img, pt, 2, CV_RGB(255,0,0), CV_FILLED );
pt.x = cvRound(trainData2.data.fl[i*2]);
pt.y = cvRound(trainData2.data.fl[i*2+1]);
cvCircle( img, pt, 2, CV_RGB(0,255,0), CV_FILLED );
}
//将结果呈现出来
cvNamedWindow( "classifier result", 1 );
cvShowImage( "classifier result", img );
cvWaitKey(0);
cvReleaseMat( &trainClasses );
cvReleaseMat( &trainData );
return 0;
}

上面例子的结果:

对应的使用了opencv2.0的形式写了与上面类似的代码:

#include "opencv2\ml\ml.hpp"
#include "opencv2\highgui\highgui.hpp"
#include<iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv){
const int K = 10;
int accuracy=0;
float response;
int train_sample_count = 100;
RNG rng;
Mat trainData = Mat::zeros(100,2,CV_32FC1);
Mat trainClasses = Mat::zeros(100, 1, CV_32FC1); Mat Image(Size2i(500, 500), CV_8UC3);
Image.setTo(0);
Mat sample(1, 2, CV_32FC1); Mat trainData1 = trainData(Range(0, train_sample_count / 2), Range::all());
Mat trainData2 = trainData(Range(train_sample_count / 2, train_sample_count), Range::all());
rng.fill(trainData1, RNG::UNIFORM, Scalar(50, 50), Scalar(200, 200));
rng.fill(trainData2, RNG::UNIFORM, Scalar(200, 200), Scalar(300, 300)); Mat trainClasses1 = trainClasses(Range(0, train_sample_count / 2),Range::all());
Mat trainClasses2 = trainClasses(Range(train_sample_count / 2, train_sample_count), Range::all());
trainClasses1.setTo(1);
trainClasses2.setTo(2); KNearest Knn(trainData, trainClasses, Mat(), false, K);
Mat nearests(1, K, CV_32FC1); Mat_<Vec3b> _I = Image;
for (size_t i = 0; i < Image.rows; ++i){
for (size_t j = 0; j < Image.cols; ++j) {
sample.at<float>(0,0) = static_cast<float>(i);
sample.at<float>(0,1) = static_cast<float>(j); response = Knn.find_nearest(sample, K); for (int k = 0, accuracy = 0; k < K; k++){
if (nearests.data[k] == response)
accuracy++;
} response == 1 ?
(accuracy > 5 ? (_I(i, j)[0] = 180, _I(i, j)[1] = 0, _I(i, j)[2]=0):
(_I(i, j)[0] = 180, _I(i, j)[1] = 120, _I(i, j)[2] = 0 )) :
(accuracy > 5 ? (_I(i, j)[0] = 0, _I(i, j)[1] = 180, _I(i, j)[2] = 0 ):
(_I(i, j)[0] = 120, _I(i, j)[1] = 120, _I(i, j)[2] = 0));
}
}
Image = _I; for (int i = 0; i < train_sample_count / 2; i++){
Point pt; pt.x = static_cast<int>(trainData1.at<float>(i, 0));
pt.y = static_cast<int>(trainData1.at<float>(i, 1));
circle(Image, pt, 1, Scalar(0, 0, 255),-1,8); pt.x = static_cast<int>(trainData2.at<float>(i, 0));
pt.y = static_cast<int>(trainData2.at<float>(i, 1));
circle(Image, pt, 1, Scalar(255, 0, 0), -1, 8);
} namedWindow("result");
imshow("result", Image);
waitKey(0);
cin.get();
return 0;
}

生成的结果为:

上面的源码部分还未完全注释,待后续接着注释。

2015年09月27日,第0次修改!

opencv7-ml之KNN的更多相关文章

  1. ML(5):KNN算法

    K近邻算法,即K-Nearest Neighbor algorithm,简称KNN算法,可以简单的理解为由那离自己最近的K个点来投票决定待分类数据归为哪一类.这个算法是机器学习里面一个比较经典的算法, ...

  2. OpenCV中的KNN

    一.K近邻 有两个类,红色.蓝色.我将红色点标记为0,蓝色点标记为1.还要创建25个训练数据,把它们分别标记为0或者1.Numpy中随机数产生器可以帮助我们完成这个任务 import cv2 impo ...

  3. [Python]基于K-Nearest Neighbors[K-NN]算法的鸢尾花分类问题解决方案

    看了原理,总觉得需要用具体问题实现一下机器学习算法的模型,才算学习深刻.而写此博文的目的是,网上关于K-NN解决此问题的博文很多,但大都是调用Python高级库实现,尤其不利于初级学习者本人对模型的理 ...

  4. OpenCV-Python 使用OCR手写数据集运行KNN | 五十四

    目标 在本章中 我们将使用我们在kNN上的知识来构建基本的OCR应用程序. 我们将尝试使用OpenCV自带的数字和字母数据集. 手写数字的OCR 我们的目标是构建一个可以读取手写数字的应用程序.为此, ...

  5. OpenCV——识别手写体数字

    这个是树莓派上运行的, opencv3 opencv提供了一张手写数字图片给我们,如下图所示,可以作为识别手写数字的样本库. 0到9共十个数字,每个数字有五行,一行100个数字.首先要把这5000个数 ...

  6. python的机器学习之路

    2018-04-1712:22:40 这是python依靠计算机视觉进行的ocr手写字的识别. 通过KNN训练数据 kNN 可以说是最简单的监督学习分类器了.想法也很简单,就是找出测试数据在特征空间中 ...

  7. only size-1 arrays can be converted to Python scalars

    python版本:3.6.5 opencv版本:3.2.0 使用的jupyter notebook 源码如下: import cv2 import numpy as np import matplot ...

  8. 02机器学习实战之K近邻算法

    第2章 k-近邻算法 KNN 概述 k-近邻(kNN, k-NearestNeighbor)算法是一种基本分类与回归方法,我们这里只讨论分类问题中的 k-近邻算法. 一句话总结:近朱者赤近墨者黑! k ...

  9. OpenCV-Python 理解K近邻 | 五十三

    目标 在本章中,我们将了解k近邻(kNN)算法的原理. 理论 kNN是可用于监督学习的最简单的分类算法之一.这个想法是在特征空间中搜索测试数据的最近邻.我们将用下面的图片来研究它. 在图像中,有两个族 ...

  10. ML一:python的KNN算法

    (1):list的排序算法: 参考链接:http://blog.csdn.net/horin153/article/details/7076321 示例: DisListSorted = sorted ...

随机推荐

  1. js-ES6学习笔记-Proxy(2)

    1.has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效.典型的操作就是in运算符. var handler = { has (target, key) { if ...

  2. easyui+webuploader+ckeditor实现插件式多图片上传

    需求:在ckeditor编辑器上实现多图片上传并要求另外单独选择ckeditor上传的图片作为封面 页面效果说明: 动态效果图: 第一步:页面布局 <html xmlns="http: ...

  3. Oracle EBS OM 删除订单行

    DECLARE l_header_rec OE_ORDER_PUB.Header_Rec_Type; l_line_tbl OE_ORDER_PUB.Line_Tbl_Type; l_action_r ...

  4. SQL Server 2016 中有外键的表无法被Truncate,只能被Delete

    问: I get the following message even when the table that references it is empty: "Cannot truncat ...

  5. pycharm的放大和缩小字体的显示 和ubunt的截圖工具使用 ubuntu上安装qq微信等工具

    https://www.cnblogs.com/sui776265233/p/9322074.html#_label0 ubuntu: 截圖工具的使用 在ubuntu 10.04 的时候,还可以很方便 ...

  6. gitlab配置push -f 关闭

    默认路径是/var/opt/gitlab/git-data/repositories/组/库 修改conf 文件 [core] repositoryformatversion = 0 filemode ...

  7. VMware 导出镜像文件供 Virtual Box 使用

    1. 问题描述 Windows 系统安装的 VMware 里的安装配置好的虚拟机需要拷贝到 MAC 的 Virtual Box 中. 需要将 VMware 中的虚拟机导出为镜像文件供 Virtual ...

  8. fedora安装字体

    #fedora安装新字体 将自己现有的字体复制到/usr/share/fonts/自己起个名字/ 例如我要安装下载的苹果苹方字体 #cp 我这个文件夹的地址/* /usr/share/fonts/Pi ...

  9. 一、HttpServletRequest接口 二、HttpServletReponse接口 三、POST和GET请求方式及其乱码处理 四、ServletContext对象和ServletConfig对象

    一.HttpServletRequest接口 内部封装了客户端请求的数据信息 接收客户端的请求参数.HTTP请求数据包中配置参数 ###<1>常用方法 getContextPath()重要 ...

  10. python中判断实例可迭代地几种方式

    1. 利用 __iter__内建属性 if hasattr(obj, '__iter__') : print 'iterable' 这种方法不能检测字符串,如:hasattr('', '__iter_ ...