Русский
Русский
English
Статистика
Реклама

Tesseract

Распознавание текста на картинке с помощью tesseract на Kotlin

11.09.2020 10:13:26 | Автор: admin

Ни для кого не секрет, что Python прочно занял первенство в ML и Data Science. А что если посмотреть на другие языки и платформы? Насколько в них удобно делать аналогичные решения?


К примеру, распознавание текста на картинке.


Среди текущих решений одним из наиболее распространённым инструментом является tesseract. В Python для него существует удобная библиотека, а для первоначальной обработки изображений, как правило, используется OpenCV. Для обоих этих инструментов есть исходные C++ библиотеки, поэтому их также возможно вызывать и из других экосистем. Попробуем это сделать в jvm и, в частности, на Kotlin.


Несколько слов о Kotlin. У него есть много удобных вещей для Data Science. В совокупности с экосистемой jvm получается статически типизированный Python на jvm. А не так давно ещё появилась возможность использовать Kotlin вместе с Apache Spark.

Первым делом установим tesseract. Его нужно установить отдельно на систему, согласно описанию из wiki. После установки можно проверить, что tesseract работает следующим образом:


tesseract input_file.jpg stdout -l eng --tessdata-dir /usr/local/share/tessdata/

Где --tessdata-dir путь до файлов tesseract (/usr/local/share/tessdata/ в macos). В случае успешной установки в stdout будет выведен распознанные текст.


После этого можно подключить tesseract в jvm и сравнить результат работы с нативным вызовом. Для этого подключим библиотеку:


implementation("net.sourceforge.tess4j:tess4j:4.5.3")

Для тех, кто не очень хорошо знаком с экосистемой jvm, есть лёгкий способ быстро себе всё настроить. Понадобится только установленная Java 13+. Её проще всего поставить через sdkman. Далее для удобства можно скачать Intellij IDEA, подойдёт и Community version. Основу проекта можно создать из IDE (new project -> Kotlin, gradle Kotlin) или можно клонировать репозиторий github, в котором перейти на ветку start.

После подключения библиотеки доступ к tesseract становится простым. Вызов той же команды из примера выше будет выглядеть следующим образом:


 val api = Tesseract() api.setDatapath("/usr/local/share/tessdata/") api.setLanguage("eng") val image = ImageIO.read(File("input_file.jpg")) val result: String = api.doOCR(image)

Как видно, практически все команды совпадают с используемыми в вызове из командной строки. Но, как минимум, на macos нужно ещё дополнительно настроить системную переменную jna.library.path, в которую нужно добавить путь до dylib-библиотеки tesseract.


val libPath = "/usr/local/lib"val libTess = File(libPath, "libtesseract.dylib")if (libTess.exists()) {    val jnaLibPath = System.getProperty("jna.library.path")    if (jnaLibPath == null) {        System.setProperty("jna.library.path", libPath)    } else {        System.setProperty("jna.library.path", libPath + File.pathSeparator + jnaLibPath)    }}

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


Перейдём теперь к обработке изображений с OpenCV. В Python для работы с ней не требуется ставить каких-либо дополнительных инструментов, кроме пакета в pip. В описании OpenCV под java указан порядок установки, когда всё ставится отдельно. Для самой jvm-экосистемы подход, когда требуются установки каких-либо нативных библиотек, не совсем привычен. Чаще всего если зависимости требуется какие-либо дополнительные библиотеки, то либо она сама их скачивает (как, например, djl-pytorch), либо при подключении через систему сборки внутри себя уже содержит библиотеки под различные операционные системы. К счастью, для OpenCV есть такая сборка, которой и воспользуемся:


implementation("org.openpnp:opencv:4.3.0-2")

Перед началом работы с OpenCV потребуется подгрузить нативные библиотеки через:


nu.pattern.OpenCV.loadLocally()

После чего можно использовать все доступные инструменты. Как, например, конвертация изображения в чёрно-белый цвет:


 Imgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2GRAY)

Как вы уже обратили внимание, аргументом для OpenCV выступает Mat, который представляет из себя основной класс-обёртку вокруг изображения в OpenCV в jvm, похожий на привычный BufferedImage.


Сам экземпляр Mat можно получить привычным для Python кода вызовом imread:


val mat = Imgcodecs.imread("input.jpg")

В таком виде экземпляр можно дальше передавать в OpenCV и проделывать с ним различные манипуляции. Но для Java общепринятым является BufferedImage, вокруг которого, как правило, уже может быть выстроен pipeline загрузки и обработки изображения. В связи с чем возникает необходимость конвертации BufferedImage в Mat:


val image: BufferedImage = ...val pixels = (image.raster.dataBuffer as DataBufferByte).dataval mat = Mat(image.height, image.width, CvType.CV_8UC3)            .apply { put(0, 0, pixels) }

И обратной конвертации Mat в BufferedImage:


val mat = ...var type = BufferedImage.TYPE_BYTE_GRAYif (mat.channels() > 1) {    type = BufferedImage.TYPE_3BYTE_BGR}val bufferSize = mat.channels() * mat.cols() * mat.rows()val b = ByteArray(bufferSize)mat[0, 0, b] // get all the pixelsval image = BufferedImage(mat.cols(), mat.rows(), type)val targetPixels = (image.raster.dataBuffer as DataBufferByte).dataSystem.arraycopy(b, 0, targetPixels, 0, b.size)

В частности, тот же tesseract в методе doOCR поддерживает как файл, так и BufferedImage. Используя вышеописанные преобразования, можно вначале обработать изображения с помощью OpenCV, преобразовать Mat в Bufferedimage и передать подготовленное изображение на вход tesseract.


Попробуем теперь на практике собрать рабочий вариант приложения, который сможет найти текст на следующей картинке:



Для начала проверим результат нахождения текста на изображении без обработки. И вместо метода doOCR будем использовать getWords, чтобы получить ещё confidence (score в Python-библиотеке) для каждого найденного слова:


val image = ImageIO.read(URL("http://img.ifcdn.com/images/b313c1f095336b6d681f75888f8932fc8a531eacd4bc436e4d4aeff7b599b600_1.jpg"))val result = api.getWords(preparedImage, ITessAPI.TessPageIteratorLevel.RIL_WORD)

В результате будет найден только разный мусор:


[ie, [Confidence: 2.014679 Bounding box: 100 0 13 14], bad [Confidence: 61.585358 Bounding box: 202 0 11 14], oy [Confidence: 24.619446 Bounding box: 21 68 18 22], ' [Confidence: 4.998787 Bounding box: 185 40 11 18], | [Confidence: 60.889648 Bounding box: 315 62 4 14], ae. [Confidence: 27.592728 Bounding box: 0 129 320 126], c [Confidence: 0.000000 Bounding box: 74 301 3 2], ai [Confidence: 24.988930 Bounding box: 133 283 41 11], ee [Confidence: 27.483231 Bounding box: 186 283 126 41]]

Если внимательней посмотреть на изображение, то можно увидеть, что шрифт для текста белый, а значит, можно попробовать использовать threshold вместе с последующей инверсией, чтобы оставить текст только на картинке:


Пробуем следующие преобразования:


// convert to grayImgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2GRAY)// text -> white, other -> blackImgproc.threshold(mat, mat, 244.0, 255.0, Imgproc.THRESH_BINARY)// inverse Core.bitwise_not(mat, mat)

После них посмотрим на картинку в результате (которую можно сохранить в файл через Imgcodecs.imwrite("output.jpg", mat) )



Теперь если посмотреть на результаты вызова getWords, то получим следующее:


[WHEN [Confidence: 94.933418 Bounding box: 48 251 52 14], SHE [Confidence: 95.249252 Bounding box: 109 251 34 15], CATCHES [Confidence: 95.973259 Bounding box: 151 251 80 15], YOU [Confidence: 96.446579 Bounding box: 238 251 33 15], CHEATING [Confidence: 96.458656 Bounding box: 117 278 86 15]]

Как видно, весь текст успешно распознался.


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


import net.sourceforge.tess4j.ITessAPIimport net.sourceforge.tess4j.Tesseractimport nu.pattern.OpenCVimport org.opencv.core.Coreimport org.opencv.core.CvTypeimport org.opencv.core.Matimport org.opencv.imgproc.Imgprocimport java.awt.image.BufferedImageimport java.awt.image.DataBufferByteimport java.io.Fileimport java.net.URLimport javax.imageio.ImageIOfun main() {    setupOpenCV()    setupTesseract()    val image = ImageIO.read(URL("http://img.ifcdn.com/images/b313c1f095336b6d681f75888f8932fc8a531eacd4bc436e4d4aeff7b599b600_1.jpg"))    val mat = image.toMat()    Imgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2GRAY)    Imgproc.threshold(mat, mat, 244.0, 255.0, Imgproc.THRESH_BINARY)    Core.bitwise_not(mat, mat)    val preparedImage = mat.toBufferedImage()    val api = Tesseract()    api.setDatapath("/usr/local/share/tessdata/")    api.setLanguage("eng")    val result = api.getWords(preparedImage, ITessAPI.TessPageIteratorLevel.RIL_WORD)    println(result)}private fun setupTesseract() {    val libPath = "/usr/local/lib"    val libTess = File(libPath, "libtesseract.dylib")    if (libTess.exists()) {        val jnaLibPath = System.getProperty("jna.library.path")        if (jnaLibPath == null) {            System.setProperty("jna.library.path", libPath)        } else {            System.setProperty("jna.library.path", libPath + File.pathSeparator + jnaLibPath)        }    }}private fun setupOpenCV() {    OpenCV.loadLocally()}private fun BufferedImage.toMat(): Mat {    val pixels = (raster.dataBuffer as DataBufferByte).data    return Mat(height, width, CvType.CV_8UC3)        .apply { put(0, 0, pixels) }}private fun Mat.toBufferedImage(): BufferedImage {    var type = BufferedImage.TYPE_BYTE_GRAY    if (channels() > 1) {        type = BufferedImage.TYPE_3BYTE_BGR    }    val bufferSize = channels() * cols() * rows()    val b = ByteArray(bufferSize)    this[0, 0, b] // get all the pixels    val image = BufferedImage(cols(), rows(), type)    val targetPixels = (image.raster.dataBuffer as DataBufferByte).data    System.arraycopy(b, 0, targetPixels, 0, b.size)    return image}

Если сравнить полученный код с Python-версией, то разница будет минимальная. Производительность тоже должна быть практически сравнимой (за исключением, быть может, чуть больших преобразований изображения между Mat и BufferedImage).


Преимущество Python в рамках текущего примера будет только в бесшовной передаче изображений между OpenCV и tesseract. Экосистема Python сама по себе удобна тем, что все библиотеки общаются одними и теми же типами.


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


Python на текущий момент, беcспорно, лидер в ML. И в первую очередь все инструменты и библиотеки появляются на нём. Тем не менее, в других экосистемах можно использовать те же инструменты. Особенно учитывая, что если что-то есть под Python, то должна быть и нативная библиотека, которую можно легко подключить.


Надеюсь, что в этой статье вы нашли для себя что-нибудь полезное и новое. Спасибо за внимание, и напоследок несколько полезных ссылок:


https://djl.ai/ Deep Learning на jvm, где можно подключать модели из pytorch и tensorflow
https://deeplearning4j.org/ аналогичное решение с возможностью обучать модели и импортировать существующие на tensorflow и keras
https://kotlinlang.org/docs/reference/data-science-overview.html разные полезные вещи по Data Science на Kotlin (и Java)


Весь код доступен в репозитории https://github.com/evgzakharov/kotlin_tesseract.

Подробнее..

Яндекс-капча vs tesseract

01.04.2021 16:06:37 | Автор: admin
Речь пойдет об относительно новом творении в области капча-производства, новой яндекс-капче. Поищем слабые места, пролезем в эти слабые места и осмотримся там. Также подумаем на тему помогает ли программа пакету распознавания текста на картинке Tesseract стать лучше.



Дано.


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

либо так (улыбка):

либо так(горка):

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

С чего начать.


Первичный анализ с помощью пакета opencv показал, что капча устойчива к таким методам как Erosion, Dilation, Harris_corners:




Также ничего не дает попытка вырезать пиксели с нужным цветом, так как в капчу добавлены шумы:


Что дальше.

.
Попробуем старый, добрый пакет tesseract, а за основу возьмем код из этой статьи.
В общем и целом в ней описывается как пакет tesseract распознает текст на изображении. На выходе программы выводится confidence и text. Грубо говоря, степень достоверности определенного текста и сам текст. Также программа рисует прямо на картинке, что она видит. Этот код нам очень поможет в дальнейшем.
Повыкидываем из него лишнее, например, рисование того, что было прочитано и т.п.
В обновленном виде он выглядит так:
код
# import the necessary packagesfrom pytesseract import Outputimport pytesseractimport argparseimport cv2# Путь для подключения tesseractpytesseract.pytesseract.tesseract_cmd = 'D:\\Tesseract-OCR\\tesseract.exe'image = cv2.imread('4-.jpg')rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)config = r'--oem 3 --psm 6'results = pytesseract.image_to_data(rgb, output_type=Output.DICT,config=config,lang='rus')# loop over each of the individual text localizationsfor i in range(0, len(results["text"])):# extract the bounding box coordinates of the text region from# the current resultx = results["left"][i]y = results["top"][i]w = results["width"][i]h = results["height"][i]# extract the OCR text itself along with the confidence of the# text localizationtext = results["text"][i]conf = int(results["conf"][i])if conf > 0:   print("Confidence: {}".format(conf))   print("Text: {}".format(text))   print("")   text = "".join([c if ord(c) < 128 else "" for c in text]).strip()   cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)   #cv2.putText(image, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX,1.2, (0, 0, 255), 3)# show the output imagecv2.imshow("Image", image)cv2.waitKey(0)


*Русский язык надо отдельно добавлять в tesseract, но это несложно, надо закинуть два файла в его директорию.
Посмотрим, что получится на выходе:

Хм, первая капча как-то быстро сдалась, поэтому возьмем другую:

Как видно, что-то определилось, а что-то нет.

Улучшаем tesseract.


Не будем утомлять бесконечными безуспешными попытками, которые не принесли результата. Перейдем к сути.
Интересен подход с поворотом изображения. Посмотрим, как реагирует tesseract при повороте изображения, допустим на 10 градусов.
Обновленный код дал следующие результаты:

То есть, работать с этим можно.
Повращаем изображение под разными углами в цикле от -20 до 20 градусов, а также отсечем слова меньше 5 букв (так как в подавляющем большинстве попадающиеся слова длиннее):
from pytesseract import Outputimport pytesseractimport argparseimport cv2pytesseract.pytesseract.tesseract_cmd = 'D:\\Tesseract-OCR\\tesseract.exe'a=[] # повернем изображение на x градусовfor x in range (-20,20):        image = cv2.imread('4-.jpg')                (h, w) = image.shape[:2]        center = (w / 2, h / 2)        #print("угол: {}".format(x))        M = cv2.getRotationMatrix2D(center, x, 1.0)        rotated = cv2.warpAffine(image, M, (w, h))        rgb = cv2.cvtColor(rotated, cv2.COLOR_BGR2RGB)        config = r'--oem 3 --psm 6'        results = pytesseract.image_to_data(rgb, output_type=Output.DICT,config=config,lang='rus')                # loop over each of the individual text localizations        for i in range(0, len(results["text"])):                                            text = results["text"][i]                conf = int(results["conf"][i])                if conf > 0:                                      if len(text)>5:                                                      a.append(text) print(a)

На выходе список того, что получилось:
['величии', 'величии', 'величии', 'величии', 'величии', 'величии', 'еличиил', 'величии', 'величии', 'величии', 'величии', 'величиЧ', 'величии', 'величиЧ', 'величи', 'величи', 'величи', 'лишил!', 'лишал|', 'лищил`']

Как видно, tesseract не так уж и плох, если им покрутить.

Осталось самое сложное.


Осталось почистить результаты и понять, какие слова правильные.
Почистим список слов, удалив оттуда слова, имеющие буквы в верхнем регистре, спецсимволы, а также дубли слов:
for i in set(a): #выкинули дубли        if any(char in " .,:;!_*-+()/#%&?)" for char in i)==True:#выкинули слова со спецсимволами                pass        else:                if i.islower(): #выкинули с верхним регистром                                        print(i) 

Останется меньше слов:
величивеличииеличииллишилвелич

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

Ну и поверженную капчу после цикла:


Таким образом, капчи с расположением по типу змейки таки могут поглощаться tesserаctом. Печально, что их не так уж и много среди прочих. Что делать с капчами по типу горок и улыбок пока не ясно.

Скачать готовый код.
Скачать тушки капч здесь.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru