本文参考两篇博客,都对KNN思路做了详细说明
(1) http://blog.csdn.net/wangyaninglm/article/details/17091901
(2) http://blog.csdn.net/xiaowei_cqu/article/details/23782561#
(3) http://download.csdn.net/download/hust_bochu_xuchao/9581796
个人对于K近邻编码实现的一点理解。主要基于第一篇,第三篇是将第一篇基于OpenCV2实现,可参考。
int trainsamples; 每类样本数
int clesses; 样本种类数
如数字识别,0-9计10个数字,则 clesses 为10,如果每个样本库中有100个样本,如100个数字 0 样本,则 trainsamples 为100.
Mat traindata; 存储样本数据
mat trainclass; 样本标识
traindata = Mat(trainsamples*traincless, templet_w*templet_h);
trainclass = Mat(trainsamples*traincless, 1);
将所有样本数据存储在 traindata 中,每个样本转换为 1*(w*h) 型矩阵,且二值化处理成只有 0 和 1 两种像素值模式
trainclass 保存每类样本标识,在 traindata 赋值过程中 赋值。如例 trainclass 为 1000*1 矩阵,那么其前100行为0,最后100行为9
训练直接调用 train() 函数即可
train(traindata, trainclass, Mat(), false, K);
Mat testimg; 待测数据
float result = CvKNearest::find_nearest(testimg, K)
上述为规模样本采用K近邻训练识别的理解。
网上有一篇基于K近邻的手写字符识别,样本是方形,实际中数字多为矩形。
第二篇博客也同理
for (int i = 0; i < image.rows; ++i)
{
for (int j = 0; j < image.cols; ++j)
{
const Mat sampleMat = (Mat_<float>(1,2) << i,j);
Mat response;
float result = knn.find_nearest(sampleMat,1);
if (result !=0)
{
image.at<Vec3b>(j, i) = green;
}
else
image.at<Vec3b>(j, i) = blue;
}
}
创建 512*512 大小的矩阵,生成10个样本,实际为10对坐标。
坐标在0-256之间的,标签为0,在256-512之间的,标签为1.
将坐标分类,非 0 类绿色,0 类蓝色。
代码补充:
knnTest.h
#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;
class knnRec
{
public:
knnRec();
float digRec(Mat img); //数字识别
void samTest(); //样本训练
private:
char folder[255]; //模板路径
int sampl_num; //每类样本数
int class_num; //样本种类数
Mat trainData; //样本数据存储矩阵
Mat trainClass; //样本类别存储矩阵
static const int K = 6; //最大邻居个数
KNearest * knn;
void getData(); //获取样本数据
void knnTrain(); //训练
};
knnTest.cpp
#include "knnTest.h"
knnRec::knnRec()//构造函数
{
sprintf(folder, "..\\pics\\Template\\");
sampl_num = 10; //训练样本,总共10个
class_num = 10; //暂时识别十个数字
int samp_w = 14; //模板的宽和高
int samp_h = 27;
trainData.create(sampl_num*class_num, samp_w*samp_h, CV_32FC1); //训练数据的矩阵
trainClass.create(sampl_num*class_num, 1, CV_32FC1);
getData();
knnTrain();
}
void knnRec::getData()
{
char filepath[255];
for (int i=0; i<class_num; i++)
{
for (int j=0; j<sampl_num; j++)
{
sprintf(filepath, "%s%d\\%d%d.bmp", folder, i, i, j);
Mat sampl = imread(filepath, 0);
if (sampl.empty())
{
printf("Error: Cant load image %s\n", filepath);
return;
}
//将 MxN 矩阵 转为 1x(MxN)
Mat templ = sampl.clone();
sampl.release();
sampl.create(1, templ.cols*templ.rows, CV_32FC1);
//sampl 当前为1行,未初始化,templ 存储 sampl 源数据
float* data_sampl = sampl.ptr<float>(0);
//确保模板均为二值图,否则进行二值化
for (int j = 0; j<templ.rows; j++)
{
uchar * data_templ = templ.ptr<uchar>(j);
for (int i = 0; i<templ.cols; i++)
{
if (data_templ[i] == 255)
{
data_sampl[j*templ.rows + i] = 1;
}
else
{
data_sampl[j*templ.rows + i] = 0;
}
}
}
//记录模板数据
float * data1 = trainData.ptr<float>(i*sampl_num+j); //定位第 i 个类中第 j 个样本
float * data2 = sampl.ptr<float>(0);
for (int k = 0; k < sampl.cols; k++)
{
data1[k] = data2[k];
}
//记录模板标志
trainClass.at<float>(i*sampl_num+j, 0) = i; //定位第 i 个类中第 j 个样本,标为 i
}
}
}
void knnRec::knnTrain()
{
knn = new KNearest(trainData, trainClass, Mat(), false, K);
}
//数字识别,img 为数字区域
float knnRec::digRec(Mat src)
{
Mat knnImg;
knnImg.create(1, K, CV_32FC1);
//处理输入的图像
Mat tmp = src.clone();
src.release();
src.create(1, tmp.cols*tmp.rows, CV_32FC1);
float* data_src = src.ptr<float>(0);
for (int j = 0; j<tmp.rows; j++)
{
uchar* data_tmp = tmp.ptr<uchar>(j);
for (int i = 0; i<tmp.cols; i++)
{
if (data_tmp[i] == 255)
{
data_src[j*tmp.rows + i] = 1;
}
else
{
data_src[j*tmp.rows + i] = 0;
}
}
}
float result = knn->find_nearest(src, K, Mat(), knnImg, Mat());
int checkNum = 0;
for (int i = 0; i<K; i++)
{
if (knnImg.at<float>(0, i) == result)
{
checkNum++;
}
}
float pre = 100 * ((float)checkNum / (float)K);
return result;
}
说明:
调用之前,数字区域需完成处理,即完成裁剪、二值化等。
knnRec rec;
double result = rec.digRec(img);