Уже не помню, как я наткнулся на статью habr.com/ru/post/464337, но она запала мне в

Наконец, несколько дней назад я осилил запуск питона и решил, а почему бы и не да и всё такое. Забыв всё, что я прочитал в упомянутой статье, пошёл своим путём.
Вспоминая несметное количество решённых капч, я предположил, что можно решать их банальным сравнением с маской, что и подтвердилось впоследствии.
Во-первых, вручную собрал тестовые капчи (83 штуки) и дал им очевидные имена. Скриптом превратил их в битовые изображения.

Цифры в капчах бывают двух размеров по высоте с разницей в 1 пиксель и трёх-четырёх начертаний по ширине. Базовая линия всех символов во всех капчах одинаковая. Всё это разнообразие, как оказалось, имеет некую общую маску, сравнение с которой однозначно идентифицирует цифру. Вырезал по нескольку (сначала – по 5, потом добавлял ещё по 1-2; с «4» провозился дольше остальных) одинаковых цифр из разных капч. В paint.net наложил их друг на друга и получил общую для всех начертаний каждой цифры маску.

Единственную проблему обнаружил позднее, уже при массовой обработке, но успешно её обошёл
Кроме этого небольшого недоразумения шум совершенно не мешает. Итогом этого этапа стал набор из 9 масок. Два вложенных цикла и вуаля! – все мои 83 капчи распознаются на ура!
Дальше встал вопрос: где взять большой набор капч для проверки. И я скачал «29 000 капч» из упомянутой статьи.
Во-вторых, а на самом деле – во-первых, – это не настоящие капчи. Сайт отдаёт картинку в формате PNG, а в наборе – JPG, причём крайне плохого качества, причём со сдвигом. Могу предположить, что именно такова была цель автора – статья же недаром называется «”зашумленная” капча».
Так что пришлось расчехлить гугл и самостоятельно намайнить идеальных капч: за ночь набралось 3224 файла, в том числе 49 абсолютно пустых, как выяснилось позднее. Cпасибо Ганеше за код.
Собственно распознавание капчи укладывается в 26 строк скучного кода на питоне. Из внешних модулей нужен только PIL. Скорость работы – примерно 1000 капч в минуту (одна тысяча капч в минуту) на стареньком Core 2 «четыре ядра четыре гига». На более приличном восьмипоточном i5 заметно быстрее, хотя дело, конечно, не в потоках. Распознавание 100% или очень к тому близко: выборочная проверка не показала ошибок.
Конечно, всё это не интересно в смысле нейронных сетей и прочих блокчейнов, но имеет совершенно определённое преимущество перед предложенным ранее вариантом: скорость и точность. Так же верно и то, что любое изменение параметров капчи – гарнитуры или размера шрифта, вид шума и т.д. – приведёт к полной неработоспособности моего решения.
Скачать архив с капчами с Яндекс.Диска (14МБ).
from PIL import Image, ImageTk
def recognize(filepath):
Zlist = [] # [(x1, z1), (x2, z2), (x3, z3), etc.] - position and digit
captcha = ""
originalimage = Image.open(filepath).convert('L').point(lambda x : 255 if x > 20 else 0, mode='1').convert('1').convert('RGBA')
if originalimage.getextrema() == ((0, 0), (0, 0), (0, 0), (255, 255)):
return("empty image")
for z in [4, 2, 3, 1, 5, 6, 7, 8, 9]: # reorder to exclude false 1 on 4
mask = Image.open('mask' + str(z) + '.png').convert('RGBA')
previ = 0
for i in range(15, 120): # no digit in left part
resultimage = Image.alpha_composite(originalimage.crop((i, 0, i + 30, 0 + 50)), mask)
if resultimage.getextrema() == ((0, 0), (0, 0), (0, 0), (255, 255)):
if z == 4: # delete 4 to exclude false 1 on 4
maskx = Image.open('mask4x.png').convert('RGBA')
originalimage.paste(Image.alpha_composite(originalimage.crop((i, 0, i + 30, 0 + 50)), maskx), (i, 0))
if previ == 0 or i > previ + 15: #no digit closer then 15 px
Zlist.append((i, z))
if len(Zlist) == 5:
Zlist.sort()
for z in Zlist:
captcha = captcha + str(z[1])
return(captcha)
previ = i
i = i + 15 #skip a little
Zlist.sort()
return(str(Zlist)) #if less then 5 digits recognized
def main():
captcha = recognize(entry.path)
#----------------------------------------------#
# в архиве полный код для массовой обработки #
#----------------------------------------------#
main()
Автор: 0617