* Copyright (c) 2004-2005 Platon SDG, http://platon.sk/ * Licensed under terms of GNU General Public License. * All rights reserved. * * Changelog: * 2004-11-20 - created * 2005-04-02 - released as an open source * - imported to Platon SDG CVS repositery * */ /* $Platon$ */ class Image_OCR { var $_debug = 0; var $_filename = null; var $_image = null; var $_width = null; var $_height = null; var $_max_color = null; var $_color_ar = null; function getWidth() /* {{{ */ { return $this->_width; } /* }}} */ function getHeight() /* {{{ */ { return $this->_height; } /* }}} */ function set($width, $height, $color_ar) { /* {{{ */ $this->_width = $width; $this->_height = $height; $this->_color_ar = $color_ar; } /* }}} */ function load($filename) /* {{{ */ { $this->_filename = $filename; return $this->reload(); } /* }}} */ function reload() /* {{{ */ { $this->_image = imagecreatefromjpeg($this->_filename); if (! $this->_image) { return false; } $this->_width = imagesx($this->_image); $this->_height = imagesy($this->_image); return true; } /* }}} */ function setBlackWhite() /* {{{ */ { if ($this->_image == null) { if ($this->reload() == false) { return false; } } for ($y = 0; $y < $this->_height; $y++) { for ($x = 0; $x < $this->_width; $x++) { $thiscol = imagecolorat($this->_image, $x, $y); $rgb = imagecolorsforindex($this->_image, $thiscol); $brightness = $rgb['red'] + $rgb['green'] + $rgb['blue']; $brightness = $brightness > 382 ? 1 : 0; $this->_color_ar[$x][$y] = $brightness; } } $this->_max_color = 1; return true; } /* }}} */ function invert() /* {{{ */ { if ($this->_color_ar == null) { $this->setBlackWhite(); } for ($y = 0; $y < $this->_height; $y++) { for ($x = 0; $x < $this->_width; $x++) { $this->_color_ar[$x][$y] = ! $this->_color_ar[$x][$y]; } } } /* }}} */ function readAscii($stream = null, $borders = true) /* {{{ */ { $str = ''; while ($tmp_data = fread($stream, 1024)) { $str .= $tmp_data; } $ar = split("\n", rtrim($str)); if ($borders) { array_shift($ar); array_pop($ar); $w_start = 1; } else { $w_start = 0; } $this->_width = strlen($ar[0]); $this->_height = count($ar); if ($borders) { $this->_width -= 2; } for ($y = 0; $y < $this->_height; $y++) { for ($x = 0; $x < $this->_width; $x++) { if ($ar[$y][$x + $w_start] == '.' || $ar[$y][$x + $w_start] == ' ') { $this->_color_ar[$x][$y] = 0; } else { $this->_color_ar[$x][$y] = 1; } } } } /* }}} */ function writeAscii($stream = null, $borders = true) /* {{{ */ { if ($this->_color_ar == null) { $this->setBlackWhite(); } $str = ''; if ($borders) { $str .= ' '; for ($x = 0; $x < $this->_width; $x++) { $str .= $x % 10; } $str .= "\n"; } for ($y = 0; $y < $this->_height; $y++) { $borders && $str .= $y % 10; for ($x = 0; $x < $this->_width; $x++) { if ($this->_color_ar[$x][$y]) { $str .= chr($this->_color_ar[$x][$y] + ord('A') - 1); //$str .= '*'; } else { $str .= '.'; } } $borders && $str .= $y % 10; $str .= "\n"; } if ($borders) { $str .= ' '; for ($x = 0; $x < $this->_width; $x++) { $str .= $x % 10; } $str .= "\n"; } if ($stream == null) { echo $str; } else { fputs($stream, $str); } } /* }}} */ function readAsciiFromFile($filename) /* {{{ */ { if (($f = @fopen($filename, 'r')) == false) { return false; } $this->readAscii($f, false); fclose($f); return true; } /* }}} */ function writeAsciiToFile($filename) /* {{{ */ { if (($f = fopen($filename, 'w')) == false) { trigger_error('fopen() failure'); exit; } $this->writeAscii($f, false); fclose($f); } /* }}} */ function writePHPcode($stream = null) /* {{{ */ { $sum = 0; $str = "array(\n"; for ($y = 0; $y < $this->_height; $y++) { $str .= sprintf(' %3d => array(', $y); for ($x = 0; $x < $this->_width; $x++) { $sum += $this->_color_ar[$y][$x]; $str .= $this->_color_ar[$y][$x]; $str .= ','; } $str .= "),\n"; } $str .= " 'filename' => '$this->_filename',\n"; $str .= " 'width' => $this->_width,\n"; $str .= " 'height' => $this->_height,\n"; $str .= " 'max_color' => $this->_max_color,\n"; $str .= " 'sum_color' => $sum,\n"; $str .= ");\n"; if ($stream == null) { echo $str; } else { fputs($stream, $str); } } /* }}} */ function trimAll() /* {{{ */ { $this->trimVertical(); $this->trimHorizontal(); } /* }}} */ function trimVertical() /* {{{ */ { for ($row_up = 0; $row_up < $this->_height; $row_up++) { $cnt = 0; for ($i = 0; $i < $this->_width; $i++) { $cnt += $this->_color_ar[$i][$row_up]; } if ($cnt != 0) break; } for ($row_down = $this->_height - 1; $row_down >= 0; $row_down--) { $cnt = 0; for ($i = 0; $i < $this->_width; $i++) { $cnt += $this->_color_ar[$i][$row_down]; } if ($cnt != 0) break; } $row_down = $this->_height - 1 - $row_down; if ($this->_debug) { printf('trimVertical(): row_up = %d, row_down = %d', $row_up, $row_down); echo "\n"; } if ($row_up == 0 && $row_down == 0) { return; } $this->_height -= $row_up + $row_down; for ($i = 0; $i < $this->_width; $i++) { $this->_color_ar[$i] = array_slice($this->_color_ar[$i], $row_up, $this->_height); } } /* }}} */ function trimHorizontal() /* {{{ */ { for ($col_up = 0; $col_up < $this->_width; $col_up++) { $cnt = 0; for ($i = 0; $i < $this->_height; $i++) { $cnt += $this->_color_ar[$col_up][$i]; } if ($cnt != 0) break; } for ($col_down = $this->_width - 1; $col_down >= 0; $col_down--) { $cnt = 0; for ($i = 0; $i < $this->_height; $i++) { $cnt += $this->_color_ar[$col_down][$i]; } if ($cnt != 0) break; } $col_down = $this->_width - 1 - $col_down; if ($this->_debug) { printf('trimHorizontal(): col_up = %d, col_down = %d', $col_up, $col_down); echo "\n"; } if ($col_up == 0 && $col_down == 0) { return; } $this->_width -= $col_up + $col_down; $this->_color_ar = array_slice($this->_color_ar, $col_up, $this->_width); } /* }}} */ function removeLines() /* {{{ */ { $this->removeVerticalLines(); $this->removeHorizontalLines(); } /* }}} */ function removeLinesIntelligent() /* {{{ */ { $ar_v = $this->_getRemoveVerticalLines(); $ar_h = $this->_getRemoveHorizontalLines(); $this->removeVerticalLines($ar_v); $this->removeHorizontalLines($ar_h); } /* }}} */ function removeVerticalLines($ar = null) /* {{{ */ { if ($ar == null) { $ar = $this->_getRemoveVerticalLines(); if (count($ar) == 0) { return; } } foreach ($ar as $i) { for ($j = 0; $j < $this->_height; $j++) { $this->_color_ar[$i][$j] = 0; } } } /* }}} */ function removeHorizontalLines($ar = null) /* {{{ */ { if ($ar == null) { $ar = $this->_getRemoveHorizontalLines(); if (count($ar) == 0) { return; } } foreach ($ar as $j) { for ($i = 0; $i < $this->_width; $i++) { $this->_color_ar[$i][$j] = 0; } } } /* }}} */ function _getRemoveVerticalLines() /* {{{ */ { $removed = array(); for ($i = 0; $i < $this->_width; $i++) { for ($sum = 0, $j = 0; $j < $this->_height; $j++) { $sum += $this->_color_ar[$i][$j]; } if ($sum == $this->_height) { // or 90% could be good $removed[] = $i; } } if ($this->_debug) { printf('_getRemoveVerticalLines(): %d removed', count($removed)); echo "\n"; } return $removed; } /* }}} */ function _getRemoveHorizontalLines() /* {{{ */ { $removed = array(); for ($j = 0; $j < $this->_height; $j++) { for ($sum = 0, $i = 0; $i < $this->_width; $i++) { $sum += $this->_color_ar[$i][$j]; } if ($sum == $this->_width) { // or 90% could be good $removed[] = $j; } } if ($this->_debug) { printf('_getRemoveHorizontalLines(): %d removed', count($removed)); echo "\n"; } return $removed; } /* }}} */ function fillAll() /* {{{ */ { $this->fillVertical(); $this->fillHorizontal(); } /* }}} */ function fillAllIntelligent() /* {{{ */ { $changed = -1; for ($i = 0; $last_changed != 0 || $changed != 0; $i++) { $last_changed = $changed; if ($i % 2) { $changed = $this->fillVertical(); } else { $changed = $this->fillHorizontal(); } } if ($this->_debug) { printf('fillAllIntelligent(): %d cycles', $i); echo "\n"; } } /* }}} */ function fillVertical() /* {{{ */ { $changed = 0; for ($i = 0; $i < $this->_width; $i++) { for ($j = 1; $j < $this->_height - 1; $j++) { if ($this->_color_ar[$i][$j - 1] && $this->_color_ar[$i][$j + 1] && ! $this->_color_ar[$i][$j]) { $this->_color_ar[$i][$j] = 1; $changed++; } } } return $changed; } /* }}} */ function fillHorizontal() /* {{{ */ { $changed = 0; for ($j = 0; $j < $this->_height; $j++) { for ($i = 1; $i < $this->_width - 1; $i++) { if ($this->_color_ar[$i - 1][$j] && $this->_color_ar[$i + 1][$j] && ! $this->_color_ar[$i][$j]) { $this->_color_ar[$i][$j] = 1; $changed++; } } } return $changed; } /* }}} */ function splitHorizontal($components) /* {{{ */ { $ret = array(); $comp_width = round($this->_width / $components); for ($i = 0; $i < $components; $i++) { $start = round(($this->_width / $components) * $i); $end = min($start + $comp_width, $this->_width - 1); $img = new Image_OCR; $img->set($end - $start, $this->_height, array_slice($this->_color_ar, $start, $end - $start)); $ret[] = $img; } return $ret; } /* }}} */ function splitHorizontalEmptyCols($min_size = 0) /* {{{ */ { $ret = array(); $this->trimHorizontal(); for ($last_zero_col = -1, $i = 0; $i <= $this->_width; $i++) { if ($i == $this->_width) { $sum = 0; // to force last component } else { for ($sum = 0, $j = 0; $j < $this->_height; $j++) { $sum += $this->_color_ar[$i][$j]; } } if ($sum == 0) { if ($i - ($last_zero_col + 1) > 0) { if ($i - ($last_zero_col + 1) >= $min_size || $i == $this->_width) { $img = new Image_OCR; $img_width = $i - ($last_zero_col + 1); $img->set($img_width, $this->_height, array_slice($this->_color_ar, $last_zero_col + 1, $img_width)); $ret[] = $img; $last_zero_col = $i; } } else { $last_zero_col = $i; } } } return $ret; } /* }}} */ function mul($coef) /* {{{ */ { $new_width = $this->_width * $coef; $new_height = $this->_height * $coef; $new_color_ar = array(); for ($y = 0; $y < $new_height; $y++) { for ($x = 0; $x < $new_width; $x++) { $new_color_ar[$x][$y] = $this->_color_ar[floor($x / $coef)][floor($y / $coef)]; } } $this->_width = $new_width; $this->_height = $new_height; $this->_color_ar = $new_color_ar; } /* }}} */ function extendTo($new_width, $new_height) /* {{{ */ { $this->extendToHeight($new_height); $this->extendToWidth($new_width); } /* }}} */ function extendToWidth($new_width) /* {{{ */ { if ($new_width <= $this->_width) return; $add = $new_width - $this->_width; $add_up = (int) floor($add / 2); $add_down = (int) ceil($add / 2); assert($add == $add_up + $add_down); $add_ar = array_fill(0, $this->_height, 0); $add_up = $add_up ? array_fill(0, $add_up, $add_ar) : array(); $add_down = $add_down ? array_fill(0, $add_down, $add_ar) : array(); $this->_color_ar = array_merge( $add_up, $this->_color_ar, $add_down); $this->_width += $add; } /* }}} */ function extendToHeight($new_height) /* {{{ */ { if ($new_height <= $this->_height) return; $add = $new_height - $this->_height; $add_up = (int) floor($add / 2); $add_down = (int) ceil($add / 2); assert($add == $add_up + $add_down); $add_up = $add_up ? array_fill(0, $add_up, 0) : array(); $add_down = $add_down ? array_fill(0, $add_down, 0) : array(); for ($i = 0; $i < $this->_width; $i++) { $this->_color_ar[$i] = array_merge( $add_up, $this->_color_ar[$i], $add_down); } $this->_height += $add; } /* }}} */ function mergeWith($ocr_obj) /* {{{ */ { if ($this->_width != $ocr_obj->getWidth()) { trigger_error('bad mergeWith() usage ['.$this->_width.', '.$ocr_obj->getWidth().']'); exit; // TODO: better error handling } if ($this->_height != $ocr_obj->getHeight()) { trigger_error('bad mergeWith() usage ['.$this->_height.', '.$ocr_obj->getHeight().']'); exit; // TODO: better error handling } for ($y = 0; $y < $this->_height; $y++) { for ($x = 0; $x < $this->_width; $x++) { $this->_color_ar[$x][$y] += $ocr_obj->_color_ar[$x][$y]; } } } /* }}} */ function normalize($top = 100) /* {{{ */ { $max_val = 1; for ($y = 0; $y < $this->_height; $y++) { for ($x = 0; $x < $this->_width; $x++) { if ($this->_color_ar[$x][$y] > $max_val) $max_val = $this->_color_ar[$x][$y]; } } for ($y = 0; $y < $this->_height; $y++) { for ($x = 0; $x < $this->_width; $x++) { $this->_color_ar[$x][$y] = ceil( ($this->_color_ar[$x][$y] / $max_val) * $top); } } $this->_max_color = $top; } /* }}} */ function isAround($x, $y) /* {{{ */ { return (int) $this->_color_ar[$x - 1][$y - 1] + (int) $this->_color_ar[$x - 1][$y ] + (int) $this->_color_ar[$x - 1][$y + 1] + (int) $this->_color_ar[$x ][$y - 1] + (int) $this->_color_ar[$x ][$y ] + (int) $this->_color_ar[$x ][$y + 1] + (int) $this->_color_ar[$x + 1][$y - 1] + (int) $this->_color_ar[$x + 1][$y ] + (int) $this->_color_ar[$x + 1][$y + 1]; } /* }}} */ function doOCR($ocr_ar, $debug = false) /* {{{ */ { foreach (array_keys($ocr_ar) as $letter) { $ocr_ar[$letter]['cur_sum'] = 0; $ocr_ar[$letter]['match_pix_ok_cnt'] = 0; $ocr_ar[$letter]['match_pix_ok_sum'] = 0; $ocr_ar[$letter]['match_pix_bad_cnt'] = 0; $ocr_ar[$letter]['match_pix_bad_sum'] = 0; $ocr_ar[$letter]['unmatch_pix_cnt'] = 0; $ocr_ar[$letter]['unmatch_pix_sum'] = 0; $this->extendTo($ocr_ar[$letter]['width'], $ocr_ar[$letter]['height']); if (0&&$debug) { echo "\n***[$letter]***\n"; $cnt = 0; } for ($y = 0; $y < $this->_height; $y++) { for ($x = 0; $x < $this->_width; $x++) { if ($this->_color_ar[$x][$y]) { if ($ocr_ar[$letter][$x][$y] > 0) { $ocr_ar[$letter]['match_pix_ok_cnt']++; $ocr_ar[$letter]['match_pix_ok_sum'] += $ocr_ar[$letter][$x][$y]; } else { $ocr_ar[$letter]['match_pix_bad_cnt']++; $ocr_ar[$letter]['match_pix_bad_sum']++; } } else if (0 && $this->isAround($x, $y) > 0) { // disabled // do nothing } else { if ($ocr_ar[$letter][$x][$y] > 0) { $ocr_ar[$letter]['unmatch_pix_cnt']++; $ocr_ar[$letter]['unmatch_pix_sum'] += $ocr_ar[$letter][$x][$y]; } } } } $ocr_ar[$letter]['cur_sum'] += $ocr_ar[$letter]['match_pix_ok_sum']; $ocr_ar[$letter]['cur_sum'] -= $ocr_ar[$letter]['unmatch_pix_sum']; $ocr_ar[$letter]['cur_sum'] -= $ocr_ar[$letter]['match_pix_bad_cnt'] * ($ocr_ar[$letter]['top_color'] / 5 * 3); } $ret = '?'; $max_sum = -100000; foreach (array_keys($ocr_ar) as $letter) { $cur_pct = ($ocr_ar[$letter]['cur_sum'] / $ocr_ar[$letter]['sum_color']) * 100; $cur_sum = $ocr_ar[$letter]['cur_sum']; if ($cur_sum > $max_sum) { $ret = $letter; $max_sum = $cur_sum; } if ($this->_debug || $debug) { printf('%s: sum=%07.2f pct=%07.2f sumc=%.5d ', $letter, $cur_sum, $cur_pct, $ocr_ar[$letter]['sum_color']); printf('(match_pix_ok=%d[%.2f], match_pix_bad=%d[%.2f], unmatch_pix=%d[%.2f])', $ocr_ar[$letter]['match_pix_ok_cnt'], $ocr_ar[$letter]['match_pix_ok_sum'], $ocr_ar[$letter]['match_pix_bad_cnt'], $ocr_ar[$letter]['match_pix_bad_sum'], $ocr_ar[$letter]['unmatch_pix_cnt'], $ocr_ar[$letter]['unmatch_pix_sum']); echo "\n"; } } return $ret; } /* }}} */ } /* Modeline for ViM {{{ * vim: set ts=4: * vim600: fdm=marker fdl=0 fdc=0: * }}} */ ?>