Сайт Koder.kz сайт для Delphi программистов.

koder.kz

Web or desctop App Developer

Написать

Добавил admin | Категория Наши материалы | 11 Августа 2014


В данной статье мы рассмотрим так называемое распознавание "КАПЧИ". И так немного погрузимся в историю. Давным-давно уже во многих областях техники используются различные автоматы и устройства, более или менее удачно решающие задачу распознавания (это и автомат для сортировки почтовых конвертов по индексу, и зенитная ракета, захватывающая горячее сопло самолетного двигателя, но игнорирующая солнце, и различные системы анализа спутниковых снимков, и голосовой вызов вашего мобильника, и многое-многое другое), тем не менее, людей, уверенных в непреодолимой сложности алгоритмов, очень много. Мы рассмотрим алгоритм, который можно обучать.

Особую пикантность алгоритму придает тот факт, что его математическое обоснование было предложено советскими математиками в начале 60х годов (т.е. в то время, когда компьютер не всегда помещался в среднестатистической квартире), а еще лет через 15 была доказана его весьма глубокая аналогия с очень популярным в настоящее время нейросетевым методом.
Давайте сначала откроем делфи и набросаем на форму компоненты. 2 Image (Первое назвал Kapcha, второе kap1), 1 buuton, 1 edit и 1 memo. Мою форму вы сможете увидеть на рисунке.


Далее нам надо найти капчу и проанализировать его. Я выбрал не очень то сложную, простенькую капчу и сохрани её.

Немного подумав приходим к выводу, что нам легче будет работать если изображение сделать черно-белым, то есть фон белым, а наши циферки черными при черными. Как можно это реализовать? Смотрим далее. Подгружаем нашу капчу в Image (Kapcha). Изображение на канве представляет собой ни что иное, как матрицу чисел, то есть цветов. Мы будем обращаться к каждому пикселю, и изменять его цвет на нужный нам (фон белый, цифры черные).


// Определяем цвет фона 
  Fon:=Kapcha.Canvas.Pixels[0,0]; 
 // Перекрашиваем все не фоновые  пиксели в белые остальные в черные
For i:=0 to Kapcha.Picture.Width do 
  For j:=0 to Kapcha.Picture.Height do 
   If Kapcha.Canvas.Pixels[i,j]=Fon then 
    Kapcha.Canvas.Pixels[i,j]:=RGB(255,255,255) 
   else 
    Kapcha.Canvas.Pixels[i,j]:=0;

Здесь мы организовали 2 вложенных цикла и изменили цвет фона и цифр. Далее нам следует вырезать каждую цифру и помещать во второй image (kap1). Чтобы это сделать нам надо найти координаты начала и конца цифры это реализовано в процедуре Find_Digit.


//Производим поиск цифр 
procedure TForm1.Find_Koordinati(var L: info); 
 var 
  i,j,i1,j1:integer; 
  Find_Digit:info; 
  proverka:boolean; 
begin 
  Find_Digit.X1:=0; Find_Digit.Y1:=0; Find_Digit.X2:=0;  Find_Digit.Y2:=0;   
    For j:=0 to Kapcha.Picture.Height do 
    begin 
     For i:=0 to Kapcha.Picture.Width do 
     if Kapcha.Canvas.Pixels[i,j]=0 then 
      begin 
       Find_Digit.Y1:=j; 
       break; 
      end; 
     if Find_Digit.Y1<>0 then 
      break; 
    end;   
     For i:=0 to Kapcha.Picture.Width do 
    begin 
     For j:=0 to Kapcha.Picture.Height do 
      If Kapcha.Canvas.Pixels[i,j]=0 then 
       begin 
        Find_Digit.X1:=i; 
        break; 
       end; 
     if Find_Digit.X1<>0 then 
      break; 
    end;   
     proverka:=true; 
    For i:=Find_Digit.X1 to Kapcha.Picture.Width do 
    begin 
     For j:=Find_Digit.Y1 to Kapcha.Picture.Height do 
      If Kapcha.Canvas.Pixels[i,j]=0 then 
       begin 
        proverka:=false; 
        break; 
       end; 
     if not(proverka) then 
       proverka:=true 
      else 
       begin 
        Find_Digit.X2:=i; 
        break; 
       end; 
    end;   
    proverka:=true; 
    For j:=Find_Digit.Y1 to Kapcha.Picture.Height do 
     begin 
      For i:=Find_Digit.X1 to Kapcha.Picture.Width do
       If Kapcha.Canvas.Pixels[i,j]=0 then 
        begin 
         proverka:=false; 
         break; 
        end; 
     if not(proverka) then 
       proverka:=true 
      else 
       begin 
        Find_Digit.Y2:=j; 
        break; 
       end; 
    end; 
    kap1.Canvas.FillRect(kap1.Canvas.ClipRect); 
     i1:=0; j1:=0; 
    For i:=Find_Digit.X1 to Find_Digit.X2 do 
     begin 
     For j:=Find_Digit.Y1 to Find_Digit.Y2 do 
       begin 
        kap1.Canvas.Pixels[i1,j1]:=Kapcha.Canvas.Pixels[i,j]; 
        Kapcha.Canvas.Pixels[i,j]:=RGB(255,255,255); 
         inc(j1); 
       end; 
      j1:=0; 
      inc(i1); 
     end; 
end;

В данной процедуре также используются вложенные циклы в которых проверяются пиксели если они черные, то значит это начало, сохраняем (X1,Y2) их потом производим поиск белых писклей не сначала изображения, а с сохраненных координат (X1,Y2) если нашли белые пиксели во весь столбец значит нашли конец, сохраняем их в (X2,Y2). Далее копируем цифру в каp1 и стираем его в kapcha (чтоб он не мешал для дальнейшего поиска). Все мы уже знаем, как выделять цифры теперь осталось распознать их.
Делается это следующим образом.

  1. Подгружаем эталоны
  2. Сравниваем нашу цифру с эталонами и находим потенциал
  3. Находим максимальный потенциал

…и вот индекс максимального потенциала и будет наша распознанная цифра. Теперь подробно рассмотрим каждый пункт.
1. Подгружаем эталоны
Для начала мы создаем сами эталоны (они находятся у меня в папке "atalon") и сохраняем их в формате *.bmp. Размеры эталонов должны совпадать с размерами kap1 (и с размерами цифр на капче). Процедура LoadData подгружает эталоны:


procedure TForm1.LoadData; 
var 
 j:integer; 
 path:string; 
begin 
 path := ExtractFilePath(Application.ExeName)+'\atalon\'; 
 for j := 0 to 3 do 
  begin 
   Data[j] := TBitmap.Create; 
   Data[j].LoadFromFile(path + IntToStr(j) + '.bmp'); 
  end; 
end;

Процедура подгружает их и записывает в массив Data.
2) Сравниваем нашу цифру с эталонами и находим потенциал.
За сравнение отвечает функция Compare


function TForm1.Compare(b1, b2: TBitmap): integer; 
  var 
   i,j,count:integer; 
begin 
count := 0; 
for i := 0 to 17 do 
 for j := 0 to 28 do 
  if b1.Canvas.Pixels[i,j] <> b2.Canvas.Pixels[i,j] then 
    inc(count); 
  Result := count; 
end; 

А код нахождения потенциала приведен ниже:


for i := 0 to 3 do 
    p[i] := 0; 
    for j := 0 to 3 do 
     begin 
      r := Compare(kap1.Picture.Bitmap,Data[j]); 
      p[j] := p[j] + 1000000/(1+r*r); 
   end; 
  
   memo1.lines.clear; 
   max := 0; 
   maxNO := 0; 
    for i := 0 to 3 do 
      if p[i] > max then 
        begin 
         max := p[i]; 
         maxNO := i; 
        end; 
  
      for i := 0 to 9 do 
        memo1.lines.add(floattostr(p[i])); 
  
        case MaxNO of 
         0: chislo:=0; 
         1: chislo:=3; 
         2: chislo:=5; 
         3: chislo:=7; 
        end; 
  
       Edit1.Text := Edit1.Text + IntToStr(chislo); 

Здесь мы вычисляем потенциал по такой формуле:

И загружаем потенциалы в мемо. Находим максимальную mysq из них и сохраняем его индекс в переменную MaxNO. И в конце по нему находим нашу цифру и выводим в Edit. Особо сложного здесь нет, просто надо немного подумать, и все. Если что не понятно, смотрите исходник.
Заключение
Предложенный метод дает весьма неплохие результаты, как на машинописных, так и на рукописных цифрах. Алгоритм легко может быть переобучен для различения других символов (латинских/русских букв, знаков препинания и т.п.). Для повышения надежности распознавания можно предусмотреть сильно различающиеся между собой эталоны (для того, чтобы как можно сильнее разбросать эталонные точки в пределах компактного множества).