PHPocr
Czy pisząc w języku PHP skrypt automatycznie wykonujący pewne operacje na danej stronie WWW, da się obejść jakże dziś popularne zabezpieczenie polegające na konieczności wpisania przez użytkownika w formularzu tekstu widocznego na obrazku? Okazuje się że tak. Ten artykuł nie jest kompletnym źródłem informacji na temat łamania tego typu zabezpieczeń. Pokazuję tu jedynie w jaki sposób można odczytać tekst o znanej nam czcionce, na jednolitym tle.
Liczy się sama idea. Dynamicznie umieszczać tekst w obrazku z poziomu PHP z rozszerzeniem GD jest banalnie łatwo. Odwrotna operacja jest znacznie trudniejsza. O tym czy nasz skrypt będzie działał względnie szybko i dokładnie zadecyduje algorytm który zostanie użyty do rozpoznawania tekstu. Ja użyłem wydaje mi się najprostszego, chciało by się rzec łopatologicznego. Algorytm moim zdaniem nie jest najlepszy, gdyż czas jego wykonywania w znacznym stopniu zależy od wielkości skanowanego obrazka, rozmiarów użytej znanej nam w tym przypadku jednej ze standardowych czcionek PHP, ilości znaków w szablonach z którymi porównywane są kolejne wycinki obrazka i tła które musi być jednolite. Sporo tych ograniczeń ale jeśli opanujemy odczyt tekstu w znanej nam czcionce, widocznego na jednolitym tle, to później będziemy w stanie rozważyć kolejne utrudnienia których w tym artykule niestety nie obejmę. I w tym momencie wspomnę tylko że jeśli macie pomysł na lepszy algorytm, a uważam że da się stworzyć lepszy, dokładniejszy i bardziej wydajny, to mail me.
A teraz konkretnie. Odczytujemy obrazek.
<?php
$procent=98; //tolerancja
// Skanowanie obrazka
$image=ImageCreateFromPng("test4.png");
ImageGammaCorrect($image, 0.0001, 1000.0);
ImageTrueColorToPalette($image, true, 2);
$text_index=ImageColorClosest($image,0,0,0);
ImageColorSet($image,$text_index,0,0,0);
$bg_index=ImageColorClosest($image,255,255,255);
ImageColorSet($image,$bg_index,255,255,255);
$szerokosc=ImageSX($image);
$wysokosc=ImageSY($image);
$image2=ImageCreate($szerokosc,$wysokosc);
$background_color = ImageColorAllocate($image2,255,255,255);
$text_color = ImageColorAllocate($image2,0,0,0);
ImageTrueColorToPalette($image2, true, 2);
ImageCopy($image2, $image, 0, 0, 0, 0, $szerokosc, $wysokosc);
$image=$image2;
ImageDestroy($image2);
for($j=0;$j<$wysokosc;$j++)
{
for($i=0;$i<$szerokosc;$i++)
{
$indeks[$i][$j]=ImageColorAt($image,$i,$j);
}
}
ImageDestroy($image);
...
?>
|
- Jak widać na podstawie obrazka w formacie PNG tworzymy nowy obiekt o nazwie "$image". Tworzymy go za pomocą funkcji ImageCreateFromPNG(). Oczywiście dla plików graficznych w innych formatach należy stosować inne odpowiedniki tej funkcji.
- Następnie obrabiamy odpowiednio nasz obrazek za pomocą funkcji ImageGammaCorrect(), uzyskując skrajnie kontrastowy czarno-biały obraz.
- Po tej operacji redukujemy głębię obrazu do 2 kolorów.
- W tym momencie potrzebny jest człowiek. Musimy poinformować parser PHP czy tekst jest jaśniejszy niż tło, czy też jest odwrotnie. Wykorzystując funkcję ImageColorClosest() definiujemy indeksy kolorów najbliższych kolorowi białemu i czarnemu we właśnie utworzonej palecie kolorów. Następnie poprzez funkcję ImageColorSet() "na sztywno" określamy kolor dla indeksu palety najbliższemu kolorowi białemu i to samo robimy dla koloru czarnego. Po konwersji obrazka otrzymaliśmy 2-kolorową paletę, więc nie powinno być z tym problemu.
- Wykorzystując funkcje ImageSX() i ImageSY() pobieramy szerokość i wysokość obrazka.
- Tworzymy nowy obrazek w palecie 256 kolorów poprzez funkcję ImageCreate().
- I tu ważny moment. Alokujemy w nim kolor tła do pierwszego indeksu oraz kolor tekstu do drugiego.
- Redukujemy głębię kolorów tego obrazka do dwóch kolorów.
- Kopiujemy pierwotny obrobiony obraz do naszego nowo przygotowanego obrazka ze zdefiniowanymi przez nas indeksami kolorów tekstu i tła. To bardzo ważne. Gdybyśmy tego nie zrobili, operacje porównania indeksów kolorów odpowiednich pikseli z wycinków obrazka i szablonów znaków zwracały by błędne wyniki.
- Z tak przygotowanego obrazka za pomocą funkcji ImageColorAt() wyodrębniamy indeksy kolorów poszczególnych pikseli i umieszczamy je w 2-wymiarowej tablicy "$index[][]".
- Następnie uwalniamy pamięć z niepotrzebnych obrazów, gdyż od tej pory operujemy na tablicy indeksów obrazu pierwotnego.
Tworzymy szablony znaków, z którymi będą porównywane wycinki naszego obrazka.
<?php ...
$znaki='AˇBCĆDEĘFGHIJKLŁMNŃOÓPQRSśTUVWXYZ¬Żaąbcćdeęfg hijklłmnńoópqrsśtuvwxyzźż0123456789';
// Generowanie szablonow liter
$k=10; $l=20;
$s=5;
if ($s==1) {$k=4; $l=8;}
else if ($s==2) {$k=5; $l=13;}
else if ($s==3) {$k=6; $l=13;}
else if ($s==4) {$k=7; $l=16;}
else if ($s==5) {$k=8; $l=16;}
for($c=0;$c<=strlen($znaki);$c++) //kolejny znak z tablicy $znaki
{
$znak=$znaki[$c];
$imszablon=ImageCreate($k,$l);
$background_color = ImageColorAllocate($imszablon,255,255,255);
$text_color = ImageColorAllocate($imszablon,0,0,0);
ImageString($imszablon,$s,0,0,$znak,$text_color);
ImageTrueColorToPalette($imszablon, true, 2);
for($j=0;$j<$l;$j++)
{
for($i=0;$i<$k;$i++)
{
$szablon[$znak][$s][$i][$j]=ImageColorAt($imszablon,$i,$j);
}
}
ImageDestroy($imszablon);
}
...
?>
|
- W zmiennej "$znaki" definiujemy znaki które mają być rozpoznawane przez nasz skrypt. W dalszej części skryptu zmienną "$znaki" będziemy traktować jako 1-wymiarową tablicę.
- Zależnie od wielkości czcionki "$s" określamy szerokość znaku "$k" i jego wysokość "$l".
- Z utworzonej przed chwilą tablicy "$znaki[]" pobieramy osobno każdy umieszczony w niej znak. Na jego podstawie tworzymy obraz "$imszablon".
- W tym obrazie za pomocą funkcji ImageString() osadzamy dany znak. Funkcja ImageString() przyjmuje jako 1 z argumentów rodzaj czcionki. Standardowe czcionki PHP oznaczane są cyframi 1-5.
- Obraz ten poddajemy identycznej obróbce co na początku obraz "$image".
- Z obrazu "$imszablon" wyodrębniamy indeksy kolorów poszczególnych pikseli i tak jak już to miało miejsce w przypadku obrazu "$image", umieszczamy je w tablicy, tym razem 4-wymiarowej o nazwie "$szablon[][][][]". W tej tablicy oprócz pozycji w pionie i poziomie danego piksela indeksowana jest wielkość czcionki i znak którego układ jest w danej chwili ewidencjonowany.
Porównujemy indeksy kolorów odpowiednich pikseli.
<?php ...
$zgodne=0;
$wszystkie=0;
// Porównywanie pikseli
if ($s==1) {$k=4; $l=8;}
else if ($s==2) {$k=5; $l=13;}
else if ($s==3) {$k=6; $l=13;}
else if ($s==4) {$k=7; $l=16;}
else if ($s==5) {$k=8; $l=16;}
for($y=0;$y<$wysokosc-$l;$y++)
{
for($x=0;$x<$szerokosc-$k;$x++)
{
for($c=0;$c<=strlen($znaki);$c++) //kolejny znak z tablicy $znaki
{
$znak=$znaki[$c];
for($j=0;$j<$l;$j++)
{
for($i=0;$i<$k;$i++)
{
$wszystkie++;
if ($indeks[$i+$x][$j+$y]==$szablon[$znak][$s][$i][$j])
$zgodne++;
} //i
} //j
if ($zgodne/$wszystkie>=$procent/100) print $znak;
$wszystkie=0;
$zgodne=0;
} //c
} //x
} //y
?>
|
- Definiujemy 2 zmienne: "$wszystkie" i "$zgodne". Ich wartości przyrównujemy do zera.
- Zależnie od wielkości czcionki "$s" określamy szerokość znaku "$k" i jego wysokość "$l".
- Porównujemy ze sobą indeksy kolorów odpowiednich pikseli pochodzących z wycinków obrazka i wygenerowanych szablonów znaków.
- Dla każdej kombinacji szablonu znaku i wycinka obrazu liczymy ilość odpowiednich indeksów pikseli których wartości się zgadzają.
- Po porównaniu wszystkich wycinków obrazu w poziomie, weryfikujemy sumę zgodnych indeksów kolorów odpowiednich pikseli.
- W przypadku gdy ilość zgodnych indeksów podzielona przez ilość wszystkich indeksów jest większa bądź równa ustawionej na początku tolerancji dzielonej przez 100, w oknie przeglądarki wypisywany jest dany znak.
- Następnie zmienne "$wszystkie" i "zgodne" są zerowane, aby kolejna pętla mogła się prawidłowo wykonać.
Tolerancja jest wartością która określa akceptowalne odchylenie liczby zgodnych pikseli. Im jest ona wyższa, z tym większa dokładnością badane są obrazy. W praktyce skrypt najlepiej działaja przy wartości zmiennej "$tolerancja" ustawionej pomiędzy 98-100.
Możemy stosować do szablonów inne czcionki niż te standardowe w PHP. W funkcji ImageString() wystarczy zamiast cyfry identyfikującej rodzaj standardowej czcionki użyć funkcji ImageLoadFont(), która jako argument przyjmuje ścieżkę do pliku GDF będącego plikiem czcionek dla rozszerzenia PHP_GD.
Przedstawiony skrypt pisałem zakładając że tekst w skanowanym obrazie jest ciemniejszy niż jego tło. W sytuacji odwrotnej należy zamienić ze sobą miejscami kolory RGB(255,255,255) i RGB(0,0,0). Skrypt nie jest idealny. Można go z pewnością udoskonalić, ale powinien on dać solidne podstawy do stworzenia prawdziwego skryptu typu PHPocr :-)
|