Оптимизированный OCR черный/белый пиксельный алгоритм

Я пишу простое решение OCR для конечного множества символов. Таким образом, я знаю точный способ, которым будут похожи все 26 букв в алфавите. Я использую C# и могу легко определить, нужно ли данный пиксель рассматривать как черный или белый.

Я генерирую матрицу черных/белых пикселей для каждого символа. Так, например, буква I (капитал i), мог бы быть похожим на следующее:

01110
00100
00100
00100
01110

Примечание: все точки, которые я использую позже в этом сообщении, предполагают, что верхний левый пиксель (0, 0), нижний правый пиксель (4, 4). 1's представляют черные пиксели, и 0 представляет белые пиксели.

Я создал бы соответствующую матрицу в C# как это:

CreateLetter("I", new List<List<bool>>() {
  new List<bool>() { false, true,  true, true,  false },
  new List<bool>() { false, false, true, false, false },
  new List<bool>() { false, false, true, false, false },
  new List<bool>() { false, false, true, false, false },
  new List<bool>() { false, true,  true, true,  false }
});

Я знаю, что мог, вероятно, оптимизировать эту часть при помощи многомерного массива вместо этого, но давайте проигнорируем, что на данный момент, это в иллюстративных целях. Каждая буква является точно теми же размерами, 10 пкс на 11 пкс (10 пкс на 11 пкс является фактическими размерами символа в моей реальной программе. Я упростил это до 5 пкс на 5 пкс в этой регистрации, так как намного легче "потянуть" буквы с помощью 0 и 1's на меньшем изображении).

Теперь, когда я даю ему часть 10 пкс на 11 пкс изображения для анализа с OCR, это должно было бы работать на каждой букве (26) на каждом пикселе (10 * 11 = 110), который будет означать 2,860 (26 * 110) повторения (в худшем случае) для каждого символа.

Я думал, что это могло быть оптимизировано путем определения уникальных характеристик каждого символа. Так, например, давайте предположим, что набор символов только состоит из 5 отличных букв: Я, A, O, B, и L. Они могли бы быть похожими на следующее:

01110  00100  00100  01100  01000
00100  01010  01010  01010  01000
00100  01110  01010  01100  01000
00100  01010  01010  01010  01000
01110  01010  00100  01100  01110

После анализа уникальных характеристик каждого символа я могу значительно сократить количество тестов, которые должны быть выполнены для тестирования на символ. Например, для символа "I", я мог определить, это - уникальные характеристики как наличие черного пикселя в координате (3, 0), так как никакие другие символы не имеют тот пиксель как черный. Таким образом вместо того, чтобы тестировать 110 пикселей на соответствие на символе "I", я уменьшил его до теста на 1 пиксель.

Это - то, на что это могло бы быть похожим для всех этих символов:

var LetterI = new OcrLetter() {
  Name = "I",
  BlackPixels = new List<Point>() { new Point (3, 0) }
}
var LetterA = new OcrLetter() {
  Name = "A",
  WhitePixels = new List<Point>() { new Point(2, 4) }
}
var LetterO = new OcrLetter() {
  Name = "O",
  BlackPixels = new List<Point>() { new Point(3, 2) },
  WhitePixels = new List<Point>() { new Point(2, 2) }
}
var LetterB = new OcrLetter() {
  Name = "B",
  BlackPixels = new List<Point>() { new Point(3, 1) },
  WhitePixels = new List<Point>() { new Point(3, 2) }
}
var LetterL = new OcrLetter() {
  Name = "L",
  BlackPixels = new List<Point>() { new Point(1, 1), new Point(3, 4) },
  WhitePixels = new List<Point>() { new Point(2, 2) }
}

Это сложно, чтобы сделать вручную для 5 символов и становится намного более твердым большее сумма букв, которые добавляются. Вы также хотите гарантировать, что у Вас есть минимальный набор уникальных характеристик буквы, так как Вы хотите, чтобы он был оптимизирован как можно больше.

Я хочу создать алгоритм, который определит уникальные характеристики всех букв и сгенерировал бы подобный код к этому выше. Я затем использовал бы эту оптимизированную черную/белую матрицу для идентификации символов.

Как я беру 26 букв, которые имеют все их черные/белые заполненные пиксели (например, блок кода CreateLetter) и преобразовывают их в оптимизированный набор уникальных характеристик, которые определяют букву (например, новый OcrLetter () блок кода)? И как я гарантировал бы, что это - самый эффективный набор определения уникальных характеристик (например, вместо того, чтобы определить 6 точек как уникальные характеристики, мог бы быть способ сделать это с 1 или 2 точками как буква, "я" в моем примере смог к).


Альтернативное решение, которое я предложил, использует хеш-таблицу, которая уменьшит его с 2 860 повторений до 110 повторений, в 26 раз сокращения. Это - то, как это могло бы работать:

Я заполнил бы его с данными, подобными следующему:

Letters["01110 00100 00100 00100 01110"] = "I";
Letters["00100 01010 01110 01010 01010"] = "A";
Letters["00100 01010 01010 01010 00100"] = "O";
Letters["01100 01010 01100 01010 01100"] = "B";

Теперь, когда я добираюсь до местоположения в изображении для обработки, я преобразовываю его в строку, такую как: "01110 00100 00100 00100 01110" и просто находят его в хеш-таблице. Это решение кажется очень простым, однако, это все еще требует, чтобы 110 повторений генерировали эту строку для каждой буквы.

В большой нотации O алгоритм является тем же с тех пор O (110 Н) = O (2860 Н) = O (N), чтобы буквы N обработали на странице. Однако это все еще улучшено постоянным множителем 26, существенное улучшение (например, вместо него занимающий 26 минут, потребовалась бы 1 минута).


Обновление: большинство решений, предоставленных до сих пор, не решило проблему идентификации уникальных характеристик символа и скорее предоставляет альтернативные решения. Я все еще ищу это решение, которое, насколько я могу сказать, является единственным способом достигнуть самой быстрой обработки OCR.

Я просто предложил частичное решение:

Для каждого пикселя, в сетке, хранят буквы, которые имеют его как черный пиксель.

Используя эти буквы:

  I      A      O      B      L
01110  00100  00100  01100  01000
00100  01010  01010  01010  01000
00100  01110  01010  01100  01000
00100  01010  01010  01010  01000
01110  01010  00100  01100  01110

У Вас было бы что-то вроде этого:

CreatePixel(new Point(0, 0), new List<Char>() {                         });
CreatePixel(new Point(1, 0), new List<Char>() { 'I',           'B', 'L' });
CreatePixel(new Point(2, 0), new List<Char>() { 'I', 'A', 'O', 'B'      });
CreatePixel(new Point(3, 0), new List<Char>() { 'I'                     });
CreatePixel(new Point(4, 0), new List<Char>() {                         });
CreatePixel(new Point(0, 1), new List<Char>() {                         });
CreatePixel(new Point(1, 1), new List<Char>() {      'A',      'B', 'L' });
CreatePixel(new Point(2, 1), new List<Char>() { 'I'                     });
CreatePixel(new Point(3, 1), new List<Char>() {      'A', 'O', 'B'      });
// ...
CreatePixel(new Point(2, 2), new List<Char>() { 'I', 'A',      'B'      });
CreatePixel(new Point(3, 2), new List<Char>() {      'A', 'O'           });
// ...
CreatePixel(new Point(2, 4), new List<Char>() { 'I',      'O', 'B', 'L' });
CreatePixel(new Point(3, 4), new List<Char>() { 'I', 'A',           'L' });
CreatePixel(new Point(4, 4), new List<Char>() {                         });

Теперь для каждой буквы, для нахождения уникальных характеристик, необходимо посмотреть, какие блоки она принадлежит, а также количество других символов в блоке. Поэтому давайте возьмем пример "I". Мы переходим ко всем блокам, которым это принадлежит (1,0; 2,0; 3,0;...; 3,4), и видят, что тот с наименьшим количеством количества других символов (3,0). На самом деле это только имеет 1 символ, означая, что это должен быть "I" в этом случае, и мы нашли нашу уникальную характеристику.

Можно также сделать то же для пикселей, которые были бы белыми. Заметьте, что блок (2,0) содержит все буквы за исключением "L", это означает, что он мог использоваться в качестве белого пиксельного теста. Точно так же (2,4) не содержит 'A'.

Блоки, которые или содержат все буквы или ни одну из букв, могут быть сразу отброшены, так как эти пиксели не могут помочь определить уникальную характеристику (например, 1,1; 4,0; 0,1; 4,4).

Это становится более хитрым, когда у Вас нет теста на 1 пиксель для буквы, например, в случае 'O' и 'B'. Давайте идти через тест для 'O'...

Это содержится в следующих блоках:

// Bucket   Count   Letters
// 2,0      4       I, A, O, B
// 3,1      3          A, O, B
// 3,2      2          A, O
// 2,4      4       I,    O, B, L

Кроме того, у нас также есть несколько белых пиксельных тестов, которые могут помочь: (Я только перечислил тех, которые отсутствуют самое большее 2). Недостающее количество было вычислено как (5 - Блок. Количество).

// Bucket   Missing Count   Missing Letters
// 1,0      2                  A, O
// 1,1      2               I,    O
// 2,2      2                     O,    L
// 3,4      2                     O, B

Таким образом, теперь мы можем взять самый короткий черный пиксельный блок (3,2) и видеть, что, когда мы тестируем на (3,2), мы знаем, что это или или 'O'. Таким образом, нам нужен простой способ сказать различие между и 'O'. Мы могли или искать черный пиксельный блок, который содержит 'O', но не (например, 2,4) или белый пиксельный блок, который содержит 'O', но не (например, 1,1). Любой из них мог использоваться в сочетании с (3,2) пиксель для однозначного определения буквы 'O' только с 2 тестами.

Это походит на простой алгоритм, когда существует 5 символов, но как я сделал бы это, когда существует 26 букв и намного больше пиксельного наложения? Например, скажем, это после (3,2) пиксельный тест, это нашло 10 различных символов, которые содержат пиксель (и это было наименьшим от всех блоков). Теперь я должен найти различия от 9 других символов вместо только 1 другого символа. Как я достиг бы своей цели получения наименьшего количества объема проверок как возможной, и удостоверился бы, что не запускаю посторонние тесты?

8
задан Senseful 12 February 2010 в 09:38
поделиться

7 ответов

У меня нет ответа, но вот некоторые ограничения вашего возможного решения:

Если вы хотите прямо вверх «использовать X пикселей в качестве ключа», тогда вам понадобится как минимум потолок ( log2 (количество символов)) пикселей. Вы не сможете устранить неоднозначность букв с меньшим количеством бит. В вашем случае попытка найти 5 пикселей эквивалентна поиску 5 пикселей, которые разделяют буквы на независимые части. Наверное, это не так просто.

Вы также можете использовать предложение Морона (хе-хе) и построить дерево на основе частотности букв языка, который вы сканируете, аналогично кодированию Хаффмана . Это займет больше места, чем 5 бит на букву, но, вероятно, будет меньше, если предположить степенное распределение использования букв. Я бы пошел с этим подходом, поскольку он позволяет вам искать определенный раздел для каждого узла, а не искать набор разделов.

4
ответ дан 5 December 2019 в 15:23
поделиться

Вы можете создать дерево.

Выберите пиксель и разделите буквы на два сегмента в зависимости от того, является ли пиксель белым или черным. Затем выберите второй пиксель, разделите сегменты на два сегмента, каждое на основе этого пикселя, и так далее.

Вы можете попытаться оптимизировать глубину дерева, выбирая пиксели, которые дают сегменты примерно одинакового размера.

Создание дерева - это однократный этап предварительной обработки. Вам не придется делать это несколько раз.

Теперь, когда у вас есть соответствующий алфавит, проследуйте по дереву на основе установленных / не установленных пикселей и получите свою букву.

4
ответ дан 5 December 2019 в 15:23
поделиться

Почему бы просто не рассматривать изображение как 25-битное целое число? 32-битное int может работать. Например, букву «I» можно рассматривать как целое число 14815374 в десятичной системе, поскольку ее двоичное выражение равно 0111000100001000010001110. Вам будет удобно сравнивать два изображения с операцией «==» как два целых числа.

1
ответ дан 5 December 2019 в 15:23
поделиться

Один из способов - определить черный пиксель примерно в половине букв и белый в другом наборе. Затем это можно использовать для разделения букв на две группы, рекурсивно используя один и тот же алгоритм на обеих половинах, пока вы не дойдете до отдельных символов.

Если вы не можете найти один пиксель, который разделяет наборы на два, возможно, вам придется перейти к группе из двух или более пикселей, но, надеюсь, использования отдельных пикселей будет достаточно.

Чтобы найти пиксель, начните с массива целых чисел того же размера, что и ваши буквы, инициализируйте все элементы до 0, затем увеличивайте элементы, если соответствующий пиксель в букве (скажем) черный. Те, которые вас интересуют, находятся в диапазоне (примерно) 10≤sum≤16 (для верхнего уровня, нижние уровни должны будут использовать другие границы).

1
ответ дан 5 December 2019 в 15:23
поделиться

Вот что у меня есть. Это не протестировано, но, надеюсь, будет менее гениально для вас работать с.: -)

double
roundd(double n, short mode)
{
    short cw, newcw;

    __asm__("fstcw %w0" : "=m" (cw));
    newcw = cw & 0xf3ff | mode;
    __asm__("fldcw %w0" : : "m" (newcw));
    __asm__("frndint" : "+t" (n));
    __asm__("fldcw %w0" : : "m" (cw));
    return n;
}

Хотя, если вы не обязаны использовать сборку для достижения вашего режима округления, подумайте об использовании функций в < fenv.h > вместо этого.: -)

-121--5044410-

Если вы используете ветвь с именем heroku в качестве «альтернативной главной» ветви (с конфиденциальными данными) и старую главную ветвь без конфиденциальных данных, то вы всегда можете сделать

git merge master

Так что вы можете протолкнуть ветвь heroku в heroku, а не главную ветвь.

-121--2414480-

У меня нет алгоритма, чтобы дать вам ключевые функции, но вот некоторые вещи, которые могут помочь.

Во-первых, я бы не стал слишком беспокоиться о поиске характерного пикселя для каждого символа, потому что, в среднем, проверка соответствия данного символа заданному образу (5x5) двоичного изображения не должна занимать больше 5-7 проверок, чтобы сказать, что нет совпадения. Почему? Вероятность. Для 7 двоичных пикселей существует 2 * * 7 = 128 различных возможностей. Это означает, что вероятность совпадения символа до 7 пикселей составляет 1/128 < 1%. Просто убедитесь, что вы остановите сравнения, когда обнаружите несоответствие.

Во-вторых, если вы не хотите делать хэш-таблицу, вы можете использовать trie для хранения всех ваших символьных данных. Он будет использовать меньше памяти, и вы будете проверять все символы одновременно. Поиск не будет таким быстрым, как в хэш-таблице, но также не придется преобразовывать в последовательность. В каждом узле дерева может быть не более 2 потомков. Например, если у вас есть два символа 2x2 (назовем их A и B):

A   B
01  00
10  11

У вас будет только один потомок в первом узле - только слева (ветвь 0). Переходим к следующему узлу. Он имеет два потомка, левая (0) ветвь ведёт к остальной части B, а правая (1) ветвь ведёт к остальной части A. Вы получаете картину. Дайте мне знать, если эта часть не ясна.

1
ответ дан 5 December 2019 в 15:23
поделиться

Хорошо, я нашел решение.

Вы просто используете поиск в глубину для каждого пикселя с каждой другой комбинацией пикселей, пока не найдете набор уникальных характеристик буквы.При выполнении поиска в глубину убедитесь, что вы не начинаете каждый раз с x = 0 и y = 0, поскольку вы хотите обрабатывать каждую комбинацию только один раз, поэтому в конечном итоге вы увеличиваете значения x и y в каждом итерация.

Я создал вспомогательный объект, который содержит следующие свойства:

public Point LastPoint { get; set; }
public List<OcrChar> CharsWithSimilarProperties { get; set; }
public List<Point> BlackPixels { get; set; }
public List<Point> WhitePixels { get; set; }

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

Некоторый псевдо-код:

rootNode.LastPoint = new Point(-1, -1)
rootNode.CharsWithSimilarProperties = all letters in alphabet except for this one
queue.Add(rootNode)

while queue.HasNodes()
  for each pixel after node.LastPoint
    if node.IsBlackPixel(pixel) && node.CharsWithSimilarProperties.IsAlwaysWhite(pixel)
      node.BlackPixels.Add(pixel)
      return node.BlackPixels and node.WhitePixels

    if node.IsWhitePixel(pixel) && node.CharsWithSimilarProperties.IsAlwaysBlack(pixel)
      node.WhitePixels.Add(pixel)
      return node.BlackPixels and node.WhitePixels

    newNode = new Node();
    newNode.BlackPixels = node.BlackPixels.Copy();
    newNode.WhitePixels = node.WhitePixels.Copy();
    newNode.LastPoint = pixel
    if node.IsBlackPixel(pixel)
      newNode.BlackPixels.Add(pixel)
      newNode.CharsWithSimilarProperties = list of chars from node.CharsWithSimilarProperties that also had this pixel as black
    else
      newNode.WhitePixels.Add(pixel)
      newNode.CharsWithSimilarProperties = list of chars from node.CharsWithSimilarProperties that also had this pixel as white
    queue.Add(newNode)

Чтобы определить, является ли «node.CharsWithSimilarProperites.IsAlwaysWhite ()» или «IsAlwaysBlack ()», вы можете сгенерировать композитную карту в каждой итерации очереди:

  for each pixel after node.LastPoint
    for each char in node.CharsWithSimilarProperties
      if char.IsBlackPixel(pixel)
        compositeMap[pixel].Add(char)

Прежде чем делать все это, я также обработал весь алфавит, чтобы найти пиксели, которые всегда белые или всегда черные, поскольку их нельзя использовать. Я добавил их в список List ignoredPixels , и каждый раз, когда я перебираю пиксели, я всегда использую if (ignoredPixels [x, y]) continue; .

Это работает отлично и очень быстро. Хотя имейте в виду, что эта часть моего решения совсем не обязательно должна быть быстрой, поскольку это разовая оптимизация, которая поможет мне в дальнейшем. В моих тестовых случаях максимум 8 символов в наборе «алфавит» обычно дает одну или две характеристики для каждого символа. Мне еще предстоит запустить его для полного набора из 26 символов.

0
ответ дан 5 December 2019 в 15:23
поделиться

Я иду по похожему пути, пытаясь изобрести алгоритм, который даст мне минимальное количество тестов, которые я могу использовать для сопоставления изображения с тем, которое я видел ранее. Моя задача - OCR, но в ограниченной области - распознать изображение из фиксированного набора изображений как можно быстрее.

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

Если мы не можем найти один такой пиксель, тогда мы (нехотя) ищем два пикселя, которые в комбинации являются уникальными. Или три. И так далее, пока у нас не будет минимального теста для каждого из изображений.

Я должен отметить, что у меня есть сильное чувство, что в моей конкретной области я смогу найти такие уникальные пиксели. Это может быть не так для вашего приложения, где у вас, похоже, много "перекрытий".

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

Вот что у меня получилось на данный момент. Метод, который я описываю ниже, написан абстрактно, но в моем приложении каждый "тест" - это пиксель, идентифицированный точкой плюс цвет, а "результат" представляет собой идентификацию изображения. Идентификация этих изображений является моей конечной целью.

Рассмотрим следующие тесты, пронумерованные от T1 до T4.

  • T1: A B C
  • T2: B
  • T3: A C D
  • T4: A D

Этот список тестов можно интерпретировать следующим образом;

  • Если тест T1 истинен, мы заключаем, что имеем результат A или B или C.
  • Если тест T2 истинен, мы заключаем, что имеем результат B.
  • Если тест T3 истинен, мы заключаем, что имеем результат A или C или D.
  • Если тест T4 истинен, мы заключаем, что имеем результат A или D.

Для каждого отдельного результата A, B, C, D мы хотим найти комбинацию тестов (в идеале всего один тест), которая позволит нам проверить однозначный результат.

Применив интуицию и немного прищурившись на экран, мы можем с трудом найти следующую комбинацию тестов.

Для A мы можем проверить комбинацию T4 (либо A, либо D) и T1 (A, но не D)

B - легко, поскольку существует тест T2, который дает результат B и ничего больше.

C немного сложнее, но в итоге мы видим, что комбинация T3 (A или C или D) и NOT T4 (не A и не D) дает желаемый результат.

И аналогично, D можно найти с помощью комбинации T4 и (не T1).

Итого

A <- T4 && T1
B <- T2
C <- T3 && ¬T4
D <- T4 && ¬T1

(где <- следует читать как "может быть найдено, если следующие тесты оцениваются как true")

Интуиция и прищуривание - это хорошо, но мы, вероятно, не получим эти техники встроенными в язык по крайней мере до C# 5.0, так что вот попытка формализации метода для реализации в менее распространенных языках.

Чтобы найти результат R,

  1. Найдите тест Tr, который дает желаемый результат R и как можно меньше нежелательных результатов (в идеале никаких других)
  2. Если тест дает результат R и ничего больше, то мы закончили. Мы можем подобрать R, где Tr истинно.
  3. Для каждого нежелательного результата X в тесте Tr;
    • (a) Найдите кратчайший тест Tn, который дает R, но не X. Если мы найдем такой тест, то сможем найти R, где (T && Tn)
    • (b) Если ни один тест не соответствует условию (a), то найдите самый короткий тест Tx, который включает X, но не включает R. (Такая проверка исключит X как результат проверки Tr). Затем мы можем проверить R, где (T && ¬Tx)

Теперь я попытаюсь следовать этим правилам для каждого из желаемых результатов, A, B, C, D.

Вот тесты еще раз для справки;

  • T1: A B C
  • T2: B
  • T3: A C D
  • T4: A D

For A

Согласно правилу (1) мы начинаем с T4, так как это самый простой тест, который дает результат A. Но он также дает результат 'D', который является нежелательным результатом. Согласно правилу (3), мы можем использовать тест T1, поскольку он включает в себя 'A', но не включает 'D'.

Поэтому мы можем проверить A с помощью

A <- T4 && T1

Для B

Чтобы найти B, мы быстро находим тест T2, который является самым коротким тестом для B, и поскольку он дает только результат B, мы закончили.

B <- T2

Для C

Чтобы найти 'C', мы начинаем с T1 и T3. Поскольку результаты этих тестов одинаково коротки, мы произвольно выбираем T1 в качестве начальной точки.

Теперь, согласно (3a), нам нужно найти тест, который включает 'C', но не 'A'. Поскольку ни один тест не удовлетворяет этому условию, мы не можем использовать T1 в качестве первого теста. У T3 та же проблема.

Не найдя теста, удовлетворяющего условию (3a), мы теперь ищем тест, удовлетворяющий условию (3b). Мы ищем тест, который дает 'A', но не 'C'. Мы видим, что тест T4 удовлетворяет этому условию, поэтому мы можем проверить C с помощью

C <- T1 && ¬T4

Для D

Чтобы найти D, мы начнем с T4. T4 включает нежелательный результат A. Нет других тестов, которые дают результат D, но не A, поэтому мы ищем тест, который дает A, но не D. Тест T1 удовлетворяет этому условию, поэтому мы можем проверить D с помощью

D <= T4 && ¬T1

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

Обновление

Я обнаружил, что оптимально одновременно искать тесты, которые удовлетворяют (a) ИЛИ (b), а не искать (a), а затем (b). Если сначала искать (a), то можно получить длинный список тестов, а можно было бы получить более короткий список, допустив некоторые тесты (b).

0
ответ дан 5 December 2019 в 15:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: