使用 MLP 神经网络识别数字






4.93/5 (6投票s)
使用多层感知机神经网络(MLP)识别英文数字。
引言
本项目旨在通过多层感知器(MLP)实现数字识别,并提供了一些从图片中提取特征的新方法。
本项目获取一些数据(此处为数字),并使用神经网络 MLP(多层感知器)进行学习。学习完成后,我们测试其他数字,我们的网络会判断该数字是什么。
背景
- 神经网络
- 人工智能
- Matlab
Using the Code
本项目中,我们有10种不同字体的数字用于学习,并从数字图片中提取26个特征向量。
特征向量包括:
- 图片四侧的颜色密度
- 图片四侧黑白比
- 确定水平线和垂直线
- 确定图片中的“孔洞”
- 使用链码确定书写数字时向上、向下、向左和向右的次数
- 将数字转换为9段码数字
- 确定每行和每列黑色像素总角度的正切值
这些是我们要让神经网络学习的特征向量。
本项目包括Main
脚本和“pref
”、“Extract Number
”、“Extract Features
”以及“chain code
”函数,它们各自执行一个特殊任务,我们将在后面解释。我们还使用从“input data
”函数中提取的“datas
”数据和“target
”目标。
Input datas()
此函数接受数字1作为参数来运行。因此,此函数提供图片地址并将其发送到“Extract Features
”并保存特征。最后,将所有特征收集到一个矩阵中并将其设置为输出。
Datas
此数据包含我们从“input data”中保存的所有特征向量,我们无需在每次运行时重新提取,这有助于节省时间。
其中包含两种不同类型的数据:
num
:包含10种不同字体数字(0到9)的特征向量,总共有100个数字。Num_half
:包含“num
”特征向量的一半。
目标
我们的目标在“binary
”类型和“one by one
”类型中包含四种不同类型:
Target100
:10种不同字体按“one by one
”划分target100
:10种不同字体按4位binary
类型划分Target50
:5种不同字体按“one by one
”划分target50
:5种不同字体按4位binary
类型划分
提示:注意目标名称中的大小写字母。
如果我们要让神经网络学习10种字体,我们必须使用“Target100
”或“target100
”作为目标参数;如果我们要让神经网络学习5种(一半)字体,我们必须使用“Target50
”或“target50
”作为目标参数。
Chain Code
这个函数获取二值图片,并以“8方向”或“4方向”两种方式模拟手写。换句话说,如果我们使用4方向,链码将输出1到4之间的数字,这意味着:
- 1是向上移动
- 2是向右移动
- 3是向下移动
- 4是向左移动
通过此函数,我们模拟手写并计算在纸上绘制数字时向上、向下、向左和向右的次数。
Extract Features
此函数在输入参数中获取地址,并在输出参数中导出我们所讨论的所有图片特征向量。下面,我们将解释每个代码。
首先,在第2行到第9行,我们将图片转换为灰度图和二值图,然后对其进行重塑和压缩。
I = imread(address);
%%chenge to gray
Igray = rgb2gray(I);
%%chenge to bw
Ibw = im2bw(Igray,graythresh(Igray));
T=reshape(Ibw,10000,1);
Ibw_comp=resizem(Ibw,0.25);
然后,在第13行到第56行,我们将图片分成4部分,然后找到每个部分的颜色密度和黑白比。(前8个特征已创建)
k=0;
j=0;
for i=1:2500
if(T(i,1)== 1)
k=k+1;%white
else
j=j+1;%black
end
end
ch1=j/2500;
w_b1=j/k;
j=0;
k=0;
for i=2500:5000
if(T(i,1)== 1)
k=k+1;%white
else
j=j+1;%black
end
end
ch2=j/2500;
w_b2=j/k;
j=0;
k=0;
for i=5000:7500
if(T(i,1)== 1)
k=k+1;%white
else
j=j+1;%black
end
end
ch3=j/2500;
w_b3=j/k;
j=0;
k=0;
for i=7500:10000
if(T(i,1)== 1)
k=k+1;%white
else
j=j+1;%black
end
end
ch4=j/2500;
w_b4=j/k;
在第59行到第92行,我们计算每列和每行的黑色像素数量,然后找到角度的反正切值。之后,我们将所有角度相加,最后,我们对角度取正切值以找到线斜率。(另外2个特征向量已创建)。
k=0;
j=0;
for i=1:25
for j=1:25
if (Ibw_comp(i,j)==0)
k=k+1;
end
end
teta(i,1)=k;
k=0;
end
k=0;
for i=1:25
for j=1:25
if (Ibw_comp(j,i)==0)
k=k+1;
end
end
teta(i,2)=k;
k=0;
end
for i=1:25
teta(i,3)=(teta(i,2)/teta(i,1));
teta(i,4)=atan(teta(i,3))
end
for i=1:25
for j=1:4
if (isnan(teta(i,j))==1)
teta(i,j)=0;
end
end
end
teta(26,4)=sum(teta(:,4));
teta(27,4)=tan(teta(26,4))
从第96行到第114行,我们计算每列的黑色像素数量,如果该数量超过我们的阈值,我们就会认为此图片中存在垂直线,并通过将数字“1
”设置为特征向量来表示它发生了。
提示:“0
”表示图片中没有垂直线,“1
”表示图片中有垂直线。
n=0;
j=1;
for i=1:100
for j=1:100
if(Ibw(j,i)==0)
n=n+1;
t(i)=n;
end
end
n=0;
end
n=max(t);
if(n>79)
ver=1;
else
ver=0;
end
从第118行到第137行,我们再次计算每行的黑色像素数量,如果该数量超过我们的阈值,我们就会认为此图片中存在水平线,并通过将数字“1
”设置为特征向量来表示它发生了。
提示:与上一条提示相同,“0
”表示图片中没有水平线,“1
”表示图片中有水平线。
例如,数字1有一条垂直线,数字4有一条水平线。
(另外2个特征向量已创建。)
t=0;
n=0;
j=1;
for i=1:100
for j=1:100
if(Ibw(i,j)==0)
n=n+1;
t(i)=n;
end
end
n=0;
end
n=max(t);
if(n>40)
har=1;
else
har=0;
end
从第140行到第167行,我们通过链码模拟手写,并将每种方式的总移动次数作为特征向量导出。(另外两个特征向量已创建)。
B = bwboundaries(Ibw,4); % find the boundaries of all objects
CC = cell(1, length(B)); % pre-allocate
for k = 1:length(B)
CC{k} = chaincode(B{k},1); % chain code for the k'th object
end
up=0;
down=0;
left=0;
right=0;
for i=1:length(B)
t=max(size(CC{1,i}.code));
for j=1:t
if(CC{1,i}.code(j,1)==0)
right=right+1;
else if(CC{1,i}.code(j,1)==2)
up=up+1;
else if(CC{1,i}.code(j,1)==4)
left=left+1;
else if(CC{1,i}.code(j,1)==6)
down=down+1;
end
end
end
end
end
end
从第171行到第292行,我们将数字更改为我们的9段码数字。
我们将图片分成9个部分,然后在每个部分中,如果黑色像素的密度大于阈值(此处为150),则该部分将变为黑色并设置为数字1。
对这9个部分都这样做,然后我们将数字更改为看起来像7段码数字,此处又创建了9个特征向量。
alfa=150;
k=0;
for i=1:33
for j=1:33
if(Ibw(i,j)==0)
k=k+1;
end
end
end
if(k>alfa)
seg1=1;
else
seg1=0;
end
k=0;
%%------------------------
for i=1:33
for j=33:66
if(Ibw(i,j)==0)
k=k+1;
end
end
end
if(k>alfa)
seg2=1;
else
seg2=0;
end
k=0;
for i=1:33
for j=66:99
if(Ibw(i,j)==0)
k=k+1;
end
end
end
if(k>alfa)
seg3=1;
else
seg3=0;
end
k=0;
for i=33:66
for j=1:33
if(Ibw(i,j)==0)
k=k+1;
end
end
end
if(k>alfa)
seg4=1;
else
seg4=0;
end
k=0;
for i=33:66
for j=33:66
if(Ibw(i,j)==0)
k=k+1;
end
end
end
if(k>alfa)
seg5=1;
else
seg5=0;
end
k=0;
for i=33:66
for j=66:99
if(Ibw(i,j)==0)
k=k+1;
end
end
end
if(k>alfa)
seg6=1;
else
seg6=0;
end
k=0;
for i=66:99
for j=1:33
if(Ibw(i,j)==0)
k=k+1;
end
end
end
if(k>alfa)
seg7=1;
else
seg7=0;
end
k=0;
for i=66:99
for j=33:66
if(Ibw(i,j)==0)
k=k+1;
end
end
end
if(k>alfa)
seg8=1;
else
seg8=0;
end
k=0;
for i=66:99
for j=66:99
if(Ibw(i,j)==0)
k=k+1;
end
end
end
if(k>alfa)
seg9=1;
else
seg9=0;
end
seg10=[seg1 seg2 seg3
seg4 seg5 seg6
seg7 seg8 seg9];
在第295行,我们判断图片是否有孔洞,并通过将数字1和0设置为特征向量来表示。(另外1个特征向量已创建)。
提示:1表示图片中有孔洞,0表示图片中没有孔洞。(例如,数字8有孔洞,但数字1没有。)
hole=bweuler(Ibw);
最后,在第298行,我们将所有特征向量收集到一个矩阵中并将其导出到输出参数。
chall=[ch1 ch2 ch3 ch4 w_b1 w_b2 w_b3 w_b4 ver har hole up down left right
seg1 seg2 seg3 seg4 seg5 seg6 seg7 seg8 seg9 teta(26,4) teta(27,4)];
Main
这是我们代码的主要部分,我们在此处运行项目。
在第6行和第8行,我们获取输入和目标。
% load data
load('datas.mat');
% load targets
load ('targets.mat');
input=num;
target=Target100;
正如我所提到的,我们有4种目标类型,我们通过“style
”参数选择它。
提示:如果style=0
,我们的目标是“one by one”类型;如果style=1
,我们的目标将是“binary”类型。
type=1;
接下来,我们使用名称“newff
”创建我们的神经网络,并使用此网络训练我们的datas
和目标。
newff=feedforwardnet([10 10],'trainlm');
newff=train(newff,input,target);
在第29行,我们测试数字1
。如果我们的网络说数字是1
,那么它工作得很好。
r= newff(num1); %<==Number for test
所以我们测试它,但网络输出的数字介于0和1之间,我们应该通过阈值0.5对其进行归一化,直到找到我们的答案。
第31行到第39行展示了如果我们的类型是“one by one
”类型(最大相似度是答案),我们如何归一化输出的数字。
if(type==1)
max=max (r(:,1));
for i=1:10
if(max==(r(i,1)))
disp('your number is:')
disp(i)
end
end
end
%%检查答案
% i=10 =>数字是0 % i=5 =>数字是5
% i=1 =>数字是1 % i=6 =>数字是6
% i=2 =>数字是2 % i=7 =>数字是7
% i=3 =>数字是3 % i=8 =>数字是8
% i=4 =>数字是4 % i=9 =>数字是9
第45行到第56行展示了如果我们的类型是“binary
”类型,我们如何归一化输出的数字(如果数字大于0.5(阈值),则将其更改为1,否则将其更改为数字0)。之后,我们将数字发送到“findNumber
”函数,它会说明数字是什么。
if(type==0)
for i=1:4
if(r(i,1)>0.5)
r(i,1)=1;
else
r(i,1)=0;
end
end
FindNumber(r)
end
在第60行到第111行,我们展示了单一字体(0到9的10个数字)的性能。
disp('performance by test num from 0 to 9 (10 number)')
per=0;
r= newff(num0);
r=ExtractNumber(1,r);
if(r==10)
per=per+1;
end
r= newff(num1);
r=ExtractNumber(1,r);
if(r==1)
per=per+1;
end
r= newff(num2);
r=ExtractNumber(1,r);
if(r==2)
per=per+1;
end
r= newff(num3);
r=ExtractNumber(1,r);
if(r==3)
per=per+1;
end
r= newff(num4);
r=ExtractNumber(1,r);
if(r==4)
per=per+1;
end
r= newff(num5);
r=ExtractNumber(1,r);
if(r==5)
per=per+1;
end
r= newff(num6);
r=ExtractNumber(1,r);
if(r==6)
per=per+1;
end
r= newff(num7);
r=ExtractNumber(1,r);
if(r==7)
per=per+1;
end
r= newff(num8);
r=ExtractNumber(1,r);
if(r==8)
per=per+1;
end
r= newff(num9);
r=ExtractNumber(1,r);
if(r==9)
per=per+1;
end
为了找到我们代码的性能,我们应该测试所有的datas
,所以我们创建一个函数“ExtractNumber
”用于所有datas
,我们创建一个函数“pref
”来查找性能。
Pref
测试所有输入datas
并计算正确和错误的答案,并告知我们代码的性能。
您可以在本项目中测试您自己的数字。只需在主脚本的第29行写入您的数字图片的地址,然后您就可以看到网络的答案。
在我们的测试中,我们获得了超过70%的性能,如果我们将用于学习的字体增加到200种或更多,我们将获得更好的性能。
附件源代码中包含一份文档。
历史
- 2017年5月23日:初始版本