Alex Cherkasov Создание системы оптического распознавания символов
В Библиотеку
Алейкин Владислав Валерьевич




Создание системы оптического распознавания символов

Author: Alex Cherkasov
Source: http://www.codeproject.com/kb/dotnet/simple_ocr.aspx


Введение

      Много людей сегодня пытается написать их собственное оптическое распознавание символов (Оптическое Распознавание символов) Система или улучшить качество существующего.
      Эта статья показывает, как использование искусственной нейронной сети упрощает разработку оптического приложения распознавания символов, достигая высокого качества распознавания и хорошей производительности.

Предисловие

      Разработка частной системы оптического распознавания символов является сложной задачей и требует большого усилия. Такие системы обычно действительно сложны и могут скрыть много логики позади кода. Использование искусственной нейронной сети в приложениях оптического распознавания символов может резко упростить код и улучшить качество распознавания, достигая хорошей производительности. Другая выгода использования нейронной сети в оптическом распознавании символов является расширяемостью системной способности распознать больше кодировок чем первоначально определено. Большинство традиционных систем оптического распознавания символов не достаточно расширяемы. Почему? Поскольку такая задача как работа с десятками тысяч китайскими символами, например, не столь же проста как работающий с 68 английскими напечатанными кодировками, и это может легко принести традиционную систему к своим коленям!
      Хорошо, Искусственная Нейронная сеть (ИНС) является замечательным инструментом, который может помочь решать такой вид проблем. ИНС - парадигма обработки данных, вдохновленная по тому, как человеческий мозг обрабатывает информацию. Искусственные нейронные сети - коллекции математических моделей, которые представляют некоторые из наблюдаемых свойств биологических нервных систем и привлекают аналогии адаптивного биологического изучения. Основной элемент ИНС - топология. ИНС состоит из большого количества чрезвычайно связанных элементарных процессоров (узлы), которые связаны со взвешенными соединениями (ссылки). Изучение в биологических системах вовлекает корректировки синаптических подключений, которые существуют между нейронами. Это - истина для ИНС также. Изучение типично происходит примером через обучение, или незащищенностью к ряду данных ввода-вывода (шаблон), где учебный алгоритм корректирует веса ссылки. Веса ссылки сохраняют знание, необходимое, чтобы решить определенные проблемы.
      Порожденный в конце 1950-ых, нейронные сети не получают большую популярность до 1980-ых. Сегодня АННС главным образом используются для решения сложных проблем реального мира. Они часто хороши при решении проблем, которые слишком сложны для обычных технологий (например, проблемы, у которых нет алгоритмического решения или для которого алгоритмическое решение слишком сложно, чтобы быть найденным) и часто хорошо подходят для проблем, которые люди способны решать, но для которого традиционные методы не. Они - хорошие механизмы распознавания образов и устойчивые классификаторы, со способностью сделать вывод в принятии решений, основанном на неточных входных данных. Они предлагают идеальные решения множества проблем классификации, таких как речь, символ и сообщают о распознавании, так же как функциональном предсказании и системном моделировании, где физические процессы не поняты или очень сложны. Преимущество АННС находится в их способности системы противостоять ошибкам против искажений во входных данных и их возможности учиться.

Использование кода

      В этой статье я использую пример приложения от Neuro.NET библиотеки, чтобы показать, как использовать нейронную сеть Обратной связи в простом приложении оптического распознавания символов.
      Давайте предположим, что Вы уже прошли все подпрограммы предварительной обработки изображения (перевыборка, дескью, зонирование, блокируя и т.д.), и у Вас уже есть изображения символов из Вашего документа. (В примере я просто генерирую те изображения).

Создание нейронной сети

      Давайте сначала создадим сеть. В этом примере я использую нейронную сеть Обратной связи. Сеть Обратной связи - многослойная персептрон модель с входным уровнем, один или более скрытыми уровнями, и уровнем вывода.
      Узлы в нейронной сети Обратной связи связаны через взвешенные ссылки с каждым узлом, обычно соединяющимся со следующим уровнем, до уровня вывода, который обеспечивает вывод для сети. Значения входного набора представлены и назначены на входные узлы входного уровня. Входные значения инициализированы к значениям между -1 и 1. Узлы в следующем уровне получают входные значения через ссылки и вычисляют собственные значения вывода, которые тогда передают к следующему уровню. Эти значения размножаются вперед через уровни, пока уровень вывода не достигнут, или помещен иначе, пока каждый узел уровня вывода не произвел значение вывода для сети. Желательный вывод для входного набора используется, чтобы вычислить ошибочное значение для каждого узла в уровне вывода, и затем размножен назад (и вот то, где сетевое название входит) через сеть, поскольку правило дельты используется, чтобы корректировать значения ссылки, чтобы произвести лучше, желательный вывод. Как только ошибка, произведенная шаблонами в учебном наборе, ниже данного допуска, обучение закончено, и сеть представлена новые входные наборы и производить вывод, основанный на опыте, который это получало от процесса обучения.
      Я буду использовать библиотечный класс BackPropagationRPROPNetwork и создам свой собственный OCRNetwork.

//Inherit form Backpropagation neural network
public class OCRNetwork: BackPropagationRPROPNetwork
{
//Override method of the base class in order to implement our
//own training method
public override void Train(PatternsCollection patterns)
{
...
}
}

      Я переопределил Train метод базового класса, чтобы осуществить мой собственный учебный метод. Почему я должен сделать это? Я делаю это из-за одной простой причины: учебное продвижение сети измерено качеством результата, к которому приводят, и скоростью обучения. Вы должны установить критерии, когда качество сетевого вывода является приемлемым для Вас и когда Вы можете остановить учебный процесс. Реализация, которую я обеспечиваю здесь, доказана (основанной на моем опыте) быть быстрым и точным. Я решил, что я могу остановить учебный процесс, когда сеть в состоянии распознать все шаблоны, без единственной ошибки. Так, вот реализация моего учебного метода.
public override void Train(PatternsCollection patterns)
{ //Current iteration number
if (patterns != null)
{
double error = 0;
int good = 0;
// Train until all patterns are correct
while (good < patterns.Count)
{
good = 0;
for (int i = 0; i {
//Set the input values of the network
for (int k = 0; k nodes[k].Value = patterns[i].Input[k];
//Run the network
this.Run();
//Set the expected result
for (int k = 0;k< this.OutputNodesCount;k++)
this.OutputNode(k).Error = patterns[i].Output[k];
//Make the network to remember corresponding output
//values. (Teach the network)
this.Learn();
//See if network did produced correct result during
//this iteration
if (BestNodeIndex == OutputPatternIndex(patterns[i]))
good++;
}
//Adjust weights of the links in the network to their
//average value. (An epoch training technique)
foreach (NeuroLink link in links)
((EpochBackPropagationLink)link).Epoch(patterns.Count);
}
}
}

      Кроме того, я реализовал метод BestNodeIndex, которое возвращает индекс узла, имеющего максимальное значение и имеющего минимальную ошибку. OutputPatternIndex метод возвращает индекс элемента вывода шаблона, имеющего значение 1. Если те индексы согласованы, сеть привела к правильному результату. Вот то, на что BestNodeIndex реализация похожа:
public int BestNodeIndex
{
get {
int result = -1;
double aMaxNodeValue = 0;
double aMinError = double.PositiveInfinity;
for (int i = 0; i< this.OutputNodesCount;i++)
{
NeuroNode node = OutputNode(i);
//Look for a node with maximum value or lesser error
if ((node.Value > aMaxNodeValue)||
((node.Value >= aMaxNodeValue)&&(node.Error {
aMaxNodeValue = node.Value;
aMinError = node.Error;
result = i;
}
}
return result;
}
}

      Так же я создаю экземпляр класса нейронной сети. У сети есть один массив целого числа, параметр конструктора, описывающий число узлов в каждом уровне сети. Первый уровень в сети - входной уровень. Ряд элементов в этом уровне соответствует ряду элементов во входном наборе и равен ряду элементов в цифровой матрице изображения (мы будем говорить об этом позже). У сети могут быть множественные средние уровни с различным числом узлов в каждом уровне. В этом примере я использую только один уровень и применяю официальное правило чтобы определить число узлов в этом уровне:
NodesNumber = (InputsCount+OutputsCount) / 2;

      Отметьте: Вы можете экспериментировать, добавляя больше средних уровней и используя различное число узлов в там - только, чтобы видеть, как оно затронет учебную скорость и качество распознавания сети.
      Последний уровень в сети - уровень вывода. Это - уровень, где мы ищем результаты. Я определяю число узлов в этом уровне, равном числу символов что мы собирающийся распознавать.

Создание учебных шаблонов

      Теперь давайте говорить об учебных шаблонах. Шаблоны будут использоваться для того, чтобы обучать нейронную сеть распознавать изображения. В основном, каждый учебный шаблон состоит из двух одномерных массивов чисел с плавающей точкой Inputs и Outputs массивы.
      Inputs массив содержит Ваши входные данные. В нашем случае это - цифровое представление изображения символа. Под оцифровкой изображения я подразумеваю процесс создания яркости (или абсолютная величина цветного вектора - независимо от того, что Вы выбираете), карта изображения. Чтобы создать эту карту, я разбил изображение на квадраты и вычисляю среднее значение каждого квадрата. Тогда я сохраняю те значения в массив.

      Я осуществил метод CharToDoubleArray, чтобы оцифровать изображение. Там я использую абсолютную величину цвета для каждого элемента матрицы. (Без сомнения, Вы можете использовать другие методики) После того, как изображение оцифровано, я должен сократить результаты, чтобы вместить их в диапазон от -1..1, чтобы выполнить ввод оценивает диапазон сети. Чтобы сделать это, я написал метод Scale, где я ищу максимальное значение элемента матрицы и затем делю все элементы матрицы. Реализация CharToDoubleArray выглядит следующим образом:
//aSrc an image of the character
//aArrayDim dimension of the pattern matrix
//calculate image quotation X step
double xStep = (double)aSrc.Width/(double)aArrayDim;
//calculate image quotation Y step
double yStep = (double)aSrc.Height/(double)aArrayDim;
double[] result = new double[aMatrixDim*aMatrixDim ];
for (int i=0; i for (int j=0;j {
//calculate matrix address
int x = (int)(i/xStep);
int y = (int)(j/yStep);
//Get the color of the pixel
Color c = aSrc.GetPixel(i,j);
//Absolute value of the color, but I guess, it is possible to
//use the B component of Alpha color space too...
result[y*x+y]+=Math.Sqrt(c.R*c.R+c.B*c.B+c.G*c.G);
}
//Scale the matrix to fit values into a range from 0..1 (required by
//ANN) In this method we look for a maximum value of the element
//and then divide all elements of the matrix by this maximum value.
return Scale(result);

      Outputs массив шаблона представляет ожидаемый результат - результат, который сеть будет использовать во время обучения. Так, например, чтобы учить сети распознавать английские символы от A до Z мы будем нуждаться в 25 элементах в Outputs массиве. Сделайте это 50, если Вы решаете включать символы строчных букв. Каждый элемент соответствует одной букве.
      Inputs из каждого шаблона установлены в цифровые данные изображения и соответствующий элемент в Outputs выстройте к 1, таким образом сеть будет знать, которые выводят (символ), соответствует входным данным. Метод CreateTrainingPatterns делает это задание для меня:
public PatternsCollection CreateTrainingPatterns(Font font) {
//Create pattern collection
// As many inputs (examples) as many elements in digitized image matrix
// As many outputs as many characters we going to recognize.
PatternsCollection result = new PatternsCollection(aCharsCount,
aMatrixDim * aMatrixDim, aCharsCount);
// generate one pattern for each character
for (int i= 0; i {
//CharToDoubleArray creates an image of the character and digitizes it.
//You can change this method to pass actual the image of the character
double[] aBitMatrix = CharToDoubleArray(Convert.ToChar(aFirstChar + i),
font, aMatrixDim, 0);
//Assign matrix value as input to the pattern
for (int j = 0; j result[i].Input[j] = aBitMatrix[j];
//Output value set to 1 for corresponding character.
//Rest of the outputs are set to 0 by default.
result[i].Output[i] = 1;
}
return result;
}

      Теперь мы завершили создание шаблонов, и мы можем использовать те, чтобы обучать нейронную сеть.

Обучение сети

      Запустить учебный процесс сети можно с помощью Train метода и передайте туда обучающие множества.
      Обычно, поток выполнения завершит этот метод, когда обучение будет закончено, но в некоторых случаях может произойти зацикливание(!). Train метод в настоящее время осуществляется, полагаясь только на один факт: рано или поздно будет закончено сетевое обучение. Хорошо, я допускаю - это - неправильное предположение, и сетевое обучение никогда, возможно, не завершится. Большинство причин для отказа обучения нейронной сети:
Обучение никогда не завершает потому что:Возможное решение
1. Топология сети слишком проста, чтобы обработать количество учебных шаблонов, которые Вы обеспечиваете. Вы должны будете создать большую сеть.Добавьте больше узлов в средний уровень или добавьте больше средних уровней к сети.
2. Учебные шаблоны не достаточно ясны, не точны или слишком сложны для сети, чтобы дифференцировать их.Как решение Вы можете убрать шаблоны, или Вы можете использовать различный тип сети / учебный алгоритм. Кроме того, Вы не можете обучать сеть предполагать затем лотерейные номера победы... :-)
3. Ваши учебные ожидания слишком высоки и/или не реалистичны.Понизьте свои ожидания. Сеть никогда не могла быть на 100 % "уверена"
4. Никакая причинаПроверьте код!

      Большинство тех причин очень просто решить, и это - хорошая тема для будущей статьи. Тем временем, мы можем наслаждаться результатами.

Наслаждение результатами

      Чтобы использовать сеть, Вы должны загрузить свои данные во входной уровень. Тогда используйте Run метод, чтобы позволить сети обрабатывать Ваши данные. Наконец, получите свои результаты из узлов вывода сети и проанализируйте те ( BestNodeIndex свойство я создал в OCRNetwork класс делает это задание для меня).