Добавил 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. Подгружаем эталоны
Для начала мы создаем сами эталоны (они находятся у меня в папке "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);
Здесь мы вычисляем потенциал по такой формуле:

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