Распознавание простой капчи [gOCR и PHP]
При прохождении одного из заданий на программирование была дана капча, которую нужно было распознать в течении 15 секунд. Вручную ввести данные нереально, поскольку состоит она из 96 символов, алфавитом являются 16-ричные значения. Да, такая вот длинная капча 🙂 Но задание поэтому и в ветке программирование, значит будем решать.
Внешне изображение выглядит так:
Неплохо. Успешно распознаны почти все символы за исключением четверки, девятки и нуля, которые я выделил красным. Можно было бы оставить и так, но так дело не пойдёт, оставлять это дело на rand() судьбы плохая затея. Поэтому у нас есть два пути:
- обучить gocr для точного распознавания проблемных символов
- использовать свой вариант для коррекции
Я выбрал второй путь, поскольку заниматься поиском информации про обучение gOCR’a желания небыло. Выбор пал на PHP и модуль GD.
Вырезав 0, 4 и 9 в отдельные файлы я приступил к быдлокодингу.
Для начала вызовем через shell_exec gocr и переведём символы в массив.
$res = shell_exec("gocr -C 0-9a-f $p1"); // получаем результат $answer = str_split($res); // разбиваем строку на символы unset($answer[96]); // удаляем последний пробел
Затем будем проходить по полученной строке, и если ннаткнулись на спорный символ, который может быть 0, 4 или девяткой – вызывем нашу функцию getChar(позиция) передав в качестве аргумента позицию символа в строке.
foreach($answer as $k=>$v){ if($v == '4' OR $v == '9' OR $v == '0') // если встретился нужный символ, вызовем функцию getChar($k); }
И сама функция
function getChar($pos){ global $p1,$answer; $img = imagecreatefromjpeg($p1); // создадим рабочую область $size = getimagesize($p1); // определим размер $w1 = $size[0]; // ширина капчи $h1 = $size[1]; // высота капчи $images = array(0,4,9); // символы которые стоит проверить foreach($images as $im){ $p2 = 'chars/'.$im.'.jpg'; // открываем поочередно вырезанные символы из файла (эталон) $img2 = imagecreatefromjpeg($p2); $size2 = getimagesize($p2); $w2 = $size2[0]; $h2 = $size2[1]; for ($i=0;$i<$w1;$i++){ $c = 0; for ($j=0;$j<$h1;$j++) { $rgb = imagecolorat($img, $i, $j); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; $data[$i][$j] = $r; } } for ($i=0;$i<$w1;$i++){ $cont = 0; for ($j=0;$j<$h1;$j++) { if($data[$i][$j] >= 240){ $cont++; } } if($cont > 0 && $started == false){ $started = true; $begin = $i; } if($cont == 0){ if(isset($begin) && $started == true) $arr[] = array($begin,$i - 1); $started = false; } } $w = $arr[$pos][1] - $arr[$pos][0] + 1; $char = imagecreatetruecolor ($w, $h1); imagecopy( $char, $img, 0, 0, $arr[$pos][0], 0, $w, $h1); for ($i=0;$i<$w;$i++){ for ($j=0;$j<$h1;$j++) { $rgb = imagecolorat($char, $i, $j); $image1[$i][$j] = imagecolorsforindex($char, $rgb); } } for ($i=0;$i<$w2;$i++){ for ($j=0;$j<$h1;$j++) { $rgb = imagecolorat($img2, $i, $j); $image2[$i][$j] = imagecolorsforindex($img2, $rgb); } } $x = 0; $y = 0; $test = 0; for($x=0; $x<count($image1) - 1; $x++){ for($y=0;$y<count($image1[$x]);$y++){ if($image1[$x][$y]['red'] > 200 && $image2[$x][$y]['red'] > 200){ $test++; } } } $return = ''; if($test > 8){ // если количество белых точек о обоих изображениях больше 8 $answer[$pos] = $im; // предположим что у данного символа приоритет выше } imagedestroy ( $img2); // удаляем мусор, освобождаем ресурсы imagedestroy ( $char); // удаляем мусор, освобождаем ресурсы } imagedestroy ( $img); // удаляем мусор, освобождаем ресурсы return $return; }
Задача функции заключается в сравнении куска изображения, полученного из полной капчи со всеми вырезанными мной символами вручную. Если количество белых пикселей в обоих кусках на одинаковых позициях более 8 штук, то перезаписываем $answer[$pos] .
Под конец выведем результат после обработки.
foreach($answer as $k=>$v){ $res2 .= $v; } echo $res2;
Таким образом результаты распознавания значительно выросли. Я не проверял работу на многих капчах просто потому, что после первой же отправки данных сайт принял результат как верный. Просмотрев глазами различий не обнаружил, значит работает система хорошо.
Возможно кому-то пригодится.
7 replies to “Распознавание простой капчи [gOCR и PHP]”
Sergey
Задачу можно было решить значительно проще, т.к. не используется никакой защиты. Вытаскиваем из изображения прямоугольники с буквами и сравниваем их с заранее подготовленными хешами для каждой буквы.
VY_CMa
Дело в том, что на изображении присутствуют артефакты. На первый взгляд их трудно заметить, но при тщательном увеличении пиксели похожие на часть символа проявляются, именно из-за этого использовать дополнительную проверку решил только на три символа, для некоторых появлялись ошибочные результаты. Хеши используются, когда пиксели заранее известны и вероятность неточности равно нулю. Хотя можно было попробовать поиграть с контрастом изображения, это да.
qwe
Интересно.
Ссылку поправь
http://stackoff.ru/goto/http://securityoverride.org/challenges/programming/11/index.php
Виктор
Круто. То что нужно. Спасибо.
kronusme
Я просто оставлю это здесь – http://kronus.me/2013/11/securityoverride-задание-про-каптчу/ 🙂
VY_CMa
Тут может быть много подходов, я хотел посмотреть на что способны OCR, проверил штук 6, с мыслью на будущее. А так да, у тебя хороший вариант.
kronusme
Согласен, OCR – штука хорошая.
себе я задачу ставил как раз наоборот – распознать без сторонних программ.