Home  >  Article  >  Backend Development  >  PHP OCR实战:用Tesseract从图像中读取文字_PHP教程

PHP OCR实战:用Tesseract从图像中读取文字_PHP教程

WBOY
WBOYOriginal
2016-07-12 09:00:531728browse

PHP OCR实战:用Tesseract从图像中读取文字

Optical Character Recognition (OCR)即光学字符辨识是把打印文本转换成一个数字表示的过程。它有各种各样的实际应用–从数字化印刷书籍、创建收据的电子记录,到车牌识别甚至破解基于图像的验证码。

Robotic eye

Tesseract是一个能实现OCR的开源项目。你能在*Nix系统,Mac系统和Windows系统上运行这个项目,但是只要使用一个库,我们就能在PHP项目中使用它了。本教程的目的是教你如何使用。

安装

准备

为了让事情变得简单和一致的, 我们将使用虚拟机本文使用Vagrant)来运行应用程序,这会涉及到安装PHP和Nginx,我们将安装 Tesseract来分别演示过程。如果你想自己基于现有Debian-based系统安装Tesseract,你可以跳过下一部分—或者查看the README来获得在其他*nix上,Mac系统或者Windows的安装指导.

配置Vagrant

为了配置Vagrant以跟上本教程,完成如下步骤。或者你也可以简单的从Github获得代码。

输入以下命令来下载Homestead Improved Vagrant配置到一个名为orc的文件夹:

git clone https://github.com/Swader/homestead_improved ocr

将Nginx配置文件Homestead.yml中的以下代码:

<ol class="dp-c"><li class="alt"><span><span>sites: </span></span></li><li><span>    - map: homestead.app </span></li><li class="alt"><span>      to: /home/vagrant/Code/Project/<span class="keyword">public</span><span> </span></span></li></ol>

修改成:

<ol class="dp-c"><li class="alt"><span><span>sites: </span></span></li><li><span>    - map: homestead.app </span></li><li class="alt"><span>      to: /home/vagrant/Code/<span class="keyword">public</span><span> </span></span></li></ol>

同样要在hosts文件中添加

<ol class="dp-c"><li class="alt"><span><span>192.168.10.10       homestead.app </span></span></li></ol>

安装Tesseract

下一步是安装Tesseract

因为Homestead Improved 使用debian,我们可以在使用vagrant ssh登陆虚拟机后使用apt-get 来安装它,简单运行如下命令:

<ol class="dp-c"><li class="alt"><span><span>sudo apt-get install tesseract-ocr </span></span></li></ol>

正如上文提到的,在the README中有其他的操作系统对应教程。

测试并定制安装

我们将使用PHP包装,但是之前我们可以在命令行测试Tesseract。

首先保存这个图片sign.png

在虚拟机中,执行如下命令来从图片中读取文字

<ol class="dp-c"><li class="alt"><span><span>tesseract sign.png out </span></span></li></ol>

这将在当前文件夹创建一个文件:out.txt里面应该有单词:CAUTION

现在尝试sign2.jpg

<ol class="dp-c"><li class="alt"><span><span>tesseract sign2.jpg out </span></span></li></ol>

这次产生单词Einbahnstral’ie。很接近但不正确—虽然图像中的文字相当清晰,它没能识别字符ß。

为了获使Tesseract正常读取字符串,我们需要安装一些新的语言文件—就本例来说,德语。

这里有一个全面的可用语言文件列表,但我们直接下载所需的文件:

<ol class="dp-j"><li class="alt"><span><span>wget https:</span><span class="comment">//tesseract-ocr.googlecode.com/files/tesseract-ocr-3.02.deu.tar.gz</span><span> </span></span></li></ol>

解压:

<ol class="dp-c"><li class="alt"><span><span>tar zxvf tesseract-ocr-3.02.deu.tar.gz </span></span></li></ol>

然后把文件复制到如下目录:

<ol class="dp-c"><li class="alt"><span><span>/usr/share/tesseract-ocr/tessdata </span></span></li></ol>

例如

<ol class="dp-c"><li class="alt"><span><span>cp deu-frak.traineddata /usr/share/tesseract-ocr/tessdata </span></span></li><li><span>cp deu.traineddata /usr/share/tesseract-ocr/tessdata </span></li></ol>

现在我们再次执行原来的命令但是要用 –l

<ol class="dp-j"><li class="alt"><span><span>tesseract sign2.jpg out -l deu </span></span></li><li><span> </span></li><li class="alt"><span>    &ldquo;deu&rdquo; 是德语的 ISO <span class="number">639</span><span>-</span><span class="number">3</span><span>码. </span></span></li></ol>

这次,文字应该是Einbahnstraße正确的)。

可以通过重复上述过程来使用任意语言。

配置应用程序

我们将使用这个库来用PHP使用Tesseract。

我们将建立一个极简的web应用:用户上传图片,并查看OCR处理结果。我们将使用Silex microframework 来实现。不要担心你不熟悉它,这个应用本身很简单。

记住这篇教程的所有代码都能在Github上获得。

第一步是用Composer来安装依赖文件:

<ol class="dp-c"><li class="alt"><span><span>composer </span><span class="keyword">require</span><span> silex/silex twig/twig thiagoalessio/tesseract_ocr:dev-master </span></span></li></ol>

然后建立三个文件夹:

<ol class="dp-c"><li class="alt"><span><span>- </span><span class="keyword">public</span><span> </span></span></li><li><span>- uploads </span></li><li class="alt"><span>- views </span></li></ol>

我们需要上传表单(views\index.twig):

<ol class="dp-c"><li class="alt"><span><span><html> </span></span></li><li><span>  <head> </span></li><li class="alt"><span>    <title>OCR</title> </span></li><li><span>  </head> </span></li><li class="alt"><span>  <body> </span></li><li><span> </span></li><li class="alt"><span>    <form action=<span class="string">""</span><span> method=</span><span class="string">"post"</span><span> enctype=</span><span class="string">"multipart/form-data"</span><span>> </span></span></li><li><span>      <input type=<span class="string">"file"</span><span> name=</span><span class="string">"upload"</span><span>> </span></span></li><li class="alt"><span>      <input type=<span class="string">"submit"</span><span>> </span></span></li><li><span>    </form> </span></li><li class="alt"><span> </span></li><li><span>  </body> </span></li><li class="alt"><span></html> </span></li></ol>

需要一个结果展示页面(views\results.twig)::

<ol class="dp-c"><li class="alt"><span><span><html> </span></span></li><li><span>  <head> </span></li><li class="alt"><span>    <title>OCR</title> </span></li><li><span>  </head> </span></li><li class="alt"><span>  <body> </span></li><li><span> </span></li><li class="alt"><span>    <h2>Results</h2> </span></li><li><span> </span></li><li class="alt"><span>    <textarea cols=<span class="string">"50"</span><span> rows=</span><span class="string">"10"</span><span>>{{ text }}</textarea> </span></span></li><li><span> </span></li><li class="alt"><span>    <hr> </span></li><li><span> </span></li><li class="alt"><span>    <a href=<span class="string">"/"</span><span>>&larr; Go back</a> </span></span></li><li><span> </span></li><li class="alt"><span>  </body> </span></li><li><span></html> </span></li></ol>

现在建立skeleton Silex app (public\index.php):

<ol class="dp-c"><li class="alt"><span><span><php </span></span></li><li><span> </span></li><li class="alt"><span><span class="keyword">require</span><span> __DIR__.</span><span class="string">'/../vendor/autoload.php'</span><span>; </span></span></li><li><span> </span></li><li class="alt"><span><span class="keyword">use</span><span> Symfony\Component\HttpFoundation\Request; </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$app</span><span> = </span><span class="keyword">new</span><span> Silex\Application(); </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$app</span><span>->register(</span><span class="keyword">new</span><span> Silex\Provider\TwigServiceProvider(), [ </span></span></li><li><span>  <span class="string">'twig.path'</span><span> => __DIR__.</span><span class="string">'/../views'</span><span>, </span></span></li><li class="alt"><span>]); </span></li><li><span> </span></li><li class="alt"><span><span class="vars">$app</span><span>[</span><span class="string">'debug'</span><span>] = true; </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$app</span><span>->get(</span><span class="string">'/'</span><span>, </span><span class="keyword">function</span><span>() </span><span class="keyword">use</span><span> (</span><span class="vars">$app</span><span>) { </span></span></li><li><span> </span></li><li class="alt"><span>  <span class="keyword">return</span><span> </span><span class="vars">$app</span><span>[</span><span class="string">'twig'</span><span>]->render(</span><span class="string">'index.twig'</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>}); </span></li><li><span> </span></li><li class="alt"><span><span class="vars">$app</span><span>->post(</span><span class="string">'/'</span><span>, </span><span class="keyword">function</span><span>(Request </span><span class="vars">$request</span><span>) </span><span class="keyword">use</span><span> (</span><span class="vars">$app</span><span>) { </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="comment">// TODO</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>}); </span></li><li><span> </span></li><li class="alt"><span><span class="vars">$app</span><span>->run(); </span></span></li></ol>

如果你在浏览器访问这个应用,你应该能看到一个文件上传表单。如果你在使用Homestead Improved Vagrant,你可以通过如下链接访问该应用。

<ol class="dp-c"><li class="alt"><span><span>http:</span><span class="comment">//homestead.app/</span><span> </span></span></li></ol>

下一步是实现文件上传。Silex使得这项工作非常简单;$request包含一个files组件,我们可以通过它来获得任意上传的文件,代码:

<ol class="dp-c"><li class="alt"><span><span class="comment">// Grab the uploaded file</span><span> </span></span></li><li><span><span class="vars">$file</span><span> = </span><span class="vars">$request</span><span>->files->get(</span><span class="string">'upload'</span><span>); </span></span></li><li class="alt"><span> </span></li><li><span><span class="comment">// Extract some information about the uploaded file</span><span> </span></span></li><li class="alt"><span><span class="vars">$info</span><span> = </span><span class="keyword">new</span><span> SplFileInfo(</span><span class="vars">$file</span><span>->getClientOriginalName()); </span></span></li><li><span> </span></li><li class="alt"><span><span class="comment">// Create a quasi-random filename</span><span> </span></span></li><li><span><span class="vars">$filename</span><span> = sprintf(</span><span class="string">'%d.%s'</span><span>, time(), </span><span class="vars">$info</span><span>->getExtension()); </span></span></li><li class="alt"><span> </span></li><li><span><span class="comment">// Copy the file</span><span> </span></span></li><li class="alt"><span><span class="vars">$file</span><span>->move(__DIR__.</span><span class="string">'/../uploads'</span><span>, </span><span class="vars">$filename</span><span>); </span></span></li></ol>

如你所见,我们产生随机文件名来减少文件名冲突—但在本应用中,我们怎么命名文件是不重要的。一旦我们在本地有一份文件拷贝,我们就可以产生一个Tessearct库的实例,然后进行分析:

<ol class="dp-c"><li class="alt"><span><span class="comment">// Instantiate the Tessearct library</span><span> </span></span></li><li><span><span class="vars">$tesseract</span><span> = </span><span class="keyword">new</span><span> TesseractOCR(__DIR__ . </span><span class="string">'/../uploads/'</span><span> . </span><span class="vars">$filename</span><span>); </span></span></li></ol>

在图像上实现OCR相当简单,我们只需调用方法recognize()。

<ol class="dp-c"><li class="alt"><span><span class="comment">// Perform OCR on the uploaded image</span><span> </span></span></li><li><span><span class="vars">$text</span><span> = </span><span class="vars">$tesseract</span><span>->recognize(); </span></span></li></ol>

最后我们把结果展示到结果页面:

<ol class="dp-c"><li class="alt"><span><span class="keyword">return</span><span> </span><span class="vars">$app</span><span>[</span><span class="string">'twig'</span><span>]->render( </span></span></li><li><span>    <span class="string">'results.twig'</span><span>, </span></span></li><li class="alt"><span>    [ </span></li><li><span>        <span class="string">'text'</span><span>  =>  </span><span class="vars">$text</span><span>, </span></span></li><li class="alt"><span>    ] </span></li><li><span>); </span></li></ol>

在一些图片上尝试,看看它效果怎样。如果你有困难,可以参考这个

一个实际的例子

让我们来看OCR一个更实用的例子。在本例中,我们尝试在图像中找到一个格式化的电话号码。

看看下面一幅图,上传到你的应用:

结果应该如下:

<ol class="dp-c"><li class="alt"><span><span>:ii&lsquo;i </span></span></li><li><span>Customer Service Helplines </span></li><li class="alt"><span> </span></li><li><span>British Airways Helpline </span></li><li class="alt"><span> </span></li><li><span>09040 490 541 </span></li></ol>

它没有挑出正文文本,这是我们能料到的,因为图片质量太差。虽然识别了号码但是也有一些“噪声”。

为了提取相关信息,有如下几件事我们可以做。

你可以让Tesseract 把它的结果限制在一定的字符集内,所以我们告诉它只返回数字型的内容代码如下:

<ol class="dp-c"><li class="alt"><span><span class="vars">$tesseract</span><span>->setWhitelist(range(0,9)); </span></span></li></ol>

但这样有个问题。它常常把非数字字符解释成数字而非忽略它们。比如“Bob”可能被解释称数字“808”。

所以我们采用两步处理。

第一步,我们可以用一个基本的正则表达式。可以用谷歌电话库来确定一个数字串是否是合法电话号码。

备注:我已在Sitepoint 写过关于谷歌电话库的内容。

让我们给谷歌电话库添加一个PHP 端口,修改composer.json,添加:

<ol class="dp-c"><li class="alt"><span><span class="string">"giggsey/libphonenumber-for-php"</span><span>: </span><span class="string">"~7.0"</span><span> </span></span></li></ol>

别忘了升级:

<ol class="dp-c"><li class="alt"><span><span>composer update </span></span></li></ol>

现在我们可以写一个函数,输入为一个字符串,尝试提取一个合法的电话号码

<ol class="dp-c"><li class="alt"><span><span class="comment">/**</span> </span></li><li><span><span class="comment">* Parse a string, trying to find a valid telephone number. As soon as it finds a</span> </span></li><li class="alt"><span><span class="comment">* valid number, it'll return it in E1624 format. If it can't find any, it'll</span> </span></li><li><span><span class="comment">* simply return NULL.</span> </span></li><li class="alt"><span><span class="comment">*</span> </span></li><li><span><span class="comment">* @param  string   $text           The string to parse</span> </span></li><li class="alt"><span><span class="comment">* @param  string   $country_code   The two digit country code to use as a "hint"</span> </span></li><li><span><span class="comment">* @return string | NULL</span> </span></li><li class="alt"><span><span class="comment">*/</span><span> </span></span></li><li><span><span class="keyword">function</span><span> findPhoneNumber(</span><span class="vars">$text</span><span>, </span><span class="vars">$country_code</span><span> = </span><span class="string">'GB'</span><span>) { </span></span></li><li class="alt"><span> </span></li><li><span>  <span class="comment">// Get an instance of Google's libphonenumber</span><span> </span></span></li><li class="alt"><span>  <span class="vars">$phoneUtil</span><span> = \libphonenumber\PhoneNumberUtil::getInstance(); </span></span></li><li><span> </span></li><li class="alt"><span>  <span class="comment">// Use a simple regular expression to try and find candidate phone numbers</span><span> </span></span></li><li><span>  preg_match_all(<span class="string">'/(\+\d+)?\s*(\(\d+\))?([\s-]?\d+)+/'</span><span>, </span><span class="vars">$text</span><span>, </span><span class="vars">$matches</span><span>); </span></span></li><li class="alt"><span> </span></li><li><span>  <span class="comment">// Iterate through the matches</span><span> </span></span></li><li class="alt"><span>  <span class="keyword">foreach</span><span> (</span><span class="vars">$matches</span><span> </span><span class="keyword">as</span><span> </span><span class="vars">$match</span><span>) { </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="keyword">foreach</span><span> (</span><span class="vars">$match</span><span> </span><span class="keyword">as</span><span> </span><span class="vars">$value</span><span>) { </span></span></li><li><span> </span></li><li class="alt"><span>      try { </span></li><li><span> </span></li><li class="alt"><span>        <span class="comment">// Attempt to parse the number</span><span> </span></span></li><li><span>        <span class="vars">$number</span><span> = </span><span class="vars">$phoneUtil</span><span>->parse(trim(</span><span class="vars">$value</span><span>), </span><span class="vars">$country_code</span><span>);    </span></span></li><li class="alt"><span> </span></li><li><span>        <span class="comment">// Just because we parsed it successfully, doesn't make it vald - so check it</span><span> </span></span></li><li class="alt"><span>        <span class="keyword">if</span><span> (</span><span class="vars">$phoneUtil</span><span>->isValidNumber(</span><span class="vars">$number</span><span>)) { </span></span></li><li><span> </span></li><li class="alt"><span>          <span class="comment">// We've found a telephone number. Format using E.164, and exit</span><span> </span></span></li><li><span>          <span class="keyword">return</span><span> </span><span class="vars">$phoneUtil</span><span>->format(</span><span class="vars">$number</span><span>, \libphonenumber\PhoneNumberFormat::E164); </span></span></li><li class="alt"><span> </span></li><li><span>        } </span></li><li class="alt"><span> </span></li><li><span>      } catch (\libphonenumber\NumberParseException <span class="vars">$e</span><span>) { </span></span></li><li class="alt"><span> </span></li><li><span>        <span class="comment">// Ignore silently; getting here simply means we found something that isn't a phone number</span><span> </span></span></li><li class="alt"><span> </span></li><li><span>      } </span></li><li class="alt"><span> </span></li><li><span>    } </span></li><li class="alt"><span>  } </span></li><li><span> </span></li><li class="alt"><span>  <span class="keyword">return</span><span> null; </span></span></li><li><span> </span></li><li class="alt"><span>} </span></li></ol>

希望注释能解释这个函数在干什么。注意如果这个库没能从字符串中解析出一个合法的电话号码它会抛出一个异常。这不是什么问题;我们直接忽略它并继续下一个候选字符。

如果我们找到一个电话号码,我们以E.164的形式返回它。这提供了一个国际化的号码,我们可以用来打电话或者发送SMS。

现在我们可以如下使用:

<ol class="dp-c"><li class="alt"><span><span class="vars">$text</span><span> = </span><span class="vars">$tesseract</span><span>->recognize(); </span></span></li><li><span><span class="vars">$number</span><span> = findPhoneNumber(</span><span class="vars">$text</span><span>, </span><span class="string">'GB'</span><span>); </span></span></li></ol>

我们需要给谷歌电话库提供一个提示来说明这个号码是哪个国家的。你也可以改成你自己的国家。

我们把所有的这些打包在一个新的路由中:

<ol class="dp-c"><li class="alt"><span><span class="vars">$app</span><span>->post(</span><span class="string">'/identify-telephone-number'</span><span>, </span><span class="keyword">function</span><span>(Request </span><span class="vars">$request</span><span>) </span><span class="keyword">use</span><span> (</span><span class="vars">$app</span><span>) { </span></span></li><li><span> </span></li><li class="alt"><span>  <span class="comment">// Grab the uploaded file</span><span> </span></span></li><li><span>  <span class="vars">$file</span><span> = </span><span class="vars">$request</span><span>->files->get(</span><span class="string">'upload'</span><span>); </span></span></li><li class="alt"><span> </span></li><li><span>  <span class="comment">// Extract some information about the uploaded file</span><span> </span></span></li><li class="alt"><span>  <span class="vars">$info</span><span> = </span><span class="keyword">new</span><span> SplFileInfo(</span><span class="vars">$file</span><span>->getClientOriginalName()); </span></span></li><li><span> </span></li><li class="alt"><span>  <span class="comment">// Create a quasi-random filename</span><span> </span></span></li><li><span>  <span class="vars">$filename</span><span> = sprintf(</span><span class="string">'%d.%s'</span><span>, time(), </span><span class="vars">$info</span><span>->getExtension()); </span></span></li><li class="alt"><span> </span></li><li><span>  <span class="comment">// Copy the file</span><span> </span></span></li><li class="alt"><span>  <span class="vars">$file</span><span>->move(__DIR__.</span><span class="string">'/../uploads'</span><span>, </span><span class="vars">$filename</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>  <span class="comment">// Instantiate the Tessearct library</span><span> </span></span></li><li><span>  <span class="vars">$tesseract</span><span> = </span><span class="keyword">new</span><span> TesseractOCR(__DIR__ . </span><span class="string">'/../uploads/'</span><span> . </span><span class="vars">$filename</span><span>); </span></span></li><li class="alt"><span> </span></li><li><span>  <span class="comment">// Perform OCR on the uploaded image</span><span> </span></span></li><li class="alt"><span>  <span class="vars">$text</span><span> = </span><span class="vars">$tesseract</span><span>->recognize(); </span></span></li><li><span> </span></li><li class="alt"><span>  <span class="vars">$number</span><span> = findPhoneNumber(</span><span class="vars">$text</span><span>, </span><span class="string">'GB'</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>  <span class="keyword">return</span><span> </span><span class="vars">$app</span><span>->json( </span></span></li><li><span>    [ </span></li><li class="alt"><span>      <span class="string">'number'</span><span>     =>  </span><span class="vars">$number</span><span>, </span></span></li><li><span>    ] </span></li><li class="alt"><span>  ); </span></li><li><span> </span></li><li class="alt"><span>}); </span></li></ol>

我们现在有简单的API的基础—-也就是JSON响应-—我们可以用来作为一个简单的移动应用的后端,这款应用可以用来从一幅图中添加联系人,打电话。

总结

OCR有许多应用——并且很容易整合进你的应用超过你的预期)。本文中,我们安装了开源OCR包;并使用一个包装器库,把它整合进一个非常简单的PHP应用。我们只是触及到了所有可能性的表面,希望这能给你一些想法,帮你想想怎么在你自己的应用中使用OCR。

译文链接:http://www.codeceo.com/article/php-ocr-tesseract-get-text.html
英文原文:OCR in PHP: Read Text from Images with Tesseract

www.bkjia.comtruehttp://www.bkjia.com/PHPjc/1091544.htmlTechArticlePHP OCR实战:用Tesseract从图像中读取文字 OpticalCharacterRecognition(OCR)即光学字符辨识是把打印文本转换成一个数字表示的过程。它有各种各样的...
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn