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

Unicode 光学字符识别

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (137投票s)

2006年8月23日

28分钟阅读

viewsIcon

732474

downloadIcon

23543

该项目是一个使用人工神经网络的光学字符识别应用程序。

一、引言

  1. 人工神经网络
  2. MLP 神经网络模型
  3. 光学语言符号

二、技术概述

  1. 引言
    1. 网络拓扑与形成
    2. 符号图像检测
    3. 符号图像矩阵映射
  2. 培训
    1. 算法
    2. 流程图
  3. 测试
    1. 算法
    2. 流程图

三、结果与讨论

  1. 迭代次数的变化
  2. 输入向量数量的变化
  3. 学习率参数的变化
  4. 性能观察
    1. 参数变化的影响
    2. 图形表示重叠异常
    3. 正交不可分离性

四、参考文献

五、附录

摘要

本项目的主要目标是展示人工神经网络实现在识别扩展光学语言符号集方面的能力。该技术的应用范围从文档数字化和保存到手持设备中的手写文本识别。

正确识别甚至印刷体光学语言符号的经典困难在于,由于字体、样式和大小的变化,相同字符的图形表示之间存在复杂的不规则性。这种不规则性在处理手写字符时无疑会加剧。

因此,将符号图像映射到矩阵、分析像素和/或矢量数据并尝试确定哪个符号对应哪个字符的传统编程方法,几乎不会产生或根本不会产生实际结果。显然,所需的方法将是能够检测图形表示与已知符号的“接近度”并根据此接近度做出决策的方法。要在传统编程中实现这种接近度算法,需要编写无休止的代码,每种可能的不规则性或与假定输出的偏差(无论是像素还是矢量参数方面)都需要一段代码,这显然不切实际。

在此特定应用领域中出现的一种新兴技术是使用人工神经网络实现,其中网络采用特定指导(学习规则)来更新其节点之间的链接(权重)。此类网络可以从输入图像的图形分析中获取数据,并经过训练以某种形式输出字符。具体来说,一些网络模型使用一组期望输出与实际输出进行比较,并计算误差以用于调整其权重。这种学习规则被称为监督学习。

多层感知器 (MLP) 模型就是这样一种具有监督学习规则的网络。它使用广义 Delta 学习规则来调整其权重,并且可以通过多次迭代对一组输入/期望输出值进行训练。该特定模型的本质是,如果向网络输入它未训练过的输入变体,它将强制输出为附近的某个值,从而解决了接近度问题。这两个概念将在本报告的引言部分进行讨论。

该项目采用了上述 MLP 技术,并在多种广泛使用的字体类型上取得了优异的结果。后续章节将讨论处理输入图像、检测图形符号、分析和映射符号以及训练网络以获取与输入图像对应的期望 Unicode 字符集所遵循的技术方法。尽管该实现在功能和鲁棒性方面可能存在一些限制,但研究人员相信它完全达到了解决预期目标的目的。

引言

一、引言

A. 人工神经网络

使用神经网络机制对系统和功能进行建模是计算机技术中相对较新且正在发展中的科学。该特定领域的基础来源于神经元在自然动物大脑(尤其是人类)中相互作用和功能的方式。众所周知,动物大脑在识别、推理、反应和损伤恢复方面以大规模并行方式运作。所有这些看似复杂的任务现在都被理解为归因于非常简单的模式存储和检索算法的聚合。大脑中的神经元通过称为突触的特殊电化学连接相互通信。一个神经元一次可以与多达10,000个其他神经元连接,尽管观察到高达数十万个连接也存在。典型的人类大脑在出生时估计拥有超过一千亿个神经元。这种组合将产生1015个突触连接,这赋予了大脑在复杂空间图形计算方面的强大能力。

与动物大脑不同,传统计算机以串行模式工作,这意味着指令一次只执行一条,假设是单处理器机器。多任务处理和实时交互的错觉是通过高速计算和进程调度来模拟的。与内部以电化学连接通信(可达到毫秒范围内的最大速度)的自然大脑相比,微处理器在微秒范围的较低速度执行指令。像英特尔奔腾4或AMD Opteron这样的现代处理器,利用多管道和超线程技术,每秒可以执行多达20MFloPs(百万浮点运算)。

正是人工机器的这种速度优势以及自然大脑的并行能力启发了人们努力将两者结合起来,并实现过去被认为不可能完成的复杂“人工智能”任务。尽管人工神经网络目前是在传统串行操作的计算机中实现的,但它们仍然以模拟方式利用大脑的并行能力。

在过去几年中,神经网络引起了人们极大的兴趣,并被成功应用于各种问题领域,涵盖金融、医学、工程、地质和物理等不同领域。实际上,任何存在预测、分类或控制问题的地方,都会引入神经网络。这种巨大的成功可归因于几个关键因素

能力: 神经网络是非常复杂的建模技术,能够对极其复杂的功能进行建模。特别是,神经网络是**非线性**的。多年来,线性建模一直是大多数建模领域中常用的技术,因为线性模型具有众所周知的优化策略。当线性近似不成立时(这种情况经常发生),模型就会相应地受损。神经网络还解决了困扰试图用大量变量建模非线性函数的**维度诅咒**问题。

易用性: 神经网络**通过示例学习**。神经网络用户收集代表性数据,然后调用**训练算法**来自动学习数据的结构。虽然用户确实需要一些关于如何选择和准备数据、如何选择合适的神经网络以及如何解释结果的启发式知识,但成功应用神经网络所需的知识水平远低于使用(例如)一些更传统的非线性统计方法所需的用户知识水平。

B. 多层感知器神经网络模型

为了捕捉生物神经网络系统的精髓,人工**神经元**定义如下

  • 它接收多个输入(来自原始数据,或来自神经网络中其他神经元的输出)。每个输入都通过一个具有强度(或**权重**)的连接;这些权重对应于生物神经元中的突触效能。每个神经元也有一个单一的阈值。输入加权求和,并减去阈值,构成神经元的**激活**(也称为神经元的突触后电位,或PSP)。

  • 激活信号通过激活函数(也称为传递函数)以产生神经元的输出。

如果使用阶跃激活函数(即,如果输入小于零,神经元的输出为0;如果输入大于或等于0,则输出为1),那么神经元的功能就如同前面描述的生物神经元(从加权和中减去阈值并与零比较,等同于将加权和与阈值比较)。实际上,阶跃函数在人工神经网络中很少使用,这一点将稍后讨论。另请注意,权重可以是负值,这意味着突触对神经元具有抑制而非兴奋作用:抑制性神经元在大脑中也有发现。

这描述了一个独立的神经元。下一个问题是:神经元应该如何连接在一起?如果一个网络要发挥任何作用,它必须有输入(承载外部世界感兴趣变量的值)和输出(形成预测或控制信号)。输入和输出对应于感觉神经和运动神经,例如来自眼睛并通向手的神经。然而,也可能存在在网络中发挥内部作用的隐藏神经元。输入、隐藏和输出神经元需要连接在一起。

典型的前馈网络具有按明确分层拓扑排列的神经元。输入层根本不是真正的神经层:这些单元只是用来引入输入变量的值。隐藏层和输出层神经元都连接到前一层中的所有单元。同样,可以定义仅部分连接到前一层中某些单元的网络;但是,对于大多数应用而言,全连接网络更好。

多层感知器神经网络可能是当今最流行的网络架构。每个单元对其输入执行带偏差的加权和,并通过激活函数传递此激活水平以产生其输出,并且这些单元以分层前馈拓扑排列。因此,该网络可以简单地解释为一种输入-输出模型,其中权重和阈值(偏差)是模型的自由参数。这种网络可以模拟几乎任意复杂度的函数,其中层数和每层中的单元数决定了函数的复杂度。多层感知器 (MLP) 设计中的重要问题包括隐藏层数量和每层单元数量的规范。

图1 典型前馈网络

最常见的激活函数是逻辑函数和双曲正切S形函数。该项目使用了**双曲正切函数:** **及其导数:**。

C. 光学语言符号

几种语言的特点是拥有自己的书面符号表示(字符)。这些字符有时是特定音素、重音或整个单词的代表。在结构方面,世界语言字符表现出不同层次的组织。关于这种结构,在易于构建和空间节约之间总是存在一个折衷问题。像拉丁字母集这样的高度结构化字母表可以轻松构建语言元素,同时强制使用额外的空间。像埃塞俄比亚语(格阿兹语)这样的中等结构字母表由于在一个符号中表示整个音素和音调而节省了空间,但却需要扩展的符号集,因此使用和学习难度较大。有些字母表,即东方字母表,结构化程度非常低,整个单词都由单个符号表示。这些语言由数千个符号组成,并且需要长达一生的学习周期。

在数字计算机中表示字母符号自计算机时代开始以来一直是一个问题。这种表示(编码)的最初努力是针对拉丁字母的字母数字集以及一些常见的数学和格式化符号。直到1960年代,美国计算机标准局 ANSI 才制定并发布了正式的编码标准,并将其命名为 ASCII 字符集。它由8位编码的计算机符号组成,总共有256个可能的独特符号。在某些情况下,允许某些键组合形成16位字来表示扩展符号。字符在用户显示器上的最终呈现留给了应用程序,以便允许实现各种字体和样式。

当时,人们认为256个以上编码字符足以满足计算机使用的所有需求。但随着非西方社会计算机市场的兴起和互联网时代的到来,计算机中需要表示更多的字母集。满足这一要求的初步尝试是基于ASCII编码字符的进一步组合来表示新符号。然而,这导致了字符渲染的严重混乱,尤其是在网页中,因为用户必须在浏览器上选择正确的编码。另一个困难在于协调不同实现者之间的按键组合使用以确保唯一性。

在1990年代,一个独立的联盟提出了最终解决方案,将基本编码宽度扩展到16位,并容纳多达65,536个独特的符号。新的编码因其能够在一个编码中表示所有已知符号而被命名为Unicode。为了保持与现有系统的兼容性,新集的前256个代码保留给了ASCII集。ASCII字符可以通过读取低8位并忽略其余部分或反之亦然,从Unicode字中提取,这取决于所使用的字节序类型(大端或小端)。

Unicode 集由 Unicode 联盟管理,该联盟负责审查编码请求、验证符号并批准最终编码,附带一组唯一的 16 位代码。该集仍有很大一部分未被占用,等待满足任何即将到来的请求。自成立以来,微软等流行的计算机硬件和软件制造商都接受并支持 Unicode 的努力。

顺便说一下,研究人员提到埃塞俄比亚字母(通常称为 Ge’ez)在 Unicode 集中表示为 1200H – 137FH

二、技术概述

A. 简介

本项目中网络实现的运作可归纳为以下步骤

  • 训练阶段
    • 分析图像中的字符
    • 将符号转换为像素矩阵
    • 检索对应的期望输出字符并转换为 Unicode
    • 将矩阵线性化并输入网络
    • 计算输出
    • 将输出与期望的 Unicode 值进行比较并计算误差
    • 相应地调整权重并重复该过程直到预设的迭代次数
  • 测试阶段
    • 分析图像中的字符
    • 将符号转换为像素矩阵
    • 计算输出
    • 显示 Unicode 输出的字符表示

实施的基本组成部分是

  • 网络的形成和权重初始化例程
  • 图像的像素分析以进行符号检测
  • 用于训练输入图像和相应期望输出字符的加载例程,存储在名为字符训练器集 (*.cts) 的特殊文件中
  • 训练好的网络(权重值)的加载和保存例程
  • 字符到二进制 Unicode 和反向转换例程
  • 误差、输出和权重计算例程

1. 网络形成

本项目所实现的 MLP 网络由3层组成,一层输入层,一层隐藏层和一层输出层。

输入层由 150 个神经元组成,它们从一个 10x15 的符号像素矩阵接收像素二进制数据。这个矩阵的大小是根据在不引入任何显著像素噪声的情况下可以映射的字符图像的平均高度和宽度来决定的。

隐藏层由 250 个神经元组成,其数量是根据试错法得到的最优结果确定的。

输出层由 16 个神经元组成,对应于 Unicode 编码的 16 位。

为了初始化权重,使用了随机函数来分配一个介于两个预设整数**±weight_bias**之间的初始随机数。权重偏差通过试错观察确定,以对应于快速收敛的平均权重。

图 2 本项目 MLP 网络

2. 符号图像检测

图像分析以通过检查像素来检测字符符号的过程是训练和测试阶段输入集准备的核心部分。符号范围根据单个像素的颜色值从输入图像文件中识别出来,对于本项目而言,假定颜色值为黑色 **RGB(255,0,0,0)** 或白色 **RGB(255,255,255,255)**。输入图像假定为任何分辨率的位图形式,可以映射到 Microsoft Visual Studio 环境中的内部位图对象。该过程还假定输入图像仅由字符组成,并且不考虑任何其他类型的边界对象(如边框线)。

分析图像以检测字符的程序列于以下算法中

一、确定字符行

枚举字符图像(“页面”)中的字符行对于划定检测的界限至关重要。因此,检测图像中的下一个字符不一定涉及重新扫描整个图像。

算法:

  1. 从图像的第一个 x 和第一个 y 像素 pixel(0,0) 开始,将行数设置为 0
  2. 在图像的相同 y 分量上扫描到图像的宽度
    1. 如果检测到黑色像素,则将 y 注册为第一行的顶部
    2. 如果未检测到,则继续扫描下一个像素
    3. 如果在宽度范围内未找到黑色像素,则增加 y 并重置 x 以扫描下一条水平线
  3. 从找到的行的顶部和第一个 x 分量像素 pixel(0,line_top) 开始
  4. 在图像的相同 y 分量上扫描到图像的宽度
    1. 如果未检测到黑色像素,则将 y-1 注册为第一行的底部。增加行数
    2. 如果检测到黑色像素,则增加 y 并重置 x 以扫描下一条水平线
  5. 从找到的最后一行底部下方开始,重复步骤 1-4 以检测后续行
  6. 如果到达图像底部(图像高度),则停止。

二、检测单个符号

检测单个符号涉及扫描字符行以查找由黑色像素组成的正交可分离图像。

算法:

  1. 从第一个字符行顶部和第一个x分量开始
  2. 在相同的y分量上扫描到图像宽度
    1. 如果检测到黑色像素,则将y注册为第一行的顶部
    2. 如果未检测到,则继续扫描下一个像素
  3. 从找到的字符顶部和第一个x分量,pixel(0,character_top)开始
  4. 在相同的x分量上扫描到行底部
    1. 如果找到黑色像素,则将x注册为符号的左侧
    2. 如果未检测到,则继续扫描下一个像素
    3. 如果没有找到黑色像素,则递增x并重置y以扫描下一条垂直线
  5. 从找到的符号的左侧和当前行的顶部,pixel(character_left, line_top)开始
  6. 在相同的x分量上扫描到图像的宽度
    1. 如果未找到黑色字符,则将x-1注册为符号的右侧
    2. 如果找到黑色像素,则递增x并重置y以扫描下一条垂直线
  7. 从当前行底部和符号左侧,pixel(character_left,line_bottom)开始
  8. 在相同的y分量上扫描到字符的右侧
    1. 如果找到黑色像素,则将y注册为字符的底部
    2. 如果没有找到黑色像素,则递减y并重置x以扫描下一条垂直线

图 3. 行和字符边界检测

从所遵循的过程和上图可以看出,检测到的字符边界可能不是该字符的实际边界。这是由于打印字母符号中存在的高度和底部对齐不规则性引起的问题。因此,行顶部不一定意味着所有字符的顶部,行底部也不一定意味着所有字符的底部。

因此,需要对字符的顶部和底部进行确认。

项目中实现的自选确认算法是

  1. 从当前行的顶部和字符的左侧开始
  2. 扫描到字符的右侧
    1. 如果检测到黑色像素,则将 y 注册为已确认的顶部
    2. 如果未检测到,则继续扫描下一个像素
    3. 如果未找到黑色像素,则递增 y 并重置 x 以扫描下一条水平线

图 4. 字符边界确认

3. 符号图像矩阵映射

下一步是将符号图像映射到相应的二维二进制矩阵。这里需要考虑的一个重要问题是确定矩阵的大小。如果将符号的所有像素都映射到矩阵中,肯定能够获取符号的所有区别性像素特征,并最大限度地减少与其他符号的重叠。然而,这种策略将意味着维护和处理一个非常大的矩阵(对于100x150像素的图像,最多1500个元素)。因此,需要一个合理的权衡,以最大限度地减少处理时间,而又不会显著影响图案的可分离性。该项目采用了一种采样策略,将符号图像映射到10x15的二进制矩阵中,只有150个元素。由于单个图像的高度和宽度不同,因此实现了一种自适应采样算法。该算法如下所示

算法

a. 对于宽度(最初为 20 个元素宽)

  1. 将第一个 (0,y) 和最后一个 (width,y) 像素分量直接映射到矩阵的第一个 (0,y) 和最后一个 (20,y) 元素
  2. 将中间像素分量 (width/2,y) 映射到第 10 个矩阵元素
  3. 进一步细分并相应地映射到矩阵

b. 对于高度(最初为 30 个元素高)

  1. 将第一个 x,(0) 和最后一个 (x,height) 像素分量直接映射到矩阵的第一个 (x,0) 和最后一个 (x,30) 元素
  2. 将中间像素分量 (x,height/2) 映射到第 15 个矩阵元素
  3. 进一步细分并相应地映射到矩阵

c. 通过在宽度和高度上以2的因子采样,进一步将矩阵减小到10x15

图 5. 将符号图像映射到二进制矩阵

为了能够将矩阵数据输入到网络(它是单维的),矩阵必须首先线性化为单维。这通过以下算法的简单例程实现

  1. 从第一个矩阵元素 (0,0) 开始
  2. 在 y 保持不变的情况下,将 x 增加到矩阵宽度
    1. 将每个元素映射到线性数组的一个元素(增加数组索引)
    2. 如果达到矩阵宽度,重置 x,增加 y
  3. 重复直到矩阵高度 (x,y)=(width, height)

因此,线性数组是我们的 MLP 网络的输入向量。在训练阶段,来自训练器集图像文件的所有此类符号都被映射到它们自己的线性数组中,并作为一个整体构成一个输入空间。训练器集还将包含一个字符字符串文件,该文件直接对应于输入符号图像,作为训练的期望输出。下面显示了一个示例迷你训练器集

图 6. 示例 Mini-Tahoma 训练器集的输入图像和期望输出文本文件

B. 训练

网络初始化完成后,训练输入空间准备就绪,网络即可进行训练。训练网络时需要解决的一些问题是

  • 输入空间有多混乱?混乱的输入随机变化,范围极端,其成员之间没有可预测的流动。
  • 我们训练网络的模式有多复杂?复杂的模式通常以特征重叠和大数据量为特征。
  • 应使用什么值
    • 学习率
    • S形斜率
    • 权重偏差
  • 给定数量的输入集,训练网络需要多少次迭代(周期)?
  • 如果需要提前停止迭代,应该使用什么误差阈值进行比较?

在模式识别研究中,字母光学符号是最混乱的输入集之一。这是由于其图形表示的不可预测性,从其顺序的序列中可以看出。例如,拉丁字母连续字符“A”和“B”在图形符号形式表示时,特征相似性很小。下图展示了拉丁字母和一些虚构字符集中的混乱和非混乱序列。

图 7. 混乱和非混乱符号序列示例

单个模式数据的复杂性也是字符识别中的另一个问题。每个符号都有大量的独特特征,为了正确识别它,需要对其进行考虑。消除一些特征可能会导致模式重叠,而所需的数据量使其成为模式识别中最复杂的输入空间类别之一。

除上述已知问题外,网络的其他数值参数是实时确定的。它们也因输入符号数量和网络拓扑结构的不同而差异很大。

本项目使用的参数为

  • 学习率 = 150
  • S形斜率 = 0.014
  • 权重偏差 = 30(通过试错确定)
  • 迭代次数 = 300-600(取决于字体类型的复杂性)
  • 平均误差阈值 = 0.0002(通过试错确定)

算法:

训练例程实现了以下基本算法

  1. 根据指定的拓扑参数形成网络
  2. 使用指定 ±weight_bias 值范围内的随机值初始化权重
  3. 加载训练器集文件(包括输入图像和期望输出文本)
  4. 分析输入图像并将所有检测到的符号映射到线性数组中
  5. 从文件中读取期望的输出文本,并将每个字符转换为二进制 Unicode 值以单独存储
  6. 对于每个字符
    1. 计算前馈网络的输出
    2. 与符号对应的期望输出进行比较并计算误差
    3. 将误差反向传播到每个链接以调整权重
  7. 移动到下一个字符并重复步骤 6,直到所有字符都被访问
  8. 计算所有字符的平均误差
  9. 重复步骤 6 和 8,直到达到指定的迭代次数
    1. 是否达到误差阈值?如果是,则中止迭代
    2. 如果不是,则继续迭代

流程图

该算法的流程图表示如下

C. 测试

实施的测试阶段简单明了。由于程序被编码为模块化部分,因此在训练阶段用于加载、分析和计算输入向量网络参数的相同例程也可以在测试阶段重复使用。

测试输入图像中字符的基本步骤可总结如下

算法

  • 加载图像文件
  • 分析图像以获取字符行
  • 对于每个字符行,检测连续的字符符号
    • 分析并处理符号图像以映射到输入向量
    • 将输入向量输入网络并计算输出
    • 将 Unicode 二进制输出转换为相应的字符并渲染到文本框中

流程图

三、结果与讨论

该网络已针对拉丁字母中多种广泛使用的字体类型进行了训练和测试。由于软件的实现是开放的,并且程序代码是可伸缩的,因此包含更多来自任何印刷语言字母表的字体是直接的。

必要的步骤是:准备好一系列输入符号图像在一个图像文件(*.bmp [位图] 扩展名)中,在文本文件(*.cts [字符训练集] 扩展名)中键入相应的字符,并将这两个文件保存在同一个文件夹中(两者除了扩展名外必须具有相同的文件名)。应用程序将提供一个文件打开对话框,供用户定位 *.cts 文本文件,并自行加载相应的图像文件。

尽管后续表格中列出的结果来自使用72pt字体大小创建的符号图像的训练/测试过程,但通过如前所述准备输入/期望输出集,使用任何其他大小的字体也是直接的。该应用程序可以使用小至20pt字体大小的符号图像进行操作。

注意:由于权重值的随机初始化,所列结果仅代表典型的网络性能,其他试验可能无法获得精确的复现。

A. 迭代次数变化的结果

字符数=90,学习率=150,S形斜率=0.014

字体类型

300

600

800

错误字符数

错误率 %

错误字符数

错误率 %

错误字符数

错误率 %

拉丁 Arial

4

4.44

3

3.33

1

1.11

拉丁 Tahoma

1

1.11

0

0

0

0

拉丁 Times Roman

0

0

0

0

1

1.11

B. 输入字符数量变化的结果

迭代次数=100,学习率=150,S形斜率=0.014

字体类型

20

50

90

错误字符数

错误率 %

错误字符数

错误率 %

错误字符数

错误率 %

拉丁 Arial

0

0

6

12

11

12.22

拉丁 Tahoma

0

0

3

6

8

8.89

拉丁 Times Roman

0

0

2

4

9

10

C. 学习率参数变化的结果

字符数=90,迭代次数=600,S形斜率=0.014

字体类型

50

100

120

错误字符数

错误率 %

错误字符数

错误率 %

错误字符数

错误率 %

拉丁 Arial

82

91.11

18

20

3

3.33

拉丁 Tahoma

56

62.22

11

12.22

1

1.11

拉丁 Times Roman

77

85.56

15

16.67

0

0

D. 性能观察

1. 参数变化的影响

  1. 增加迭代次数通常与网络性能呈正相关。然而,在某些情况下,进一步增加迭代次数会产生不利影响,即导致更多错误识别。这部分可归因于学习率参数值较高,当网络接近其最优极限时,进一步的权重更新会导致偏离最优状态。随着进一步的迭代,网络将试图在期望状态之间来回“摆动”,很有可能在最终迭代时错过最优状态。这种现象被称为过学习。

  2. 输入状态的大小也是影响性能的另一个直接因素。很自然地,网络需要训练的输入符号集越多,它就越容易出错。通常,复杂和大型的输入集需要一个拓扑结构更大、迭代次数更多的网络。对于上述最大集90个符号,达到的最优拓扑结构是一个包含250个神经元的隐藏层。

  3. 学习率参数的变化也会影响网络在给定迭代次数限制下的性能。该参数值越小,网络更新其权重的幅度就越小。这直观地意味着它不太可能面临上述过学习的困难,因为它将缓慢且以更精细的方式更新其链接。但不幸的是,这也意味着需要更多的迭代次数才能达到其最佳状态。因此,需要进行权衡以优化网络的整体性能。学习参数的最终最优值为150。

2. 图形表示重叠异常

从结果列表中可以很容易地观察到,字体类型“Latin Arial”的条目通常在其同类中表现最低。这已被发现是由于其两个符号的图形表示重叠引起的,即大写字母“I”(Times Roman 中的“I”)和小写字母“l”(Times Roman 中的“l”)。

图 8. Arial 字体中小写字母“l”(006Ch)和大写字母“I”(0049h)的矩阵分析。

这将无疑给网络带来一个逻辑上不可分离的识别任务,因为训练集将指示它为某个符号图像输出一个状态,而在其他时间为相同的图像输出另一个状态。这不仅会干扰这两个字符的输出向量,还会干扰附近的图像,如错误字符的数量所示。在这种情况下,网络能够达到的最佳状态是训练自身为两个输入输出同一个向量,从而导致其中一个输出处于错误状态。然而,这种最佳状态只能通过更多次迭代才能达到,对于本实现而言是 800 次。在如此高的迭代次数下,其他集合往往会如前所述陷入过学习状态。

3. 正交不可分性

某些符号序列是正交不可分的。这意味着,在两个符号之间无法插入一条垂直线,而不穿过其中任何一个的位图区域。由于这需要复杂的图像处理算法,因此在本项目范围内无法对单个符号的此类图像进行处理。下面列出了一些示例

图 9. 拉丁字母中一些正交不可分的符号组合

五、附录

A. ASCII 码表

1. 字符编码表 1 (0-127)

2. 字符编码表 2 (128-255)

B. 代码列表 [MS Visual C#.NET]

© 2006, Daniel Admassu

1. 图像行识别

public void identify_lines()
{               
    int y=image_start_pixel_y;
    int x=image_start_pixel_x;
    bool no_black_pixel;
    int line_number=0;
    line_present=true;

    while(line_present)
    {                    
        x=image_start_pixel_x;

        while(Convert.ToString (input_image.GetPixel (x,y))==
            "Color [A=255, R=255, G=255, B=255]")
        {
            x++;
            if(x==input_image_width)
            {
                x=image_start_pixel_x;
                y++;
            }
            if(y>=input_image_height)
            {
                line_present=false;
                break;
            }
        }
        if(line_present)
        {
            line_top[line_number]=y;
            no_black_pixel=false;
            while(no_black_pixel==false)
            {                    
                y++;
                no_black_pixel=true;
                for(x=image_start_pixel_x;x<input_image_width;x++)
                    if((Convert.ToString (input_image.GetPixel (x,y))==
                        "Color [A=255, R=0, G=0, B=0]"))      

                no_black_pixel=false;
            }               
            line_bottom[line_number]=y-1;
            line_number++;
        }
    }
    number_of_lines=line_number;               
}

2. 字符符号检测

public void get_character_bounds()
{                 
    int x=image_start_pixel_x;
    int y=image_start_pixel_y;                
    bool no_black_pixel=false;
    if(y<=input_image_height && x<=input_image_width)
    {
        while(Convert.ToString (input_image.GetPixel (x,y))==
            "Color [A=255, R=255, G=255, B=255]")
        {
            x++;
            if(x==input_image_width)
            {
                x=image_start_pixel_x;  y++;
            }
            if(y>=line_bottom[current_line])
            {
                character_present=false; break;
            }
        }
        if(character_present)
        {
            top=y;      
            x=image_start_pixel_x; y=image_start_pixel_y;   
            while(Convert.ToString (input_image.GetPixel (x,y))==
                "Color [A=255, R=255, G=255, B=255]")
            {
                y++;
                if(y==line_bottom[current_line])
                {
                    y=image_start_pixel_y;  x++;
                }
                if(x>input_image_width) break;
            }
            if(x<input_image_width) left=x;
            no_black_pixel=true;
            y=line_bottom[current_line]+2;
            while(no_black_pixel==true)
            {                       
                y--;                                
                for(x=image_start_pixel_x;x<input_image_width;x++)
                    if((Convert.ToString (input_image.GetPixel (x,y))==
                        "Color [A=255, R=0, G=0, B=0]"))   
                        no_black_pixel=false;                           
            }                 
            bottom=y;
            no_black_pixel=false;
            x=left+10;
            while(no_black_pixel==false)
            {
                x++; no_black_pixel=true;
                for(y=image_start_pixel_y;y<line_bottom[current_line];y++)
                    if((Convert.ToString (input_image.GetPixel (x,y))==
                        "Color [A=255, R=0, G=0, B=0]"))               
                        no_black_pixel=false;                           
            }           
            right=x-1;                          
        }                 
    }
3. 网络形成

public void form_network()
{
      layers[0]=number_of_input_nodes;
      layers[number_of_layers-1]=number_of_output_nodes;
      for(int i=1;i<number_of_layers-1;i++)
            layers[i]=maximum_layers;
}

4. 权重初始化

public void initialize_weights()
{
    for(int i=1;i<number_of_layers;i++)
        for(int j=0;j<layers[i];j++)
            for(int k=0;k<layers[i-1];k++)
                weight[i,j,k]=(float)(rnd.Next(-weight_bias,weight_bias));
}

5. 网络训练

public void train_network()
{                                                     
    int set_number;   
    float average_error=0.0F;
    progressBar1.Maximum =epochs;

    for(int epoch=0;epoch<=epochs;epoch++)
    {
        average_error=0.0F;
        for(int i=0;i<number_of_input_sets;i++)
        {                             
            set_number=rnd.Next(0,number_of_input_sets);
            get_inputs(set_number);
            get_desired_outputs(set_number);
            calculate_outputs();                
            calculate_errors();
            calculate_weights();
            average_error=average_error+get_average_error();
        }                                               

        progressBar1.PerformStep ();
        label15.Text = epoch.ToString ();
        label15.Update ();
        average_error=average_error/number_of_input_sets;

        if(average_error<error_threshold)
        {
            epoch=epochs+1;                           
            progressBar1.Value =progressBar1.Maximum;                         
            label22.Text ="<"+error_threshold.ToString ();
            label22.Update ();
        }                       
    }                                               
}

6. 输出计算

public void calculate_outputs()
{
    float f_net;
    int number_of_weights;
    for(int i=0;i<number_of_layers;i++)
        for(int j=0;j<layers[i];j++)
        {
            f_net=0.0F;
            if(i==0) number_of_weights=1;
            else number_of_weights=layers[i-1];             
            for(int k=0;k<number_of_weights;k++)
                if(i==0)
                    f_net=current_input[j];
                else
                    f_net=f_net+node_output[i-1,k]*weight[i,j,k];
            node_output[i,j]=sigmoid(f_net);
        }                 
}

7. 激活函数

public float sigmoid(float f_net)
{                                   
      float result=(float)((2/(1+Math.Exp(-1*slope*f_net)))-1); // Bipolar
      return result;
}

public float sigmoid_derivative(float result)
{
      float derivative=(float)(0.5F*(1-Math.Pow(result,2))); // Bipolar
      return derivative;
}

8. 误差计算

public void calculate_errors()
{
    float sum=0.0F;
    for(int i=0;i<number_of_output_nodes;i++)                   
        error[number_of_layers-1,i] = 
            (float)((desired_output[i]-node_output[number_of_layers-1,i])*
            sigmoid_derivative(node_output[number_of_layers-1,i]));

    for(int i=number_of_layers-2;i>=0;i--)
        for(int j=0;j<layers[i];j++)
        {
            sum=0.0F;
            for(int k=0;k<layers[i+1];k++)
                sum=sum+error[i+1,k]*weight[i+1,k,j];
            error[i,j] = (float)(sigmoid_derivative(node_output[i,j])*sum);
        }
}

9. 权重更新

public void calculate_weights()
{
    for(int i=1;i<number_of_layers;i++)
        for(int j=0;j<layers[i];j++)
            for(int k=0;k<layers[i-1];k++)
            {
                weight[i,j,k] = (float)(weight[i,j,k] + 
                    learning_rate*error[i,j]*node_output[i-1,k]);
            }
}

10. Unicode 到字符及字符到 Unicode 的转换

public void character_to_unicode(string character)
{                 
      int byteCount = unicode.GetByteCount(character.ToCharArray());
      byte[] bytes = new Byte[byteCount];             
      bytes= unicode.GetBytes(character);
      BitArray bits = new BitArray( bytes );
      System.Collections.IEnumerator bit_enumerator = bits.GetEnumerator();
      int bit_array_length = bits.Length;                   
      bit_enumerator.Reset ();

      for(int i=0;i<bit_array_length;i++)
      {
            bit_enumerator.MoveNext();
            if(bit_enumerator.Current.ToString()=="True")
                  desired_output_bit[i]=1;
            else
                  desired_output_bit[i]=0;
      }      
}

public char unicode_to_character()
{
      int dec=binary_to_decimal();              
      Byte[] bytes = new Byte[2];               
      bytes[0]=(byte)(dec);
      bytes[1]=0;
      int charCount = unicode.GetCharCount(bytes);
      char[] chars = new Char[charCount];
      chars=unicode.GetChars(bytes);
      return chars[0];
}

C. 软件用户界面

© 2006, Daniel Admassu

D. 示例网络文件 [拉丁 Times Roman.ann]

Unicode OCR ANN Weight values. © 2006 Daniel Admassu. 
Network Name       = Latin Times Roman
Hidden Layer Size  = 250
Number of Patterns = 90
Number of Epochs   = 300
Learning Rate      = 150
Sigmoid Slope      = 0.014
Weight Bias        = 30

Weight[1 , 0 , 0] = -344.8769
Weight[1 , 0 , 1] = -490.7207
Weight[1 , 0 , 2] = -739.5387
Weight[1 , 0 , 3] = -401.8878
Weight[1 , 0 , 4] = 183.874
Weight[1 , 0 , 5] = 167.6926
Weight[1 , 0 , 6] = -29.25896
Weight[1 , 0 , 7] = -42.90004
Weight[1 , 0 , 8] = 443.4576
Weight[1 , 0 , 9] = -208.5291
Weight[1 , 0 , 10] = -264.9088
Weight[1 , 0 , 11] = 16.65468
Weight[1 , 0 , 12] = 401.0692
Weight[1 , 0 , 13] = 409.3685
Weight[1 , 0 , 14] = -161.1487
Weight[1 , 0 , 15] = 89.9006
Weight[1 , 0 , 16] = -851.1705
Weight[1 , 0 , 17] = -96.01544
Weight[1 , 0 , 18] = 290.6281
Weight[1 , 0 , 19] = 64.17367
.
.
.
Weight[2 , 15 , 237] = -5.202068
Weight[2 , 15 , 238] = -16.83099
Weight[2 , 15 , 239] = 23.25441
Weight[2 , 15 , 240] = -24.27003
Weight[2 , 15 , 241] = -4.82569
Weight[2 , 15 , 242] = 36.25824
Weight[2 , 15 , 243] = -28.49981
Weight[2 , 15 , 244] = -8.105846
Weight[2 , 15 , 245] = -1.679604
Weight[2 , 15 , 246] = -3.154837
Weight[2 , 15 , 247] = -11.18855
Weight[2 , 15 , 248] = 8.335608
Weight[2 , 15 , 249] = 30.13228

六、参考文献

  1. 人工智能与认知科学
    © 2006, Nils J. Nilsson
    斯坦福人工智能实验室
    http://ai.stanford.edu/~nilsson
  2. 使用人工神经网络的离线手写识别
    © 2000, Andrew T. Wilson
    明尼苏达大学莫里斯分校
    http://wilsonat@mrs.umn.edu
  3. 使用神经网络创建自适应字符识别系统
    © 2002, Alexander J. Faaborg
    康奈尔大学,纽约伊萨卡
  4. 使用神经网络的手写字符识别器
    © 2000, Shahzad Malik
  5. 神经网络与模糊逻辑.
    © 1995, Rao, V., Rao, H.
    MIS 出版社,纽约
  6. 神经网络
    © 2003, StatSoft Inc.
    http://www.statsoft.com/textbook/stneunet.html
© . All rights reserved.