PHP OCR - Ein simpler Weg zur Texterkennung

PHP OCR - Ein simpler Weg zur Texterkennung 5 von 5 mit 1 Stimmen
| Sick^

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

Werbung
In diesem Artikel möchte ich euch einen Weg aufzeigen wie man ein simples PHP OCR Script entwickeln kann. OCR (Optical Character Recognition) heißt das man mit einem Algorithmus verschiedene Muster, zumeist Texte, in einem Dokument wie einem Bild oder PDF erkennt. Dieses PHP OCR Script ist eine sehr simple Methode und weißt in dieser Version eine hohe Fehlerquote bei der Erkennung auf.

Der Berg namens OCR

Genau wie beim Besteigen eines Berges trifft man bei der Entwicklung eines PHP OCR Scriptes auf viele Probleme, welche oft nicht so leicht zu lösen sind.
Ein paar dieser Probleme sind das Filtern der Charaktere aus dem Dokument, das erkennen des gefilterten Charakters, die Lernfähigkeit des PHP OCR Scriptes und vieles mehr.
Beim Filtern der Charaktere sind vor allem Rotation, Verzerrung und Farbwechsel problematisch.
Dieses PHP OCR Script behandelt lediglich das erkennen von einfachen Charakteren welche ohne Rotation, Verzerrung und vielen Farbwechseln dargestellt werden.

Der Weg zum Ziel

Es gibt viele Wege ein PHP OCR Script zu entwickeln.
Manche davon führen über Eigenvektoren und erfordern sehr gute Kenntnisse im Umgang mit Linearer Algebra.
Wir bedienen uns in diesem PHP OCR Script einfacher Logik und Schleifen um die Charaktere zu filtern und zu erkennen.
Dafür definieren wir zu erst Funktionen und Klassen welche uns später helfen werden.

Funktionen zum ermitteln und vergleichen der Farbwerte

PHP-Quellcode

  1. /**
  2. * Retrieve the rgba value from 1 pixel
  3. *
  4. * @param Resource $image The image resource
  5. * @param Integer $x The x coordinate of the Pixel
  6. * @param Integer $y The y coordinate of the Pixel
  7. * @return Integer The color index
  8. */
  9. function getColor($image, $x, $y) {
  10. $color = imagecolorat($image, $x, $y);
  11. return imagecolorsforindex($image, $color);
  12. }
  13. /**
  14. * Helper function to comapre if two colors matches with a range
  15. *
  16. * @param Array<Integer> $color1 Rgba values of color
  17. * @param Array<Integer> $color1 Rgba values of color
  18. * @param Integer The range the colors can differ
  19. * @return Boolean True if the colors matches
  20. */
  21. function compareColor($color1, $color2, $range = 20) {
  22. // Check if the parameters fulfill the requirements
  23. if (empty($color1) || empty($color2)) {
  24. return false;
  25. }
  26. // Check if the color1 is greater or equal to the color2
  27. if ($color1['red'] >= ($color2['red'] + $range) &&
  28. $color1['green'] >= ($color2['green'] + $range) &&
  29. $color1['blue'] >= ($color2['blue'] + $range) &&
  30. $color1['alpha'] >= ($color2['alpha'] + $range))
  31. {
  32. return false;
  33. }
  34. // Check if the color1 is less or equal to the color2
  35. else if ($color1['red'] <= ($color2['red'] - $range) &&
  36. $color1['green'] <= ($color2['green'] - $range) &&
  37. $color1['blue'] <= ($color2['blue'] - $range) &&
  38. $color1['alpha'] <= ($color2['alpha'] - $range))
  39. {
  40. return false;
  41. }
  42. else {
  43. return true;
  44. }
  45. }
Alles anzeigen

Klassen zum festhalten der Koordinaten

PHP-Quellcode

  1. /**
  2. * Helper class to store x y Koordinates
  3. */
  4. class Point {
  5. public $x = null;
  6. public $y = null;
  7. public function __construct($x, $y) {
  8. $this->x = $x;
  9. $this->y = $y;
  10. }
  11. }
  12. /**
  13. * Class to handle multiple points
  14. */
  15. class Matrix {
  16. public $points = [];
  17. public $width = 0;
  18. public $height = 0;
  19. public $paddingLeft = 0;
  20. public $paddingTop = 0;
  21. /**
  22. * Add a point object to the matrix
  23. *
  24. * @param Point $point
  25. */
  26. public function add(Point $p) {
  27. $this->points[] = $p;
  28. // If the point exceed the current width increase the width
  29. if ($p->x > $this->width) {
  30. $this->width = $p->x;
  31. }
  32. // If the point exceed the current height increase the height
  33. if ($p->y > $this->height) {
  34. $this->height = $p->y;
  35. }
  36. }
  37. /**
  38. * Delete all points on a x coordinate
  39. *
  40. * @param Integer $x
  41. */
  42. public function deleteColumn($x) {
  43. foreach ($this->points as $index => $point) {
  44. if ($point->x == $x)
  45. unset($this->points[$index]);
  46. }
  47. }
  48. /**
  49. * Get the matrix without whitespace
  50. *
  51. * @return Matrix A new Matrix object
  52. */
  53. public function getCleanMatrix() {
  54. // Get the top and left padding to skip whitespace
  55. $paddingTop = $this->getMinValue($this->points, 'y');
  56. $paddingLeft = $this->getMinValue($this->points, 'x');
  57. $points = [];
  58. $width = $this->width - $paddingLeft;
  59. $height = $this->height - $paddingTop;
  60. // Update the x and y coordinates with the missing whitespace
  61. foreach ($this->points as $point) {
  62. $point->x -= $paddingLeft;
  63. $point->y -= $paddingTop;
  64. $points[] = $point;
  65. }
  66. // Create a matrix without whitespace :)
  67. $matrix = new Matrix();
  68. $matrix->width = $width;
  69. $matrix->height = $height;
  70. $matrix->paddingTop = $paddingTop;
  71. $matrix->paddingLeft = $paddingLeft;
  72. $matrix->points = $points;
  73. return $matrix;
  74. }
  75. /**
  76. * Helper function to get the minimum value of a property
  77. *
  78. * @param Array<Object> $array Objects to get the in value from
  79. * @param String $property The property name on the objects
  80. * @return Integer The minimum value
  81. */
  82. private function getMinValue(array $array, $property) {
  83. $min = 0;
  84. foreach ($array as $element) {
  85. if ($element->{$property} < $min || !$min)
  86. $min = $element->{$property};
  87. }
  88. return $min;
  89. }
  90. }
Alles anzeigen

Charaktererkennung

Wir iterieren nun über die X und Y Achse, vergleichen den aktuellen Pixel mit den umliegenden Pixeln und erkennen, anhand der Unterschiede in den Farbwerten der Pixel, die Kanten der Charaktere.
Gehen wir davon aus das Kantenglättung genutzt wurde so müssen wir mittels compareColor prüfen ob ein Farbwert mit Abweichung die Zielfarbe trifft.
Wenn ein Pixel sich vom Hintergrund unterscheidet gehen wir erst mal davon aus dass dieser zu einem Charakter gehören kann.
Um nur die Kanten eines Charakters zu vergleichen, prüfen wir ob der aktuelle Pixel sich zu den umliegenden Pixeln unterscheidet.
Wenn das zutrifft fügen wir die Koordinaten des Pixels in eine Matrix ein.
Wir erkennen ob das Ende eines Charakters erreicht ist, in dem wir die Streuung der Pixel auf der Vertikalen Y Achse überprüfen.
Dafür Summieren wir alle Farbwerte der Pixel auf dieser Achse und teilen diesen Wert durch die Höhe des Bildes.
Ist die Abweichung dieses Wertes kleiner als 0.7% des Farbwertes vom Hintergrund, so gehen wir davon aus dass wir das Ende eines Charakters erreicht haben.
Diese Methode sorgt dafür das sich Charaktere geringfügig überschneiden dürfen.
Somit erkennt das PHP OCR Script z.B. auch die Charaktere F und A (FA) welche sich auf der Y Achse überlagern.

Pixel der Charaktere in Matrizen einteilen

PHP-Quellcode

  1. // Create new Matrix for collecting pixels
  2. $matrix = new Matrix();
  3. $width = imagesx($image);
  4. $height = imagesy($image);
  5. $background = getColor($image, 0, 0);
  6. $backgroundDecimal = imagecolorat($image, 0, 0);
  7. $characters = [];
  8. var_dump($width, $height);
  9. // Loop through x axis
  10. for ($x = 0; $x < $width; $x++) {
  11. $dispersion = 0;
  12. // Loop through y axis
  13. for ($y = 0; $y < $height; $y++) {
  14. // start with false because the color change
  15. $appendToMatrix = false;
  16. $dispersion += imagecolorat($image, $x, $y);
  17. // Collect all pixel values around the current location
  18. $color = getColor($image, $x, $y);
  19. $nextYColor = $y + 1 >= $height ? $color : getColor($image, $x, $y + 1);
  20. $nextXColor = $x + 1 >= $width ? $color : getColor($image, $x + 1, $y);
  21. $lastYColor = $y == 0 ? $color : getColor($image, $x, $y - 1);
  22. $lastXColor = $x == 0 ? $color : getColor($image, $x - 1, $y);
  23. // Check if the current pixel differ to the background
  24. if (!compareColor($color, $background)) {
  25. // Check if the current pixel differ to the last pixels
  26. if (!compareColor($color, $lastXColor) || !compareColor($color, $lastYColor)) {
  27. $appendToMatrix = true;
  28. }
  29. // Check if the current pixel differ to the next pixels
  30. if (!compareColor($color, $nextXColor) || !compareColor($color, $nextYColor)) {
  31. $appendToMatrix = true;
  32. }
  33. $appendToMatrix = true;
  34. }
  35. if ($appendToMatrix) {
  36. $matrix->add(new Point($x, $y));
  37. }
  38. }
  39. // if there are just 0.7 percent filled match a new character
  40. if ((2147483647 - ($dispersion / $height)) < $backgroundDecimal * 0.072) {
  41. $matrix->deleteColumn($x);
  42. // If we have enough points get the character without whitespace
  43. if (sizeof($matrix->points) > 10) {
  44. $characters[] = $matrix->getCleanMatrix();
  45. $matrix = new Matrix();
  46. }
  47. }
  48. }
Alles anzeigen

Matrix Vergleich

Nun haben wir die Pixel der Charaktere in Matrizen zerlegt.
Jetzt folgt das zuordnen der Charakter Matrizen zu den Werten.
Dafür vergleichen wir unsere Matrizen mit vordefinierten Matrizen.
Diese Matrizen stellen die Punkte auf den X und Y Achsen der Pixel unseres Musters dar und haben unterschiedlich hohe Werte auf den für sie passenden Koordinaten.

Matrix für den Buchstaben B

PHP-Quellcode

  1. 'B' => [
  2. 'pointAmount' => 1066,
  3. 'totalPoints' => 6313,
  4. 'matrix' => [
  5. [0,1,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,1,0,0,0,0,0,0,0,0,0,],
  6. [1,3,4,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5,5,5,4,3,2,2,0,0,0,0,0,0,0,],
  7. [2,4,6,7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,7,7,7,6,5,4,2,1,0,0,0,0,0,],
  8. [2,4,7,8,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,8,8,8,8,7,6,4,3,1,0,0,0,0,],
  9. [2,4,7,8,9,9,9,9,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,8,8,6,5,3,1,0,0,0,],
  10. [2,4,7,8,9,9,8,7,7,6,6,6,6,6,6,6,6,6,6,6,7,7,7,8,9,9,9,8,7,5,3,0,0,0,],
  11. [2,4,7,8,9,9,7,6,4,3,3,3,3,3,3,3,3,3,3,3,4,5,5,6,7,8,9,9,8,6,4,2,0,0,],
  12. [2,4,7,8,9,8,7,5,3,1,0,0,0,0,0,0,0,0,1,1,2,2,3,4,6,7,8,9,9,7,5,3,0,0,],
  13. [2,4,7,8,9,8,7,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,4,6,8,9,9,8,6,4,2,0,],
  14. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,5,7,8,9,8,7,5,2,0,],
  15. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,6,8,9,9,7,5,3,0,],
  16. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,6,8,9,9,8,6,3,0,],
  17. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,5,8,9,9,8,6,3,0,],
  18. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,6,8,9,9,8,5,3,0,],
  19. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,6,8,9,8,7,5,2,0,],
  20. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,7,8,9,8,6,4,2,0,],
  21. [2,4,7,8,9,8,7,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,6,7,8,8,7,5,3,0,0,],
  22. [2,4,7,8,9,8,7,4,2,1,0,0,0,0,0,0,0,0,0,0,1,2,2,4,5,7,8,8,8,6,4,2,0,0,],
  23. [2,4,7,8,9,9,7,5,4,3,2,2,2,2,2,2,2,2,3,3,3,4,5,6,7,8,8,8,6,4,2,0,0,0,],
  24. [2,4,7,8,9,9,8,7,6,5,5,5,5,5,5,5,5,5,5,5,6,6,7,8,8,8,8,6,5,3,1,0,0,0,],
  25. [2,4,7,8,9,9,9,8,8,7,7,7,7,7,7,7,7,7,7,8,8,8,8,9,9,8,7,5,3,2,0,0,0,0,],
  26. [2,4,7,8,9,9,9,9,9,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,8,7,5,4,2,0,0,0,0,],
  27. [2,4,7,8,9,9,9,9,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,8,7,5,3,2,0,0,0,],
  28. [2,4,7,8,9,9,8,8,7,7,6,6,6,6,6,6,6,6,6,7,7,7,7,8,8,8,8,8,7,6,4,2,0,0,],
  29. [2,4,7,8,9,9,8,6,5,4,4,4,4,4,4,4,4,4,4,4,4,5,5,6,7,8,8,8,8,7,6,4,2,0,],
  30. [2,4,7,8,9,8,7,5,3,2,2,1,1,1,1,1,1,1,2,2,2,2,3,4,5,6,7,8,9,8,7,6,3,1,],
  31. [2,4,7,8,9,8,7,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,4,6,7,8,9,8,7,5,2,],
  32. [2,4,7,8,9,8,6,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,6,8,9,9,8,6,3,],
  33. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,5,7,8,9,8,7,4,],
  34. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,6,8,9,9,7,5,],
  35. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,6,8,9,9,8,6,],
  36. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,5,8,9,9,8,6,],
  37. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,6,8,9,9,8,6,],
  38. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,6,8,9,9,8,5,],
  39. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,7,8,9,8,7,5,],
  40. [2,4,7,8,9,8,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,5,7,9,9,8,6,4,],
  41. [2,4,7,8,9,8,7,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,5,7,8,9,9,8,5,3,],
  42. [2,4,7,8,9,8,7,5,3,1,0,0,0,0,0,0,0,0,0,0,1,1,2,3,4,5,7,8,9,9,8,7,4,2,],
  43. [2,4,7,8,9,9,7,6,4,3,3,3,3,3,3,3,3,3,3,3,3,4,4,5,6,7,8,9,9,8,7,5,3,1,],
  44. [2,4,7,8,9,9,8,7,7,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,8,8,9,9,8,7,6,4,2,0,],
  45. [2,4,7,8,9,9,9,9,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,8,8,7,6,4,2,0,0,],
  46. [2,4,7,8,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,8,8,8,8,8,7,6,5,4,2,0,0,0,],
  47. [2,4,6,7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,7,7,7,7,6,5,4,3,2,0,0,0,0,],
  48. [1,3,4,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5,5,4,4,3,3,2,0,0,0,0,0,0,],
  49. ]
  50. ]
Alles anzeigen


Da unsere Matrizen höchst wahrscheinlich unterschiedliche Maße aufweisen, müssen wir diese in ein Verhältnis zu unseren im PHP OCR Script vordefinierten Matrizen setzen.
Nun prüfen wir den ins Verhältnis gesetzten Punkt und die umliegenden Punkte auf einen Treffer.
Von allen Pixeln nehmen wir nun den höchsten Wert und addieren diesen zu einer Variable welche den Treffer Wert des Charakters enthält.
Nachdem wir all unsere vordefinierten Matrizen verglichen haben ermitteln wir die Matrix mit dem höchsten Treffer wert.
Nun hat unser PHP OCR Script zu einer gewissen Wahrscheinlichkeit einen Charakter erkannt.

Matrizen mit vordefinierten Matrizen vergleichen

PHP-Quellcode

  1. $character = [];
  2. // Iterate through character matrices for matching
  3. foreach ($characters as $index => $char) {
  4. $matchedArray = [];
  5. // Iterate through our letter matrices and compare them to our character matrix
  6. foreach ($letters as $letter => $properties) {
  7. $matrix = $properties['matrix'];
  8. $matched = 0;
  9. // Calculate the relation of charcter matrix size to the letter matrix size
  10. $xRatio = (sizeof($matrix[0]) - 1) / $char->width;
  11. $yRatio = (sizeof($matrix) - 1) / $char->height;
  12. foreach ($char->points as $point) {
  13. $x = ($point->x * $xRatio);
  14. $y = ($point->y * $yRatio);
  15. // Check each point and its surrounding for matching the pixel
  16. $pointMatch = [];
  17. if (isset($matrix[$y][$x]))
  18. $pointMatch[] = $matrix[$y][$x];
  19. if (isset($matrix[$y+1][$x]))
  20. $pointMatch[] = $matrix[$y+1][$x];
  21. if (isset($matrix[$y-1][$x]))
  22. $pointMatch[] = $matrix[$y-1][$x];
  23. if (isset($matrix[$y][$x+1]))
  24. $pointMatch[] = $matrix[$y][$x+1];
  25. if (isset($matrix[$y][$x-1]))
  26. $pointMatch[] = $matrix[$y][$x-1];
  27. if (isset($matrix[$y+1][$x+1]))
  28. $pointMatch[] = $matrix[$y+1][$x+1];
  29. if (isset($matrix[$y+1][$x-1]))
  30. $pointMatch[] = $matrix[$y+1][$x-1];
  31. if (isset($matrix[$y-1][$x+1]))
  32. $pointMatch[] = $matrix[$y-1][$x+1];
  33. if (isset($matrix[$y-1][$x-1]))
  34. $pointMatch[] = $matrix[$y-1][$x-1];
  35. // Get the highest matching of all surroinding pixels
  36. $matched += max($pointMatch);
  37. }
  38. $matchedArray[$letter] = $matched;
  39. }
  40. // Sort the letters and get the best matching letter
  41. arsort($matchedArray);
  42. $letter = key($matchedArray);
  43. $match = reset($matchedArray);
  44. var_dump($letter, $match / (sizeof($char->points) / 100));
  45. }
Alles anzeigen

Schlusswort zum PHP OCR Script

Nun habt ihr also einen einfachen aber recht Fehler anfälligen Weg kennen gelernt wie man ein PHP OCR Script entwickeln kann.
Ihr werdet mit dem Aktuellen PHP OCR Script Probleme bei dem Vergleich mit der vordefinierten Matrix für den Buchstaben I haben.
Dieser Buchstabe hat nämlich keinen Whitespace und trifft, durch das „ins Verhältnis setzen der Koordinaten“, jeden Punkt.
Man könnte das reduzieren in dem man die Anzahl der gefilterten Punkte, mit den zu vergleichenden Punkten, mit in den Vergleich einbezieht.
Wie man sieht habe ich in der Matrix für den Buchstaben B bereits die Anzahl der gesamten Punkte und der maximal treffbaren Punkte eingetragen.
Damit könnte man den Treffer Wert ins Verhältnis zur gefilterten Matrix setzen und somit diesen Wert noch verfeinern.
Werbung
  • Es wurden noch keine Einträge an der Pinnwand verfasst.