Распознавание простой капчи [gOCR и PHP]
При прохождении одного из заданий на программирование была дана капча, которую нужно было распознать в течении 15 секунд. Вручную ввести данные нереально, поскольку состоит она из 96 символов, алфавитом являются 16-ричные значения. Да, такая вот длинная капча 🙂 Но задание поэтому и в ветке программирование, значит будем решать.
Внешне изображение выглядит так:
В достоинство алгоритма генерации можно отнести мелкие символы и их количество. В остальном же разгадать такую не составит труда. Распознавание данной капчи, как и любой другой в виде изображения можно выполнить разными способами. Я решил воспользоваться готовой OCR. Это такая штука, которая большую часть работы делает за нас. Проведя эксперименты с различным софтом предназначенным для оптического распознавания символов, остановился на gOCR. Данная программа существует под все распространённые операционные системы, но что более приятно — есть в репозиториях убунты (на которой построен дистрибутив BackBox).
Установка gOCR
Я привык пользоваться менеджером пакетов APT, поэтому:
apt-get install gocr
Установив гоцр, можно проверить его работоспособность выполнив в терминале gocr <опции> /путь/к/изображению
Поскольку в нашем случае базой являются значения 0-9 и a-f, зададим их через параметр -C.
Неплохо. Успешно распознаны почти все символы за исключением четверки, девятки и нуля, которые я выделил красным. Можно было бы оставить и так, но так дело не пойдёт, оставлять это дело на 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 или девяткой — вызывем нашу функцию [highlight background=»#e84386″]getChar(позиция)[/highlight] передав в качестве аргумента позицию символа в строке.
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 штук, то перезаписываем [highlight]$answer[$pos][/highlight].
Под конец выведем результат после обработки.
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 — штука хорошая.
себе я задачу ставил как раз наоборот — распознать без сторонних программ.