Pull to refresh

Разбор простейшей капчи (C#)

Reading time 3 min
Views 12K
    Какое-то время назад мне пришлось сделать программку, которая скачивала в автоматическом режиме файлы с одного достаточно известного сайта. Проблема /на первый взгляд/ состояла в том, что там была капча. Однако одного взгляда на нее было достаточно, чтобы понять, решаемо и очень быстро :) По прошествии нескольких лет снова наткнулся на тот проект и решил вот выложить на хабр. Сразу оговорюсь, сайт называть не буду ибо капча там до сих пор такая и пусть такой и остается.



Этап первый: Сбор информации


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

Вот посмотрите, это примеры капчи:


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

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


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

    Приведу пример тех изображений:


    Здесь я привел маску с только самыми часто встречаемыми цветами. Чтобы отсеять лишнее я убрал из маски все цвета, которые содержат менее 25 пикселей на форме. В принципе это дает возможность промахнуться с определением, если в капче будет 1-2 символа, которые займут меньше 25 пикселей, но я ни одного такого изображения в этой капче не встретил и заморачиваться не стал.

    Итак в последнем отображении видно, что у нас фактически есть маска. Абсолютно чистая, которая выглядит в редакторе вот так:


Привожу код, которым выдергивал варианты масок по цветам:

        public Bitmap ClearBitmap(Bitmap input, Color clr)
        {
            var result = new Bitmap(input.Width, input.Height);
            for (var x = 0; x < input.Width; x++)
            {
                for (var y = 0; y < input.Height; y++)
                {
                    var color = input.GetPixel(x, y);
                    result.SetPixel(x, y, clr == color ? Color.Black : Color.White);
                }
            }

            return result;
        }

        public void Main()
        {
            var bitmap = new Bitmap("D:\\check_image1227.png");
            var palette = new Dictionary<Color, int>();
            for (var x = 0; x < bitmap.Width; x++)
            {
                for (var y = 0; y < bitmap.Height; y++)
                {
                    var clr = bitmap.GetPixel(x, y);
                    if (!palette.ContainsKey(clr))
                    {
                        palette.Add(clr, 1);
                    }
                    else
                    {
                        palette[clr] = palette[clr] + 1;
                    }
                }
            }
            var i = 0;
            foreach (var c in palette)
            {
                if (c.Value > 30)
                {
                    var temp = this.ClearBitmap(bitmap, c.Key);
                    temp.Save(String.Format("D:\\mask-{0}.bmp", i));
                    i++;
                }
            }
        }


Этап три: В бой!


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




    По какой-то причине, в капче не использовался символ 9, но не важно. Дальше просто. Каждое число берем в квадрат, правая часть — минимальный бит, левая — максимальный. Кто работал с ассемблером на 8086 и делал маски символов меня поймет, для остальных пример:



    На изображении сверху поставлены номера бит, справа готовое число. Чтобы было еще более понятно, черные точки заменяем на 1, белые на ноль. Например верхняя строчка данного числа выглядит в двоичной системе как 0001111111000, т.е. 1016 в десятичной системе. Под каждую цифру был сделан массив, описывающий матрицу.

    Дальнейший алгоритм выглядел так. Была написана функция, которая очищала полученное изображение от белых точек сверху, снизу и по бокам. И была написана функция, которая возвращала массив чисел, представляющих указанную область. После этого все было просто. Так как все делалось автоматически, я сделал так, что после очистки изображения и отрезания ненужных данных делалась проверка, чтобы высота изображения попадала под размер цифры (в данном случае имело смысл искать цифру). После чего в цикле слева направо сравнивалась область с каждой цифрой до совпадения. Совпала цифра -> передвигаемся по изображению на ширину символа вправо. Проверяем следующую область и так далее. В итоге, все получилось не сильно быстрым, я приложу проект, чтобы вы могли сами посмотреть, но решение возвращало 100% правильно распознанную капчу.

P.S. Сильно не пинайте за код проекта, писалось давно и на скорость. Многое можно оптимизировать, но это уже задачка вам, если конечно интересно :)

P.P.S. Все таки думаю ничего не изменится, если я укажу адрес сайта, это был cracks.ms

Проект для VS2010
Tags:
Hubs:
+33
Comments 27
Comments Comments 27

Articles