Opencv人工神经网络实现字母与数字识别流程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011808673/article/details/78510830

目录


人工神经网络简介

人工神经网络(Artificial Neural Network,ANN)简称神经网络(NN),是基于生物学中神经网络的基本原理,在理解和抽象了人脑结构和外界刺激响应机制后,以网络拓扑知识为理论基础,模拟人脑的神经系统对复杂信息的处理机制的一种数学模型。神经网络是一种运算模型,由大量的节点(或称神经元)之间相互联接构成。每个节点代表一种特定的输出函数,称为激活函数(activation function)。每两个节点间的连接都代表一个对于通过该连接信号的加权值,称之为权重(weight),神经网络就是通过这种方式来模拟人类的记忆。网络的输出则取决于网络的结构、网络的连接方式、权重和激活函数。

人工神经元/神经网络模型

神经元1.png神经元2.png


左边几个灰底圆中所标字母w代表浮点数,称为权重(weight,或权值,权数)。进入人工神经细胞的每一个input(输入)都与一个权重w相联系,正是这些权重将决定神经网络的整体活跃性。你现在暂时可以设想所有这些权重都被设置到了-1和1之间的一个随机小数。因为权重可正可负,故能对与它关联的输入施加不同的影响,如果权重为正,就会有激发(excitory)作用,权重为负,则会有抑制(inhibitory)作用。当输入信号进入神经细胞时,它们的值将与它们对应的权重相乘,作为图中大圆的输入。大圆的‘核’是一个函数,叫激励函数(activation function),它把所有这些新的、经过权重调整后的输入全部加起来,形成单个的激励值(activation value)。激励值也是一浮点数,且同样可正可负。然后,再根据激励值来产生函数的输出也即神经细胞的输出:如果激励值超过某个阀值(作为例子我们假设阀值为1.0),就会产生一个值为1的信号输出;如果激励值小于阀值1.0,则输出一个0。这是人工神经细胞激励函数的一种最简单的类型。在这里,从激励值产生输出值是一个阶跃函数.

字符特征提取

在深度学习(将特征提取作为训练的一部分)这个概念引入之前,一般在准备分类器进行识别之前都需要进行特征提取。因为一幅图像包含的内容太多,有些信息能区分差异性,而有些信息却代表了共性。所以我们要进行适当的特征提取把它们之间的差异性特征提取出来。

这里面我们计算二种简单的字符特征:梯度分布特征、灰度统计特征。这两个特征只是配合本篇文章来说明神经网络的普遍用法,实际中进行字符识别需要考虑的字符特征远远要比这复杂,还包括相似字特征的选取等,也由于工作上的原因,这一部分并不深入的介绍。

1,首先是梯度分布特征,该特征计算图像水平方向和竖直方向的梯度图像,然后通过给梯度图像分划不同的区域,进行梯度图像每个区域亮度值的统计,以下是算法步骤:

<1>将字符由RGB转化为灰度,然后将图像归一化到16*8。

<2>定义soble水平检测算子: 和竖直方向梯度检测算子 。

<3>对图像分别用 和 进行图像滤波得到 和 ,下图分别代表原图像、 和 。

<4>对滤波后的图像,计算图像总的像素和,然后划分4*2的网络,计算每个网格内的像素值的总和。

<5>将每个网络内总灰度值占整个图像的百分比统计在一起写入一个向量,将两个方向各自得到的向量并在一起,组成特征向量。

2,第二个特征非常简单,只需要将图像归一化到特定的大小,然后将图像每个点的灰度值作为特征即可。

<1>将图像由RGB图像转换为灰度图像;

<2>将图像归一化大小为 ,并将图像展开为一行,组成特征向量。

Sample Code (以下代码使用的是 Opencv 3.0环境)

float sumMatValue(const Mat & image){
	float sumValue = 0;
	int r = image.rows;
	int c = image.cols;
	if(image.isContinuous()){
	    c = r*c;
            r = 1;	
	}
	for(int i = 0; i < r; i++){
		const uchar *linePtr = image.ptr<uchar>(i);
		for (int j = 0; j < c; j++){
			sumValue += linePtr[j];
		}	
	}
	return sumValue;
}
void calcGradientFeat(Mat & imgSrc, vector<float> & feat){
      	Mat image;
      	cvtColor(imgSrc, image, CV_BGR2GRAY);
      	resize(image, image, Size(8,16));
      	float mask[3][3] = {{1,2,1},{0,0,0},{-1,-2,-1}};      
      	Mat y_mask = Mat(3,3, CV_32F, mask) / 8;
	Mat x_mask = y_mask.t(); // 转置
	Mat sobelX, sobelY;

	filter2D(image, sobelX, CV_32F, x_mask); 
	filter2D(image, sobelY, CV_32F, y_mask);
	sobelX = abs(sobelX);
	sobelY = abs(sobelY);

	float totleValueX = sumMatValue(sobelX);
	float totleValueY = sumMatValue(sobelY);
	for(int i = 0; i < image.rows; i = i +4)
	{
		for( int j = 0; j < image.cols; j = j + 4)
		{
			Mat subImageX = sobelX(Rect(j, i, 4, 4));
			feat.push_back(sumMatValue(subImageX) / totleValueX);
		 	Mat subImageY= sobelY(Rect(j, i, 4, 4)); 
	             	feat.push_back(sumMatValue(subImageY) / totleValueY); 
		}	   	
 	}	
	
	Mat img2;
	resize(image, img2, Size(4,8));
	int r = img2.rows;
	int c = img2.cols;
	if(img2.isContinuous()){
	    c = r*c;
            r = 1;	
	}
	for(int i = 0; i < r; i++){
		const uchar *linePtr = img2.ptr<uchar>(i);
		for (int j = 0; j < c; j++){
			feat.push_back(linePtr[j]);
		}	
	}	
}

Opencv的神经网络

CvANN_MLP是OpenCV中提供的一个神经网络的类,正如它的名字一样(multi-layer perceptrons),它是一个多层感知网络,它有一个输入层,一个输出层以及1或多个隐藏层。

创建一个网络

Ptr<StatModel> buildMLPClassifier(Mat & input , Mat & output){
	Ptr<ANN_MLP> model;
	//train classifier;
	int layer_sz[] = {input.cols, 100 , output.cols};
	int nlayers = (int)(sizeof(layer_sz)/ sizeof(layer_sz[0]));
	Mat layer_sizes(1,nlayers,CV_32S, layer_sz);
	int method;
	double method_param;
	int max_iter;
	if(1){
		method = ANN_MLP::BACKPROP;
		method_param = 0.0001;
		max_iter = 1000;
	}else{
		method = ANN_MLP::RPROP;
 	 	method_param = 0.1;
 		max_iter = 1000;
	}
	Ptr<TrainData> tData = TrainData::create(input,ROW_SAMPLE,output);
	model = ANN_MLP::create();
	model->setLayerSizes(layer_sizes);
	model->setActivationFunction(ANN_MLP::SIGMOID_SYM, 0, 0);
	model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, max_iter, FLT_EPSILON));
	//setIterCondition(max_iter, 0);
 	model->setTrainMethod(method, method_param);
 	model->train(tData);
	model->save("mlp1.xml");
 	return model;
}
}
layerSizes:一个整型的数组,这里面用Mat存储。它是一个1*N的Mat,N代表神经网络的层数,第 列的值表示第 层的结点数。这里需要注意的是,在创建这个Mat时,一定要是整型的,uchar和float型都会报错。

比如我们要创建一个3层的神经网络,其中第一层结点数为 ,第二层结点数为 ,第三层结点数为 ,则layerSizes可以采用如下定义:

1 Mat layerSizes=(Mat_<int>(1,3)<<x1,x2,x3);

或者用一个数组来初始化:

1 int ar[]={x1,x2,x3}; 2 Mat layerSizes(1,3,CV_32S,ar);

activateFunc:这个参数用于指定激活函数,不熟悉的可以去看我博客里的这篇文章《神经网络:感知器与梯度下降》,一般情况下我们用SIGMOID函数就可以了,当然你也可以选择正切函数或高斯函数作为激活函数。OpenCV里提供了三种激活函数,线性函数(CvANN_MLP::IDENTITY)、sigmoid函数(CvANN_MLP::SIGMOID_SYM)和高斯激活函数(CvANN_MLP::GAUSSIAN)。

后面两个参数则是SIGMOID激活函数中的两个参数 和 ,默认情况下会都被设置为1。

网络参数设置

神经网络训练参数的类型存放在CvANN_MLP_TrainParams这个类里,它提供了一个默认的构造函数,我们可以直接调用,也可以一项一项去设。

 1 CvANN_MLP_TrainParams::CvANN_MLP_TrainParams() 2 { 3     term_crit = cvTermCriteria( CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 1000, 0.01 ); 4     train_method = RPROP; 5     bp_dw_scale = bp_moment_scale = 0.1; 6     rp_dw0 = 0.1; rp_dw_plus = 1.2; rp_dw_minus = 0.5; 7     rp_dw_min = FLT_EPSILON; rp_dw_max = 50.; 8 }

它的参数大概包括以下几项。

term_crit:终止条件,它包括了两项,迭代次数(CV_TERMCRIT_ITER)和误差最小值(CV_TERMCRIT_EPS),一旦有一个达到条件就终止训练。

train_method:训练方法,OpenCV里提供了两个方法一个是很经典的反向传播算法BACKPROP,另一个是弹性反馈算法RPROP,对第二种训练方法,没有仔细去研究过,这里我们运用第一种方法。

剩下就是关于每种训练方法的相关参数,针对于反向传播法,主要是两个参数,一个是权值更新率bp_dw_scale和权值更新冲量bp_moment_scale。这两个量一般情况设置为0.1就行了;太小了网络收敛速度会很慢,太大了可能会让网络越过最小值点。

我们一般先运用它的默认构造函数,然后根据需要再修改相应的参数就可以了。如下面代码所示,我们将迭代次数改为了5000次。

1 CvANN_MLP_TRainParams param; 2 param.term_crit=cvTermCriteria(CV_TerMCrIT_ITER+CV_TERMCRIT_EPS,5000,0.01);

inputs:输入矩阵。它存储了所有训练样本的特征。假设所有样本总数为nSamples,而我们提取的特征维数为ndims,则inputs是一个 的矩阵,我们可以这样创建它。

1 Mat inputs(nSamples,ndims,CV_32FC1); //CV_32FC1说明它储存的数据是float型的。

我们需要将我们的训练集,经过特征提取把得到的特征向量存储在inputs中,每个样本的特征占一行。

outputs:输出矩阵。我们实际在训练中,我们知道每个样本所属的种类,假设一共有nClass类。那么我们将outputs设置为一个nSample行nClass列的矩阵,每一行表示一个样本的预期输出结果,该样本所属的那类对应的列设置为1,其他都为0。比如我们需要识别0-9这10个数字,则总的类数为10类,那么样本数字“3”的预期输出为[0,0,1,0,0,0,0,0,0,0];

sampleWeights:一个在使用RPROP方法训练时才需要的数据,所以这里我们不设置,直接设置为Mat()即可。

sampleIdx:相当于一个遮罩,它指定哪些行的数据参与训练。如果设置为Mat(),则所有行都参与。

params:这个在刚才已经说过了,是训练相关的参数。

flag:它提供了3个可选项参数,用来指定数据处理的方式,我们可以用逻辑符号去组合它们。UPDATE_WEIGHTS指定用一定的算法去初始化权值矩阵而不是用随机的方法。NO_INPUT_SCALE和NO_OUTPUT_SCALE分别用于禁止输入与输出矩阵的归一化。

一切都准备好后,直接开始训练吧!

识别

Ptr<StatModel> model = buildMLPClassifier(input, output);
	//Ptr<StatModel> model = loadMLPClassifiler();
	float response = model->predict(test, test1);
	cout<<"response = "<<response<<endl;
	for(int i = 0; i < test1.size(); i++)
	{
		cout<<"test1 = "<<test1[i]<<endl;	
	}
识别返回的response 就是预测的值,test1 里面存放的是每个字母的可能概率

完整代码:

  int main()
  {
      Mat image;
      vector<float>feats;
      vector<float>test,test1;
      string path = "code/python_image_learn/identfying_code_recognize/charSamples/";
      int num = 0;
      int classfilternum = 34;
      int modlenum = 30;
      for(int i = 0 ; i < classfilternum ; i++){
	for(int j = 0; j < modlenum; j++){
		ostringstream oss;
		oss<<path<<i<<"/"<<j<<".png";
		//cout<<oss.str()<<endl;
		image=imread(oss.str());
		calcGradientFeat(image, feats);
		num++;

		if(i == 10 && j == 10){
			ostringstream oss; 
		oss<<path<<i<<"/"<<(j+1)<<".png";
		//cout<<oss.str()<<endl;
		image=imread(oss.str());
		calcGradientFeat(image, test);			
		}	
	}
      }	
      	Mat input, output;	
	input = Mat(classfilternum*modlenum, 48, CV_32F);
	output = Mat(classfilternum*modlenum, classfilternum, CV_32F, Scalar(0));
	int r = input.rows;
	int c = input.cols;
	if(input.isContinuous()){
	    c = r*c;
            r = 1;
	}
	for(int i = 0; i < r; i++){
		float *linePtr = input.ptr<float>(i);
		for (int j = 0; j < c; j++){
			linePtr[j] = feats[c*i + j];
		}	
	}
	for(int i = 0; i < output.rows; i++){
		float *lineoutput = output.ptr<float>(i);
		lineoutput[i/modlenum] = 1;
	}	
	
	//if(
	Ptr<StatModel> model = buildMLPClassifier(input, output);
	//Ptr<StatModel> model = loadMLPClassifiler();
	float response = model->predict(test, test1);
	cout<<"response = "<<response<<endl;
	for(int i = 0; i < test1.size(); i++)
	{
		cout<<"test1 = "<<test1[i]<<endl;	
	}
	//cout<<input<<endl;
	//cout<<"rows = "<<input.rows<<"col = "<<input.cols<<endl;
	//cout<<output<<endl;
//	waitKey();                    //等待按键

     	return 0;
  }

字符样本的下载

链接:http://pan.baidu.com/s/1pLPeZkZ 密码:26eb


展开阅读全文

没有更多推荐了,返回首页