Антикапча для почты России

В январе 2014 произошла повторная смена шрифта. Актуальная версия скрипта в конце статьи.

12 января 2014 года


В середине декабря 2013 почта России поменяла шрифт для капчи. Актуальная версия скрипта в конце статьи

22 декабря 2013 года


Последние несколько месяцев работа почты России оставляет желать лучшего. В первую очередь это касается международных отправлений. Вот уже 3 месяца я жду посылку из Китая, при этом каждые два часа происходит проверка её статуса. Использовать сервис Antigate для распознавании капчи из-за этого становится невыгодным. Чтобы повысить эффективность системы автоматического отслеживания РПО (регистрируемых почтовых отправлений) пришлось разрабатывать алгоритм, который позволит обойти ограничения, наложенные почтой России.

Сервер, который я использую, может запускать только скрипты, написанные на PHP, поэтому выбор языка программирования для реализации моей затеи был предрешен. Для работы с изображениями в PHP предусмотрена библиотека GD. Чтобы написать систему автоматического распознавания капчи на сайте России, нам понадобится всего несколько функций из нее. Давайте разместим их в порядке уменьшения значимости при разработке скрипта.

1) imagecolorat(resource $image , int $x , int $y) – получает значение индекса цвета пиксела, где:
$image – ресурс изображения, полученный, например, функцией imagecreatefrompng;
$x – x-координата пиксела;
$y – y-координата пиксела.

2) imagecreatefrompng(string $filename) – создает изображение из файла или URL-ссылки, где:
$filename – ссылка на изображение в формате PNG.
В случае успешного выполнения, функция возвращает идентификатор изображения. При возникновении любой ошибки будет возвращена пустая строка.

3) imagesx(resource $image) – возвращает ширину изображения, переданного функции идентификатором $image, в пикселях.

4) imagesy(resource $image) – возвращает высоту изображения, переданного функции идентификатором $image, в пикселях.

С инструментами, необходимыми для разработки, мы определились. Теперь обозначим алгоритм, которого будем придерживаться. Изначально соберем исходные данные об изображении, для чего циклом пройдемся по каждой его точке и сосчитаем количество пикселей с одинаковым цветовым индексом. Цвет, которого окажется больше, определит фон, а все остальное – необходимые нам цифры.

Исходный код
$image_name="1.png"; //имя загружаемого файла (капча с сайта ПР)
$im = imagecreatefrompng($image_name);
$number=array();
for ($y=0;$y!=imagesy($im);$y++)
{
    for ($x=0;$x!=imagesx($im);$x++)
    {
        $number[imagecolorat($im,$x,$y)]++;
    }
}
print_r($number); //отображаем статистику

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

Исходный код
$image_name="1.png";
$im = imagecreatefrompng($image_name);
echo("<table style='font-size:10px;font-weight:bold;text-align:center' cellpadding='0' cellspacing='0'>");
for ($y=0;$y!=imagesy($im);$y++)
{
    echo("<tr>");
    for ($x=0;$x!=imagesx($im);$x++)
    {
        $colors=imagecolorat($im,$x,$y);
        if ($colors==16777215)
        {
            echo("<td></td>");
        }
        else
        {
            echo("<td style='background-color:black;color:white;width:15px;height:15px;'>{$x}<br>{$y}</td>");
        }
    }
    echo("</tr>");
}
echo("</table>"); 

Так и есть, создается впечатление, что специалисты IT-отдела почты России старались сделать все возможное, чтобы капчу можно было распознать без особых проблем. После преобразования мы видим хорошо различимые цифры.

Распознавать такие символы я решил «в лоб» с помощью прямого сравнения, но не всех точек, а лишь опорных. Последними их можно назвать разве что условно. В каждом конкретном случае принималось абсолютно индивидуальное решение. Точки выбирались таким образом, чтобы их количество позволило однозначно идентифицировать цифру, но при этом не было избыточным. Такой прием повысит точность распознавания, когда один символ пересекает другой.

Идея состоит в следующем. Выбирается одна из опорных точек, которая становится началом координат. Именно относительно её описываются все другие элементы и заносятся в качестве шаблона в скрипт. Например, точка с координатами (58;4) будет иметь координаты (0;0), точка (62;4) соответствует точке (4;0) в относительных координатах, а точка (57;5) – точке (-1;1).
Для того чтобы включить эту информацию в разрабатываемый скрипт, я решил использовать многомерный массив $mask[a][b][c][d], где:
a – целое число от 0 до 9, которое соответствует значению разгадываемого числа;
b – целое число от 0 до очень большого значения, которое определяет вариант шаблона;
c – целое число от 0 до очень большого значения, которое определяет порядковый номер точки;
d – буква либо «x», либо «y».
Например, опорная точка (начало координат) для первого варианта цифры 8 определяется в скрипте следующими командами:
$mask[8][0][0]['x']=0;
$mask[8][0][0]['y']=0;
Вторая точка первого варианта цифры 8:
$mask[8][0][1]['x']=4;
$mask[8][0][1]['y']=0;

Чтобы получить представление о шрифте, используемом почтой России, пришлось проанализировать несколько десятков капч. Исследование показало, что для каждой цифры используется два написания, поэтому чтобы свести процент ошибок к минимуму необходимо добавить в словарь описание 20 символов.

При написании функции, распознающей символы, как нельзя, кстати, пришлась такая особенность PHP, как отсутствие жесткой типизации данных. Дополнительно пришлось отключить предупреждения интерпретатора, чтобы исключить вывод лишней информации в браузер. Отключение производится путем выполнения команды ini_set('error_reporting', E_ERROR). Ниже приведу кусок кода, отвечающий за сравнение элементов массива со словарем, содержащимся в переменной $mask, объявив последнюю как глобальную.

Фрагмент скрипта reqognize.php
function test_picture($x,$y,$mass_x)
{
    global $mask; 
    $result=-16;
    $page=0;
    while ($page<sizeof($mask))
    {
        $num=0;
        while ($num<sizeof($mask[$page]))
        {
            $num_arr=$mask[$page][$num];
            $i=0;
            while ($i<sizeof($num_arr))
            {
                if ($mass_x[$x+$num_arr[$i]['x']][$y+$num_arr[$i]['y']]!='1')
                {
                    $i=100;
                } else {$i++;}
            }
            if ($i!=100)
            {
                $num=1000;
                return $page;
            }
            $num++;
        }
        $page++;
    }
    return $result;
}

Функция test_picture в качестве аргументов принимает массив с исходными данными ($mass_x) и координаты ($x, $y) внутри этого массива, относительно которых происходит сравнение со словарем. В случае успешного выполнения мы получаем распознанный символ, либо число -16 – при ошибке. Однако перед тем как использовать эту функцию, мы должны подготовить массив с исходными данными.

Фрагмент скрипта reqognize.php
$im = imagecreatefrompng($image_name);
for ($i;$i!=imagesx($im);$i++)
{
    for ($a=0;$a!=imagesy($im);$a++)
    {
        $colors=imagecolorat($im,$i,$a);
        if ($colors==16777215){$img_data[$i][$a]='0';}else{$img_data[$i][$a]='1';}
    }
}

Затем остается пробежаться по всем точкам массива $img_data. Все символы, которые мы найдем функцией test_picture, будем сохранять (вместе с координатами) в специальную структуру $ocr_text.

Фрагмент скрипта reqognize.php
$i=0;
$ocr_text=array();
for ($y=0;$y<23;$y++)
{
    for ($x=0;$x<70;$x++)
    {
        if ($img_data[$x][$y]=='1')
        {
            $main_data=test_picture($x,$y,$img_data);
            if ($main_data!=-16)
            {
                $ocr_text[$i]['x']=$x;
                $ocr_text[$i]['y']=$y;
                $ocr_text[$i]['text']=$main_data;
                $i++;
            }
        }
    }
}

Из-за особенностей алгоритма распознанные символы находятся в структуре в разброс. Отсортировав $ocr_text по возрастанию координат «х» методом пузырьковой сортировки, получим интересующую нас комбинацию чисел.

На этом, считаю свою миссию выполненной. В результате мы получили модуль reqognize.php, содержащий функцию ocr_image, которая возвращает текстовую строку, принимая в качестве аргумента ссылку на файл капчи почты России. Для этой функции вы можете использовать URL в качестве имени файла, если включена опция fopen wrappers. Рассмотрим пример использования функции ocr_image. Ниже приведу алгоритм, который распознает все картинки в формате PNG, находящиеся в папке скрипта. Вывод информации осуществляется в таблицу.

Исходный код
<?
require_once("reqognize.php");
$filelist = glob("*.png");
echo("<html>\r\n <head></head>\r\n <body>\r\n  <table>\r\n");
for ($i=0;$i!=sizeof($filelist);$i++)
{
  $captcha=ocr_image($filelist[$i]);
  echo("   <tr><td>$captcha</td><td><img src='$filelist[$i]'></td></tr>\r\n");
}
echo("  </table>\r\n </body>\r\n</html>");
?>

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

Материалы к статье:
Версия на апрель 2014 г. - скачать;
Версия от 12 января 2014 г. - скачать;
Версия от 30 июня 2013 г. - (больше не актуальна).

Удачи в Ваших начинаниях!!!