65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MLP 神经网络识别数字

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (6投票s)

2017年5月23日

CPOL

7分钟阅读

viewsIcon

17023

downloadIcon

483

使用多层感知机神经网络(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”中保存的所有特征向量,我们无需在每次运行时重新提取,这有助于节省时间。

其中包含两种不同类型的数据:

  1. num:包含10种不同字体数字(0到9)的特征向量,总共有100个数字。
  2. Num_half:包含“num”特征向量的一半。

目标

我们的目标在“binary”类型和“one by one”类型中包含四种不同类型:

  1. Target100:10种不同字体按“one by one”划分
  2. target100:10种不同字体按4位binary类型划分
  3. Target50:5种不同字体按“one by one”划分
  4. 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日:初始版本
© . All rights reserved.