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

  • Tutorial
Речь пойдет об относительно новом творении в области капча-производства, новой яндекс-капче. Поищем слабые места, пролезем в эти слабые места и осмотримся там. Также подумаем на тему — помогает ли программа пакету распознавания текста на картинке — Tesseract — стать лучше.


Дано


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

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



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



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



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

С чего начать


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







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



Что дальше


Попробуем старый, добрый пакет tesseract, а за основу возьмем код из этой статьи.

В общем и целом в ней описывается как пакет tesseract распознает текст на изображении. На выходе программы выводится confidence и text. Грубо говоря, степень достоверности определенного текста и сам текст. Также программа рисует прямо на картинке, что она «видит». Этот код нам очень поможет в дальнейшем.

Повыкидываем из него лишнее, например, рисование того, что было прочитано и т.п.

В обновленном виде он выглядит так:

код
# import the necessary packages
from pytesseract import Output
import pytesseract
import argparse
import cv2
# Путь для подключения tesseract
pytesseract.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 localizations
for i in range(0, len(results["text"])):
	# extract the bounding box coordinates of the text region from
	# the current result
	x = 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 localization
	text = 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 image
cv2.imshow("Image", image)
cv2.waitKey(0)


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

Посмотрим, что получится на выходе:



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



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

Улучшаем tesseract


Не будем утомлять бесконечными безуспешными попытками, которые не принесли результата. Перейдем к сути.

Интересен подход с поворотом изображения. Посмотрим, как реагирует tesseract при повороте изображения, допустим на 10 градусов.

Обновленный код дал следующие результаты:



То есть, работать с этим можно.

Повращаем изображение под разными углами в цикле от -20 до 20 градусов, а также отсечем слова меньше 5 букв (так как в подавляющем большинстве попадающиеся слова длиннее):


from pytesseract import Output
import pytesseract
import argparse
import cv2
pytesseract.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ом. Печально, что их не так уж и много среди прочих. Что делать с капчами по типу «горок» и «улыбок» пока не ясно.

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

Комментарии 11

    0
    Осталось почистить результаты и понять, какие слова правильные.

    Ну если в капче слова, то да. А если просто несколько символов, это не поможет.

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

    А вот направление с шумами интереснее, но читабельность страдает.

    Против этого я вот придумал такую капчу:
    image
    Если ее доработать по моему будет вполне стойкая капча.
      0
      И для такого подхода, что горка, что улыбка — все равно.

      Как вы планируете разбивать на символы «горку» или «улыбку»? Они не в статичном положении представлены, а «гуляют» по canvas от капчи к капче.
      А вот направление с шумами интереснее, но читабельность страдает.

      Здесь оно тупиковое, т.к. цвета пикселей в словах и фоне пересекаются. И при denoise отлетает часть слова вместе с вычищенным фоном.

      *Речь скорее о «штатных» подходах, не обязательно сразу обращаться к CNN.
        0
        Разделяем по яркости — останется белый фон — черные буквы. Находим все черные связные области. Находим их координаты — выстраиваем цепочку по соседям. У нас получится набор картинок по порядку, в каждой буква с поворотом. Но нейросетке этот поворот без разницы.

        Да я понимаю, что у вас статья привязана к инструменту, а я в целом о задаче.
          0
          В том-то и фокус, что по яркости не разделить. В буквах и фоне одни и те же цвета.

          Вовсе не привязана. Но как вы здесь обучать собираетесь?
            0
            В том-то и фокус, что по яркости не разделить. В буквах и фоне одни и те же цвета.

            пример дайте — картинку.

            Вовсе не привязана. Но как вы здесь обучать собираетесь?

            Детально не смогу сказать. Общий принцип понимаю;
            Например этап отделения букв — скармливаете нейросеть сети кучу капч — и советсвующие координаты границ букв — учите вычленять отдельные буквы. На следующем этапе даете набор с координатами — и массив координат выстроенных по порядку — учите расставлять по порядку и т.д.
              0
              пример дайте — картинку.

              Берите любую, к статье приложены.

              координаты границ букв

              Так координаты меняются постоянно.
                0
                В буквах и фоне одни и те же цвета.


                image

                Цвет один — серый — точнее компоненты цвета нету. Но яркости разные и глаз их различает и алгоритмы их различают.

                Эта картинка например в фотошопе операцией Image-Adjustment-Threshold при значении Threshold Level 72 превращается в белую — с отчетливыми черными буквами (попробуйте сами). Во всяких графических пакетах типа GDAL это можно сделать программно.

                Вот примерно так это работает:
                image
                image

                Так координаты меняются постоянно.

                В обучающейся выборке в каждом отдельном случае все фиксированно. Ну это ладно долго объяснять.
                  0
                  Совсем по учебнику, легкое размытие по Гауссу + Otsu's threshold:

                  image

                  Этого не хватит?
                    0
                    Вполне. Осталось понять, как «нарезать» буквы, расположенные каждый раз в разных местах, для целей обучения CNN. Если, конечно, мы пойдем этим путем.
                      0
                      Если просто «нарезать», то, насколько я помню, connected components уже есть в «Симпсонах» в OpenCV. Проблемы с отваливающимися частями букв вроде i или й, думаю, можно решить, приклеив компоненты со слишком маленькой площадью к ближайшим компонентам побольше.

                      Если речь о том, чтобы не только нарезать на буквы, но и выровнять горизонтально ужос вроде картинки с «ashes innermost», то, конечно, уже интереснее. Для начала, я бы попробовал что-то вроде следующего:
                      • поделить на несвязанные компоненты (см. выше)
                      • для каждого компонента найти геометрический центр (есть в OpenCV)
                      • связать каждый центр отрезками с двумя ближайшими центрами, после чего удалить две самые длинные связи — должна получиться цепь упорядоченных символов, а если распрямить полученную ломанную в прямую и поморфить изображение соответственно — то и эти символы должны выстроиться в более-менее ровный ряд
                      • ну и далее читаем Тессерактом или чем там еще

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

    Самое читаемое