Оптическое распознавание символов
Процессы распознавания символов. Шаблонные и структурные алгоритмы распознавания. Процесс обработки поступающего документа. Обзор существующих приложений по оптическому распознаванию символов. Определение фиксированного шага и сегментация слов.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | дипломная работа |
Язык | русский |
Дата добавления | 11.02.2017 |
Размер файла | 3,3 M |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
3. Затем изображение сравнивается с пороговой поверхностью и все пиксели с интенсивностью большей пороговой, делаются белыми(255), а все интенсивности меньшие пороговой делаются черными(0)
4. Для данного алгоритма единственный входной параметр - размер окна.
Входной параметр - размер окна:
1. Экспериментально было установлено, что лучшие результаты распознавания достигаются с размером окна равным половине высоты текста.
2. Таким образом, алгоритм способен обрабатывать образцы с различным размером текста
3. Но требуется система оценивающая высоту текста
2.5.3 Оценка высоты текста
1. Для каждой ячейки на изображении в «оттенках серого» вычисляются интенсивности. Это позволяет получить массив дисперсий, у которых длина равна высоте изображения.
2. Области дисперсии, у которых расхождение больше порога «Tv» - рассматриваются как текст.(«Tv» - среднее значение всего набора дисперсии)
3. Медианная ширина зон текста большая «Tv» - высота текста.
2.5.4 Удаление линий (через анализ области вокруг текста)
Шаги алгоритма:
1. Локализация связанных областей текста в бинаризованном изображении.
2. Обнаружение линий на изображении без текста.
3. Удаление линий из оригинального изображения.
Связанные области текста:
1. Высота текста нам известна(h)
2. Бинаризованное изображение расширяется с помощью следующего ядра:
3. Изображение обрабатывается нормализованным hxh ядром:
4. Результирующая обработка ограничена порогом в 0.25
5. Полученные блобы (Blob - регион цифрового изображения, обладающий постоянными свойствами) вероятнее всего были получены из текста, т.о связанные регионы теста найдены.
Обнаружение линий
1. Удаление всех зон текста (все, что находится внутри связанных регионов)
2. Применяем трансформацию Хафа чтобы найти горизонтальные линии (Линии в большинстве случаев «сломаны» и обладают не одинаковой толщиной, поэтому трансформация Хафа применяется с порогом равным половине высоты, и максимальном расстоянию между линиями равному половине ширины)
3. Предыдущий шаг найдет список позиций горизонтальных линий
Удаление линий
1. Используем позиции горизонтальных линий и извлекаем пиксели из оригинального изображения, которые принадлежат линиям, но не к текстовым символам.
2. Предполагаем, что толщина линий не постоянна, но не больше чем h/10.
3. Для каждой возможной толщины, и каждой координаты на линии, накладываем на оригинальное изображение маску и отмечаем все пиксели ,которые совпадают с маской.
4. Создаем отклонение, двигая маску вверх-вниз со стартовой позиции. Данное перемещение не должно быть больше толщины линии.
Используемая маска:
,
где «d» - это толщина линии.
5. Нули добавлены, чтобы убедиться, что маска не накладывается на текст.
6. Если при наложении маски, найдены пиксели подходящие ей, то он маркируются для удаления.
Входное изображение должно содержать текст, с окружающей его зоной, достаточно большой, чтобы иметь фоновые линии после того, как текстовые зоны будут удалены. Линии могут быть под углом, пока возможно их обнаружение по кусочкам. При обработке, текст не будет потерян. Иногда небольшие участки текста остаются, но они могут быть удалены в последующих шагах обработки.
2.6 Обученные данные
Обучающая выборка состоит из стандартного пакета русского языка для tesseract. Результаты обработки с ее помощью были не идеальны, поэтому были улучшены путем внесения изменений в файлы обучающей выборки.
2.7 Результат работы программы
Рис. 27 Результат работы программы
На данный момент, система демонстрирует 94-95% точность распознавания.
2.8 Описание графического интерфейса пользователя
Разработанное приложение обладает простым интуитивно понятным интерфейсом. При запуске приложения пользователь видит основное окно (рис. 28)
Рис. 28 Стартовый экран приложения
Для того, чтобы система начала свою работу необходимо нажать кнопку «Open» и выбрать изображение паспорта, затем нажатием кнопки «Recognize» запускается процесс распознавания данных с изображения паспорта. Результат работы программы будет выведен в соответствующие графы под изображением (Рис. 29.)
Рис. 29 Результат работы программы
2.9 Тестирование функции распознавания паспортных данных
Исходными данными для тестирования являются сканированные изображения паспортов и файлы с проверочными данными для каждого изображения в определенном формате. На данный момент корректные данные заносятся в проверочные файлы вручную, в дальнейшем планируется разработать GUI для удобного редактирования данных.
Рис. 30. Проверочные данные для тестирования
2.9.1 Процесс тестирования
В процессе работы утилиты для каждого изображения формируется файл с распознанными данными в том же формате, что и проверочный файл. Далее проводится сравнение полученных и корректных данных используя Расстояние Левенштейна.
2.9.2 Результат тестирования
Результатом тестирования являются сгенерированные статистические файлы для каждого изображения, и сводный файл в формате html для визуализации результатов.
Рис. 31 Результат тестирования.
В целом проводилось тестирование на 50 различных паспортах, ошибка составила 5%. Данный процент ошибки - в основном результат применения различных шрифтов в разных регионах нашей страны, и не идеальность алгоритма удаления фона.
Заключение
распознавание символ документ оптический
В настоящие время качественные системы распознавания текста достаточно дороги, поэтому недостаточно широко распространены. Например, FineReader 12 показывает лучший результат. Он великолепно распознает как сканированные, так и сфотографированные изображения. Однако, минимальная его стоимость составляет 80€ за лицензию на 10000 распознаваний в год. Данная же система не имеет таких ограничений и может быть установлена, например, в бухгалтерии или отделе кадров любой компании, для автоматизации сбора и обновления данных. Наше OCR приложение на базе Tesseract позволяет с достаточно высокой степенью точности распознавать символы, при наличии различных шумов и помех, благодаря разработанному алгоритму предварительной обработки изображений и системой постобработки текста, основанной на словарном контроле результатов. Несомненным преимуществом нашей системы является возможность быстрой доработки под иной формат документов и интуитивно понятный интерфейс пользователя. Кроме того, разработанное решение было успешно внедрено в Территориальный фонд Обязательного медицинского страхования Нижегородской области, следовательно, имеет практическую значимость.
На данный момент у данного модуля существует один значительный недостаток касательно поддержки русского языка: данные недостаточно обучены, что не дает такого же высокоточного результата, как с международными языками, но со временем этот недостаток нивелируется.
В дальнейшем планируется разработка web-based прототипа приложения, расширение возможностей по распознаванию данных: поддержка других видов документов, борьба с шумами, более качественные метод удаления фона, видоизменение стандартного алгоритма определения базовой линии (используя openCV).
Список литературы
1. Богданов В., Ахметов К. Системы распознавания текстов в офисе. // Компьютер-пресс -- 1999 №3, с.40-42.
2. Павлидис Т. Алгоритмы машинной графики и обработки изображений. М:, Радиоисвязь, 1986
3. Shani U. Filling Regions in Binary Raster Images -- a Graph-theoretic Approach. // SIGGRAPH'80, pp 321-327.
4. Merrill R.D. Representation of Contours and Regions for Efficient Computer Search. // CACM, 16 (1973), pp. 69-82.
5. Pavlidis T. Filling Algorithms for Raster Graphics. // CGIP, 10 (1979), pp. 126141.
6. https://habrahabr.ru/post/112442/
7. Lieberman H. How to Color in a Coloring Book. // SIGGRAPH'78, Atlanta, Georgia, (August, 1978), pp. 111-116. PublishedbyACM.
8. Fuller, R. (2004). Fuzzy logic and neural nets in intelligent systems. In C. Carlsson (Ed.)Information Systems Day, pp. 74-94. Turku: Abo Academi University Press.
9. Melin P., Urias J., Solano D., Soto M., Lopez M., Castillo O., Voice Recognition with Neural Networks, Type-2 Fuzzy Logic and Genetic Algorithms. Engineering Letters, 13:2, 2006.
10. R.W. Smith, The Extraction and Recognition of Text from Multimedia Document Images, PhD Thesis, University of Bristol, November 1987.
11. R.W.Smith, “A Simple and Efficient Skew Detection Algorithm via Text Row Accumulation”, Proc. of the 3rd Int. Conf. on Document Analysis and Recognition (Vol. 2), IEEE 1995, pp. 1145-1148.
12. R.W. Smith, An Overview of the Tesseract OCR Engine, IEEE 2007 pp. 629 - 633
13. http://yann.lecun.com/exdb/mnist/
14. International Journal of Computer Vision 57(2), 137-154, 2004
15. Canny Edge Detection Tutorial by Bill Green, 2002.
16. Shapiro, L. G. & Stockman, G. C: "Computer Vision", page 137, 150. PrenticeHall, 2001
17. OpenCV Python tutorials(http://opencv-python-tutroals.readthedocs.org/)
18. Багрова И. А., Грицай А. А., Сорокин С. В., Пономарев С. А., Сытник Д. А. Выбор признаков для распознавания печатных кириллических символов // Вестник Тверского Государственного Университета 2010 г., 28, стр. 59-73
19. Journal of Signal and Information Processing, 2013, 4, 173-175
20. https://ru.wikipedia.org/wiki/Tesseract
21. https://en.wikipedia.org/wiki/Comparison_of_optical_character_recognition_software
22. Квасников В.П., Дзюбаненко А.В. Улучшение визуального качества цифрового изображения путем поэлементного преобразования // Авиационно-космическая техника и технология 2009 г., 8, стр. 200-204
23. https://habrahabr.ru/company/abbyy/blog/225215/
24. Mark S. Nixon and Alberto S. Aguado. Feature Extraction and Image Processing. -- Academic Press, 2008. -- С. 88.
25. Hsueh, M. (2011). Interactive text recognition and translation on a mobile device. Electrical Engineering and Computer Sciences, 57, 47-60.
26. Gllavata, G. & Ewerth, R. (2003). A robust algorithm for text detection in images. New York: Halstead Press.
27. Bieniecki, W. & Grabowski, S. (2007). Image preprocessing for improving OCR accuracy. Columbia: Columbia University Press.
Приложение
1. Prototype.py
try:
import Image
except ImportError:
from PIL import Image
import safepytesseract
import cv2
from multiprocessing import Pool
import autorotate
import cleanbg
import postactions
import findboxes
def do_recognize(img, dbg=''):
""" save image and process with tesseract """
if dbg != '':
filename = dbg + '_tmp.png'
cv2.imwrite(filename, img)
img = Image.fromarray(img, 'RGB')
result = safepytesseract.image_to_string(img, lang='rus')
return result
def recognize(img, rot, (l1, t1, r1, b1), coeff, post=None, dbg='', shrinkboxfactor=0):
""" prepares picture for recognition, passes picture
to tesseract and does post actions:
img - image to process
(l, t, r, b) - box to look into
rot - rotation flag for the box above
(l1, t1, r1, b1) - box to ignore within (l, t, r, b)
dbg - debug flag
post - array of post actions
shrinkboxfactor - by how much factor of height will the box be shrinked from each side (left and right) after
background cleaning and before it is passed to OCR. Must be >=0
"""
# do rotation if needed
if rot:
img = cv2.transpose(img)
img = cv2.flip(img, 0)
img = cleanbg.clean_bg((img, coeff, True))
if shrinkboxfactor < 0:
raise ValueError("shrinkbox must be >=0")
shrinkpixels = int(img.shape[0] * shrinkboxfactor)
if shrinkpixels > 0:
img = img[:, shrinkpixels:-shrinkpixels, :]
# wash the box which is to be ignored
if l1 != 0 or t1 != 0 or r1 != 0 or b1 != 0:
for rr in range(t1, b1):
for pp in range(l1, r1):
img[rr][pp] = [255, 255, 255]
result = do_recognize(img, dbg + str(coeff) if dbg != '' else '')
result = postactions.do_post_actions(result, post)
if dbg != '':
print result
return result
# noinspection PyArgumentList
def recognize_wrapper(args):
""" wrapper function to be passed
to the process pool """
return recognize(*args)
def do_box(img, (l, t, r, b), rot, (l1, t1, r1, b1), post=None, agressive=0, dbg='', expandboxfactor=0):
""" Does recognition for image within given box """
if dbg != '':
print '_do_box (l:%u t:%u r:%u b:%u rot:%u)' % (l, t, r, b, rot)
if rot != 0: # don't expand rotated images
expandboxfactor = 0
if expandboxfactor != 0:
l -= int(expandboxfactor * (b-t))
r += int(expandboxfactor * (b-t))
# crop the box
img = img[t:b, l:r]
if agressive:
def frange(x, y, jump):
while x < y:
yield x
x += jump
coeffs = frange(0.0, 2.0, (2.0 - 0.0) / agressive)
pool = Pool(4)
attempts = pool.map(recognize_wrapper,
[(img.copy(), rot, (l1, t1, r1, b1),
coeff, post, dbg, expandboxfactor) for coeff in coeffs])
result = postactions.do_sift(attempts, dbg)
else:
coeff = 1.0
result = recognize(img, rot, (l1, t1, r1, b1), coeff, post, dbg, expandboxfactor)
return result
# noinspection PyArgumentList
def do_box_wrapper(args):
""" wrapper function to be passed
to the process pool """
return do_box(*args)
boxes = (('surname', (0.44, 0.85, 0.54, 0.62), 0, (0, 0, 0, 0), [postactions.post_remove_newlines,
postactions.post_remove_spaces]),
('name', (0.51, 0.80, 0.62, 0.67), 0, (0, 0, 0, 0), [postactions.post_remove_newlines,
postactions.post_remove_spaces]),
('patronymic', (0.51, 0.80, 0.67, 0.71), 0, (0, 0, 0, 0), [postactions.post_remove_newlines,
postactions.post_remove_spaces]),
('series', (0.92, 0.98, 0.05, 0.25), 90, (0, 0, 0, 0), [postactions.post_remove_newlines,
postactions.post_remove_spaces]),
('number', (0.92, 0.98, 0.25, 0.48), 90, (0, 0, 0, 0), [postactions.post_remove_newlines,
postactions.post_remove_spaces]),
('whom', (0.10, 0.92, 0.09, 0.20), 0, (0.00, 0.16, 0.00, 0.22), [postactions.newlines_to_spaces,
postactions.post_remove_spaces_before_dots,
postactions.smart_replacement]),
('when', (0.18, 0.399, 0.20, 0.25), 0, (0, 0, 0, 0), [postactions.post_remove_newlines,
postactions.post_remove_spaces]),
('birthdate', (0.57, 0.90, 0.71, 0.75), 0, (0, 0, 0, 0), [postactions.post_remove_newlines,
postactions.post_remove_spaces,
postactions.commas_to_dots]),
('birthplace', (0.36, 0.90, 0.75, 0.86), 0, (0.00, 0.06, 0.00, 0.10), [postactions.newlines_to_spaces,
postactions.
post_remove_spaces_before_dots]),
('gender', (0.38, 0.49, 0.71, 0.75), 0, (0, 0, 0, 0), [postactions.post_remove_newlines,
postactions.post_remove_spaces,
postactions.remove_dots_and_commas]))
def process_image(filename, dbg=0, agressive=0, rotate=1, adaptive_bboxes=False, expandboxfactor=0):
""" innter1 function """
if dbg:
print filename
print 'agressive = ', agressive
# read and rotate the picture
img = cv2.imread(filename)
if rotate:
img = autorotate.auto_rotate((img, filename if dbg != 0 else ''))
rows, cols, _ = img.shape
if rows == 0 or cols == 0:
return ''
# print findboxes.get_boxes(img, dbg)
# print findboxes.get_boxes()
if adaptive_bboxes:
bboxes = findboxes.get_boxes(img, dbg)
result = ''
for name, rects, rot, ignore, post in bboxes:
# print rects
fieldres = name + ': '
for r in rects:
out = do_box(img, r, rot, ignore, post, expandboxfactor=expandboxfactor)
if name == 'series':
out = out[:4]
elif name == 'number':
out = out[-6:]
fieldres += out
fieldres += ' '
result += fieldres + '\n'
return result
else:
bboxes = findboxes.get_boxes()
if agressive:
# for agressive recognition process boxes one by one;
# do_box() starts its own process for every coeff;
# python doesn't allow to start child processes from childs
out = []
for name, (l, r, t, b), rot, (l1, r1, t1, b1), post in bboxes:
out.append(do_box(img,
(int(l * cols), int(t * rows), int(r * cols), int(b * rows)),
rot,
(int(l1 * r * cols), int(t1 * b * rows), int(r1 * r * cols), int(b1 * b * rows)),
post=post,
agressive=agressive,
dbg=filename + "_" + name if dbg != 0 else ''))
return out # [(result,weights),...]
else:
# for fast recognition start pool of workers and
# and recognize each box in its separate process
'''
# uncomment this and comment the code below for serial processing
out = ''
for name, (l, r, t, b), rot, (l1, r1, t1, b1), post in boxes:
out += name + ': ' + do_box(img.copy(),
(int(l * cols), int(t * rows), int(r * cols), int(b * rows)),
rot,
(int(l1 * r * cols), int(t1 * b * rows), int(r1 * r * cols),
int(b1 * b * rows)),
post,
0,
filename + "_" + name if dbg != 0 else '') + '\n'
return out
'''
pool = Pool(4)
out = pool.map(do_box_wrapper, [(img.copy(),
(int(l * cols), int(t * rows), int(r * cols), int(b * rows)),
rot,
(int(l1 * r * cols), int(t1 * b * rows), int(r1 * r * cols),
int(b1 * b * rows)),
post,
0,
filename + "_" + name if dbg != 0 else '')
for name, (l, r, t, b), rot, (l1, r1, t1, b1), post in bboxes])
names = [name for name, _, _, _, _ in boxes]
result = [name + ": " + out[names.index(name)] for name in names]
return reduce(lambda aa, bb: aa + '\n' + bb, result)
2. Cleanbg.py
from scipy.ndimage import measurements
import numpy as np
import cv2
BORDER_WIDTH = 10
BORDER_HEIGHT = 10
def clean_bg_v1(img, hist_max_ix, deviation, coeff):
""" cleans image bg by washing colors
inside [hist_max_ix +/- coeff*deviation] """
rows, cols, _ = img.shape
thres = [coeff * deviation[0], coeff * deviation[1], coeff * deviation[2]]
for row in range(0, rows):
for col in range(0, cols):
for channel in [0, 1, 2]:
if img[row][col][channel] > hist_max_ix[channel] - thres[channel]:
img[row][col][channel] = 255
# bitwise remaining colors
if img[row][col][0] != 255 or img[row][col][1] != 255 or img[row][col][2] != 255:
img[row][col] = [0, 0, 0]
def clean_bg_v2(img, coeff):
"""
# the algorithm breaks colors on the box on three areas:
# - upper area, which is to be cleaned up, this must be bg
# - middle area, area of interest which colors constructs characters
# - lowers area, area with colors below characters colors, nothing must be here in the begging
# upper and lower areas are to be cleaned up and middle area is to be stretched from 0 up to 225
# upper cutting threshold is fixed for the whole picture
# lower threeshold is floating from pixel to pixel
#
# the idea is that character pixels with overlights from watermarks should be scaled down to
# more lower value than usual pixels
"""
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rows, cols = img.shape
# statistics for overall box
avr = np.average(img)
std = np.std(img)
median = np.median(img)
# print 'average: ', avr
# print 'median: ', median
# print 'std: ', std
# statistics for character colors
# colors below 110 are considered related to the characters
# TODO: to try calculate 110 dynamically
blacks = []
for row in range(0, rows):
for col in range(0, cols):
if img[row][col] < 110:
blacks.append(img[row][col])
# blacks_avr = np.average(blacks)
blacks_median = np.median(blacks)
blacks_std = np.std(blacks)
# print 'black average: ', blacks_avr
# print 'black median: ', blacks_median
# print 'black std: ', blacks_std
# statistics for character colors
# colors higher 110 are considered related to the bg
# TODO: to try calculate 110 dynamically
bg = []
for row in range(0, rows):
for col in range(0, cols):
if img[row][col] > 110:
bg.append(img[row][col])
# bg_avr = np.average(bg)
# bg_median = np.median(bg)
# bg_std = np.std(bg)
# print 'bg average: ', bg_avr
# print 'bg median: ', bg_median
# print 'bg std: ', bg_std
# comparing meadian values for the whole box and for the characters only
# we can try to assume if the box contains watermarks.
# due to high watermaks colors, diff between above values are less on
# the pictures with watermaks compared to the ones without them.
# empirically detected values:
# - with watermaks: bg =~ 200, charaters =~ 100
# - without watermaks: bg =~ 220, charaters =~ 90
# TODO: to be verified on other samples
# watermark detection is needed to set propper upper threshold.
# the recognition result is very sensitive for the upper threshold.
# magic numbers 1.1 and 0.7 are also empirically settled and may not work on othen pictures.
# TODO: is it possible to figure out some common algorith which would set propper upper threshold?
delta = median - blacks_median
if delta > (100 + 130) / 2:
up_thresh = avr - 1.0 * coeff * std
else:
up_thresh = median - 0.7 * coeff * std
# print 'delta: ', delta
# print 'coeff: ', coeff
# print 'up_thresh: ', up_thresh
# print img.shape
img = cv2.copyMakeBorder(img, BORDER_HEIGHT, BORDER_HEIGHT, BORDER_WIDTH, BORDER_WIDTH, cv2.BORDER_REPLICATE)
rows, cols = img.shape
mask = np.zeros(img.shape, np.float32)
box_rows = 10
box_cols = 10
# the lower threshold is calculated dynamically for each pixel
# as min value for some locality of that pixel
for row in range(box_rows / 2, rows - box_rows / 2):
for col in range(box_cols / 2, cols - box_cols / 2):
mask[row][col] = np.min(img[row - box_rows / 2:row + box_rows / 2, col - box_cols / 2:col + box_cols / 2])
# scale up colors from 0...up_thresh to 0...255
for row in range(box_rows / 2, rows - box_rows / 2):
for col in range(box_cols / 2, cols - box_cols / 2):
if img[row][col] > up_thresh:
img[row][col] = 255
else:
img[row][col] = img[row][col] * 255 / up_thresh
# scale down colors from mask[row][col]...255 to 0...255
for row in range(box_rows / 2, rows - box_rows / 2):
for col in range(box_cols / 2, cols - box_cols / 2):
if img[row][col] < mask[row][col]:
img[row][col] = 0
elif mask[row][col] != 255:
img[row][col] = 255 - (255 - img[row][col]) * 255 / (255 - mask[row][col])
# cut box borders
img = img[BORDER_HEIGHT:rows-BORDER_HEIGHT, BORDER_WIDTH:cols-BORDER_WIDTH]
rows, cols = img.shape
# this magic stuff is needed to get rid of the noise happened to appear in the box.
# proper setting of upper threshold could help to clean that noise also, but
# but there is no common algorithm to calculate upper threshold for each box personally.
# condition to do cleaning: no watermarks and characters are fat enough
if delta > (100 + 130) / 2 and blacks_std > 6:
# print 'do noise clean'
kernel = np.ones((3, 3), np.float32) / 9
dst = cv2.filter2D(img, -1, kernel)
for row in range(0, rows):
for col in range(0, cols):
if dst[row][col] > 255 * (9 - 3) / 9:
img[row][col] = 255
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
return img
def get_char_dot_height(img):
""" calculates apprx charater height
and dot height on the image """
fimg = np.float32(img.copy())
dst = cv2.cornerHarris(fimg, 20, 31, 0.04)
dst = cv2.dilate(dst, None)
fimg = img.copy()
fimg[dst < 0.01*dst.max()] = 0
contours, hierarchy = cv2.findContours(fimg.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(contours, key=cv2.contourArea, reverse=True)
hsum = 0
hcount = 0
for c in cnts:
peri = cv2.arcLength(c, True)
if peri > 100:
x, y, w, h = cv2.boundingRect(c)
hsum += h
hcount += 1
if hcount != 0:
char_height = hsum/hcount/2
else:
char_height = 20
dot_height = char_height/3 - 2
# print char_height
# print dot_height
return char_height, dot_height
def clean_bg_v3(img, coeff, clean_noise):
"""
# the algorithm breaks colors on the box on three areas:
# - upper area, which is to be cleaned up, this must be bg
# - middle area, area of interest which colors constructs characters
# - lowers area, area with colors below characters colors, nothing must be here in the begging
# upper and lower areas are to be cleaned up and middle area is to be stretched from 0 up to 225
# upper cutting threshold is fixed for the whole picture
# lower threeshold is floating from pixel to pixel
#
# the idea is that character pixels with overlights from watermarks should be scaled down to
# more lower value than usual pixels
#
# diff against v2:
# - adaptive upper threshold
# - noise cleaning
"""
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rows, cols = img.shape
# calculate an apprx charater height and dot height on the orig image
# used later in noise filtering
if clean_noise:
char_height, dot_height = get_char_dot_height(img)
# calculate upper threshold by picking one row and analazing its colors:
# - filter image saving characters border
# - pick the most black line
# - colors on the line should be easily clasterized into bg color and char color
# - take upper threshold as charaters color minus one third from distance between
# bg color and char colors (one third is an empirical coeff)
# apply bilateral filter, TODO: kernel size to be calculated in runtime
sigmacolor = np.std(img.flatten())
bltf = cv2.bilateralFilter(img, 51, sigmacolor, 21)
# pick the most black line
sum_per_row = []
for rr in range(0, rows):
_sum_per_row = 0
for cc in range(0, cols):
_sum_per_row += (255 - bltf[rr][cc])
sum_per_row.append(_sum_per_row)
rix = sum_per_row.index(max(sum_per_row))
# print 'row ix: ', rix
# extract the line
# almost sure numpy can do this as well
rowx = []
for cc in range(0, cols):
rowx.append(bltf[rix][cc])
# clusterize color on that line
rowx = np.float32(rowx)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.1)
ret, labels, centers = cv2.kmeans(rowx, 2, criteria, 100, cv2.KMEANS_RANDOM_CENTERS)
# print ret, labels, centers
# determine if watermarks are present as it is done in v2 algo
median = np.median(img)
blacks = []
for row in range(0, rows):
for col in range(0, cols):
if img[row][col] < 110:
blacks.append(img[row][col])
blacks_median = np.median(blacks)
delta = median - blacks_median
whatermarks = (delta > (100 + 130) / 2)
if whatermarks:
up_thresh = max(centers) - (max(centers)-min(centers))/2.0*coeff
else:
up_thresh = max(centers) - (max(centers)-min(centers))/4.0*coeff
print coeff, up_thresh
# expand the box to be able to do 'convolution' properly
img = cv2.copyMakeBorder(img, BORDER_HEIGHT, BORDER_HEIGHT, BORDER_WIDTH, BORDER_WIDTH, cv2.BORDER_REPLICATE)
rows, cols = img.shape
# mask for the lower threshold
mask = np.zeros(img.shape, np.float32)
box_rows = 10
box_cols = 10
# the lower threshold is calculated dynamically for each pixel
# as min value for some locality of that pixel
for row in range(box_rows / 2, rows - box_rows / 2):
for col in range(box_cols / 2, cols - box_cols / 2):
mask[row][col] = np.min(img[row - box_rows / 2:row + box_rows / 2, col - box_cols / 2:col + box_cols / 2])
# scale up colors from 0...up_thresh to 0...255
for row in range(box_rows / 2, rows - box_rows / 2):
for col in range(box_cols / 2, cols - box_cols / 2):
if img[row][col] > up_thresh:
img[row][col] = 255
else:
img[row][col] = img[row][col] * 255 / up_thresh
# scale down colors from mask[row][col]...255 to 0...255
for row in range(box_rows / 2, rows - box_rows / 2):
for col in range(box_cols / 2, cols - box_cols / 2):
if img[row][col] < mask[row][col]:
img[row][col] = 0
elif mask[row][col] != 255:
img[row][col] = 255 - (255 - img[row][col]) * 255 / (255 - mask[row][col])
if clean_noise:
# fill the borders with the white, need later on
cv2.rectangle(img, (0, 0), (cols, BORDER_HEIGHT/2), (255, 255, 255), -1)
cv2.rectangle(img, (0, rows-BORDER_HEIGHT/2), (cols, rows), (255, 255, 255), -1)
cv2.rectangle(img, (0, 0), (BORDER_WIDTH/2, rows), (255, 255, 255), -1)
cv2.rectangle(img, (cols-BORDER_WIDTH/2, 0), (cols, rows), (255, 255, 255), -1)
# binarize into tmp image
tmp = img.copy()
for row in range(0, rows):
for col in range(0, cols):
if tmp[row][col] < 255:
tmp[row][col] = 0
# find contours on binarized image
# one of returned contours is the box itself, how to get rid of it?
contours, hierarchy = cv2.findContours(tmp.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
bratio = float(rows)/cols if rows < cols else float(cols)/rows
for cnt in contours[1:]:
x, y, w, h = cv2.boundingRect(cnt)
# loop over the contours and filter them by the condition below
cratio = float(w)/h if w < h else float(h)/w
if w < dot_height or h < dot_height or ((w > 1.5*char_height or h > 1.5*char_height) and cratio < 0.1) or \
((w < char_height or h < char_height) and cratio < bratio):
# -1 in the last param fills the contour with given color
cv2.drawContours(img, [cnt], -1, (255, 255, 255), -1)
# else:
# print cratio
# cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 1)
# cut box borders
img = img[BORDER_HEIGHT:rows-BORDER_HEIGHT, BORDER_WIDTH:cols-BORDER_WIDTH]
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
return img
# noinspection PyPep8Naming
def locally_adaptive_binarization(img, window_size):
"""
__author__ = 'Vladan Krstic'
Binarizes the image according to the algorithm briefly explained in
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.19.4933&rep=rep1&type=pdf
This algorithm is better than OpenCV's locally adaptive thresholding because it adopts a global minimum for
threshold. This will leave white areas in the image to be white (it will not calculate a threshold based on local
pixels 'at all cost' - half on one side and half of other side).
:param img: Image array to be binarized. Expecting 8bit gray image (vals = 0..255, ndims = 2)
:param window_size: Size of the window used to calculate locally adaptive binarization threshold. Must be odd!
:return: binarized image as np.uint8
"""
if window_size % 2 == 0 or window_size <= 0:
raise ValueError("window_size must be odd and positive")
k = (window_size - 1) / 2
img_expanded = cv2.copyMakeBorder(img, k, k, k, k, cv2.BORDER_REPLICATE)
rows, cols = img_expanded.shape
R = 0
M = np.min(img)
s = np.zeros(img.shape)
m = np.zeros(img.shape)
c = 0.2
for i in range(k, rows - k):
for j in range(k, cols - k):
w = img_expanded[i - k:i + k + 1, j - k:j + k + 1]
s[i - k, j - k] = np.std(w)
m[i - k, j - k] = np.mean(w)
if s[i - k, j - k] > R:
R = s[i - k, j - k] # global maximum of stdev
if R == 0:
raise ValueError("maximum stdev is zero - this shouldn't happen")
# binarization threshold:
T = m - c * (1 - s / R) * (m - M) # per-element matrix operations (avoids having another pair of nested for loops)
result = np.zeros(img.shape, dtype=np.uint8)
result[img > T] = 255
return result
def estimate_text_height(img, print_msg=False):
"""
__author__ = 'Vladan Krstic'
Estimates text height by looking at the pixel variance profile. The idea is to go row by row and observe pixel
variance. The rows with higher variance are assumed to be areas with text information. If multiple lines of text are
detected, median of their height is taken and returned as result. Areas smaller than 9px are ignored.
Input image should contain area bigger than the text who's height is to be determined (text shouldn't touch upper or
lower edge of the image).
:param img: Grayscale image of the text (dtype=uint8)
:param print_msg: Bool switch - Controls whether debug messages are printed to console.
:return: Estimated text height in pixels
"""
h_var = np.var(img, 1)
mh = np.mean(h_var)
if h_var[0] >= mh and print_msg:
print "Warning: text probably touching the edge of the image."
heights = []
a = 0
for i in range(1, h_var.size):
if h_var[i - 1] < mh <= h_var[i]:
a = i # top of the text
continue
if h_var[i - 1] >= mh > h_var[i]:
if i-a < 9:
# expect text to be at least 9px high
# todo: this is not universal solution, consider algorithms for outlier detection on heights list
continue
heights.append(i - a) # bottom of the text
if not heights:
if print_msg:
print "Warning: unable to determine text height"
# raise ValueError("Unable to determine text height.")
if a == 0: # appears like text height is equal to image height.
t_height = img.shape[0]
else: # or text is touching the lower edge of the image, take helf the image height as text height
t_height = img.shape[0] / 2
else:
t_height = int(np.median(heights)) # text line height
if print_msg:
print "Estimated text height:", t_height
return t_height
# noinspection PyPep8Naming,PyUnresolvedReferences
def remove_lines(img, textHeight):
"""
__author__ = 'Vladan Krstic'
Remove thin horizontal lines from binary image.
1. Take binary image, do the dilatation with a row-vector kernel to ensure that adjacent letters will be recognized
as one blob.
2. Take the convolution of the dilated image with a square kernel size = text height
3. Threshold the convolution result to get binary blobs. Find bounding boxes for those blobs. -> words bboxes
4. Remove the bboxes from the image and analyze the rezulting image to find the lines
5. Remove the lines found from the original binary image.
Input image should contain area wide enough to still have background lines after the text area is removed.
Recommended minimum width is two times text height on each (lef/right) side of the text.
:param img: binarized image, dtype=uint8
:param textHeight: height of text - used as basis for other parameters
:return: image with lines removed
"""
# image form suitable for operations
imgbw = 1 - img / 255.
# dilate with a row kernel
kernel = np.ones((1, textHeight / 2), np.float32)
blobs_tmp = cv2.dilate(imgbw, kernel)
# convolve with a box xernel
k = textHeight
kernel = np.ones((k, k), np.float32) / (k * k)
boxconv = cv2.filter2D(blobs_tmp, -1, kernel)
t = 0.25 # todo: experiment with this thresh (must be between 0 and 1)
blobs_tmp = boxconv > t
# find bounding boxes
contours, hierarchy = cv2.findContours(np.uint8(blobs_tmp), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
rects = []
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
rects.append([x, y, w, h])
# get the image without text
img_notext = imgbw.copy()
for rect in rects:
x, y, w, h = rect
for i in range(y, y + h):
for j in range(x, x + w):
img_notext[i, j] = 0
# find lines
img_lines = np.dstack((np.uint8(255 * img_notext), np.uint8(255 * img_notext), np.uint8(255 * img_notext)))
img_notext_8bit = np.uint8(img_notext * 255)
maxgap = img.shape[1] / 2
threshold = textHeight / 2
# maxgap = int(img.shape[1] * 0.95)
# threshold = int(textHeight * 0.1)
lines = cv2.HoughLinesP(img_notext_8bit, 1, np.pi / 2, threshold, maxLineGap=maxgap)
line_pos = set() # i want unique values
if lines is not None:
for x1, y1, x2, y2 in lines[0]:
cv2.line(img_lines, (x1, y1), (x2, y2), (0, 255, 0), 1)
if y1 == y2:
line_pos.add(y1)
# for each line found, do the filtering on the image with text
# during filtering, make variations of the column mask to accomodate varying thickness of the line
max_thickness = (textHeight + 5) / 10
imgbw_padded = np.pad(imgbw, ((max_thickness, max_thickness), (0, 0)), mode='constant')
deletemask = np.zeros(imgbw_padded.shape)
if line_pos:
for y in line_pos:
y += max_thickness # compensate for padding
for t in range(1, max_thickness+1):
col_mask = np.ones(t+2)
col_mask[0] = 0
col_mask[-1] = 0
for v in range(0, t):
# variations are formed by moving the column mask up/down by a number of pixels up
# to the thickness of the line.
rows = imgbw_padded[y-t+v:y+v+2, :]
mask_rows = deletemask[y-t+v:y+v+2, :] # creates a view of the rows of deletmask
for j in range(0, rows.shape[1]):
if all(rows[:, j] == col_mask):
mask_rows[:, j] += col_mask
deletemask = deletemask[max_thickness:-max_thickness] # remove padding from the mask
# remove line pixels from the original image
img_cleaned = img.copy()
img_cleaned[deletemask > 0] = 255
return img_cleaned
# noinspection PyUnusedLocal
def clean_bg_v4(img, a, b):
"""
__author__ = 'Vladan Krstic'
Algorithm steps:
1. Text height is estimated.
2. Image is locally adaptively binarized with regards to text height.
3. Background lines are removed.
The algorithm assumes that:
1. The text doesn't touch img edges.
2. Background lines are horizontal and extend beyond the width of the text bounding boxes.
Image should have at least 5px area above and below the text. Image should contain enough of the passport form
line left and/or right of the text so it can be detected when the text is removed.
Rule of thumb: image should contain area bigger than text bounding box by at least two heights of the text (more
is better, but not too much so it doesn't pick up too much background noise).
"""
grayimg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# blur background while keeping edges (bilateral filter)
sigmacolor = np.std(grayimg.flatten()) / 2
grayimg = cv2.bilateralFilter(grayimg, 5, sigmacolor, 5) # todo: calculate kernel size automatically
# determine text height
t_height = estimate_text_height(grayimg, True)
# locally adaptive thresholding
window_size = t_height / 2 # set window size relative to line height.
if window_size % 2 == 0: # must be odd
window_size += 1
bin_result = locally_adaptive_binarization(grayimg, window_size)
img_cleaned = remove_lines(bin_result, t_height)
# remove all blobs smaller than some value...
minpixels = t_height / 2
blobsies = img_cleaned
labeled_blobs, num_blobs = measurements.label(blobsies == 0, np.ones((3, 3)))
for i in range(1, num_blobs + 1):
if np.sum((labeled_blobs == i) * np.ones(labeled_blobs.shape)) < minpixels:
blobsies[labeled_blobs == i] = 255
# labeled_blobs[labeled_blobs == i] = 0
result = cv2.cvtColor(blobsies, cv2.COLOR_GRAY2RGB)
return result
# noinspection PyUnusedLocal
def clean_bg(arg):
return clean_bg_v3(*arg)
Размещено на Allbest.ru
Подобные документы
Оптическое распознавание символов как механический или электронный перевод изображений рукописного, машинописного или печатного текста в последовательность кодов. Компьютерные программы для оптического распознавания символов и их характеристика.
презентация [855,2 K], добавлен 20.12.2011Методы предобработки изображений текстовых символов. Статистические распределения точек. Интегральные преобразования и структурный анализ. Реализация алгоритма распознавания букв. Анализ алгоритмов оптического распознавания символов. Сравнение с эталоном.
курсовая работа [2,1 M], добавлен 20.09.2014Необходимость в системах распознавания символов. Виды сканеров и их характеристики. Оптимальное разрешение при сканировании. Программы распознавания текста. Получение электронного документа. FineReader - система оптического распознавания текстов.
презентация [469,2 K], добавлен 15.03.2015Обзор математических методов распознавания. Общая архитектура программы преобразования автомобильного номерного знака. Детальное описание алгоритмов: бинаризация изображения, удаление обрамления, сегментация символов и распознавание шаблонным методом.
курсовая работа [4,8 M], добавлен 22.06.2011Проектирование приложения на языке С# в среде Microsoft Visual Studio 2008: составление алгоритмов сегментации текста документа и распознавания слова "Указ" в нем, создание архитектуры и интерфейса программного обеспечения, описание разработанных классов.
курсовая работа [2,4 M], добавлен 05.01.2011Подсистема управления процессами и потоками вычислительной системы. Формирование новых символов для матричного принтера, разработка команд для загрузки символов в оперативную память принтера и программы, реализующей процесс печати заданных символов.
курсовая работа [201,1 K], добавлен 23.06.2011Ознакомление с приемами управления работой печатающих устройств в MS-DOS. Формирование новых символов для матричного принтера, разработка команд загрузки символов в оперативную память принтера и программы, реализующей процесс печати заданных символов.
курсовая работа [1,2 M], добавлен 22.06.2011Как работает система оптического распознавания. Деление текста на символы. Образ страницы и распознавание по шаблонам, особенности коррекции ошибок. Увеличение скорости бесклавиатурного ввода документов в технологиях электронного документооборота.
контрольная работа [15,6 K], добавлен 29.04.2011Этап предварительной обработки данных, классификации, принятия решения. Изображения обучающих рукописных символов, тестового символа. Выход нейронной сети для тестового символа. График тренировки нейронной сети. Последовательность точек. Входные вектора.
статья [245,7 K], добавлен 29.09.2008Оптико-электронная система идентификации объектов подвижного состава железнодорожного транспорта. Автоматический комплекс распознавания автомобильных номеров. Принципы и этапы работы систем оптического распознавания. Особенности реализации алгоритмов.
дипломная работа [887,3 K], добавлен 26.11.2013