opencv 學習之 基於K近鄰的數字識別

秦城書生發表於2016-12-17

本文參考兩篇部落格,都對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);





相關文章