PHP OCR实战:用Tesseract从图像中读取文字
Optical Character Recognition (OCR)即光学字符辨识是把打印文本转换成一个数字表示的过程。它有各种各样的实际应用–从数字化印刷书籍、创建收据的电子记录,到车牌识别甚至破解基于图像的验证码。
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中的以下代码:
sites: - map: homestead.app to: /home/vagrant/Code/Project/public
修改成:
sites: - map: homestead.app to: /home/vagrant/Code/public
同样要在hosts文件中添加
192.168.10.10 homestead.app
安装Tesseract
下一步是安装Tesseract
因为Homestead Improved 使用debian,我们可以在使用vagrant ssh登陆虚拟机后使用apt-get 来安装它,简单运行如下命令:
sudo apt-get install tesseract-ocr
正如上文提到的,在 the README 中有其他的操作系统对应教程。
测试并定制安装
我们将使用PHP包装,但是之前我们可以在命令行测试Tesseract。
首先保存 这个图片sign.png
在虚拟机中,执行如下命令来从图片中读取文字
tesseract sign.png out
这将在当前文件夹创建一个文件:out.txt里面应该有单词:CAUTION
现在尝试 sign2.jpg
tesseract sign2.jpg out
这次产生单词Einbahnstral’ie。很接近但不正确—虽然图像中的文字相当清晰,它没能识别字符ß。
为了获使Tesseract正常读取字符串,我们需要安装一些新的语言文件—就本例来说,德语。
这里有一个全面的可用 语言文件列表 ,但我们直接下载所需的文件:
wget https://tesseract-ocr.googlecode.com/files/tesseract-ocr-3.02.deu.tar.gz
解压:
tar zxvf tesseract-ocr-3.02.deu.tar.gz
然后把文件复制到如下目录:
/usr/share/tesseract-ocr/tessdata
例如
cp deu-frak.traineddata /usr/share/tesseract-ocr/tessdata cp deu.traineddata /usr/share/tesseract-ocr/tessdata
现在我们再次执行原来的命令但是要用 –l
tesseract sign2.jpg out -l deu
“deu” 是德语的 ISO 639-3码.
这次,文字应该是Einbahnstraße(正确的)。
可以通过重复上述过程来使用任意语言。
配置应用程序
我们将使用 这个库 来用PHP使用Tesseract。
我们将建立一个极简的web应用:用户上传图片,并查看OCR处理结果。我们将使用 Silex microframework 来实现。不要担心你不熟悉它,这个应用本身很简单。
记住这篇教程的所有代码都能在 Github 上获得。
第一步是用Composer来安装依赖文件:
composer require silex/silex twig/twig thiagoalessio/tesseract_ocr:dev-master
然后建立三个文件夹:
- public - uploads - views
我们需要上传表单( vi ews\index.twig):
<html> <head> <title>OCR</title> </head> <body> <form action="" method="post" enctype="multipart/form-data"> <input type="file" name="upload"> <input type="submit"> </form> </body> </html>
需要一个结果展示页面(views\results.twig)::
<html> <head> <title>OCR</title> </head> <body> <h2>Results</h2> <textarea cols="50" rows="10">{{ text }}</textarea> <hr> <a href="/">← Go back</a> </body> </html>
现在建立skeleton Silex app (public\index.php):
<?php require __DIR__.'/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; $app = new Silex\Application(); $app->register(new Silex\Provider\TwigServiceProvider(), [ 'twig.path' => __DIR__.'/../views', ]); $app['debug'] = true; $app->get('/', function() use ($app) { return $app['twig']->render('index.twig'); }); $app->post('/', function(Request $request) use ($app) { // TODO }); $app->run();
如果你在浏览器访问这个应用,你应该能看到一个文件上传表单。如果你在使用Homestead Improved Vagrant,你可以通过如下链接访问该应用。
http://homestead.app/
下一步是实现文件上传。Silex使得这项工作非常简单;$request包含一个files组件,我们可以通过它来获得任意上传的文件,代码:
// Grab the uploaded file $file = $request->files->get('upload'); // Extract some information about the uploaded file $info = new SplFileInfo($file->getClientOriginalName()); // Create a quasi-random filename $filename = sprintf('%d.%s', time(), $info->getExtension()); // Copy the file $file->move(__DIR__.'/../uploads', $filename);
如你所见,我们产生随机文件名来减少文件名冲突—但在本应用中,我们怎么命名文件是不重要的。一旦我们在本地有一份文件拷贝,我们就可以产生一个Tessearct库的实例,然后进行分析:
// Instantiate the Tessearct library $tesseract = new TesseractOCR(__DIR__ . '/../uploads/' . $filename);
在图像上实现OCR相当简单,我们只需调用方法recognize()。
// Perform OCR on the uploaded image $text = $tesseract->recognize();
最后我们把结果展示到结果页面:
return $app['twig']->render( 'results.twig', [ 'text' => $text, ] );
在一些图片上尝试,看看它效果怎样。如果你有困难,可以参考 这个
一个实际的例子
让我们来看OCR一个更实用的例子。在本例中,我们尝试在图像中找到一个格式化的电话号码。
看看下面一幅图,上传到你的应用:
结果应该如下:
:ii‘i Customer Service Helplines British Airways Helpline 09040 490 541
它没有挑出正文文本,这是我们能料到的,因为图片质量太差。虽然识别了号码但是也有一些“噪声”。
为了提取相关信息,有如下几件事我们可以做。
你可以让Tesseract 把它的结果限制在一定的字符集内,所以我们告诉它只返回数字型的内容代码如下:
$tesseract->setWhitelist(range(0,9));
但这样有个问题。它常常把非数字字符解释成数字而非忽略它们。比如“Bob”可能被解释称数字“808”。
所以我们采用两步处理。
- 尝试提取可能是电话号码的数字串。
- 用一个库轮流评估每一个候选字符,一旦找到一个有效电话号码则停止。
第一步,我们可以用一个基本的正则表达式。可以用 谷歌电话库 来确定一个数字串是否是合法电话号码。
备注:我已在Sitepoint 写过关于 谷歌电话库的内容 。
让我们给谷歌电话库添加一个PHP 端口,修改composer.json,添加:
"giggsey/libphonenumber-for-php": "~7.0"
别忘了升级:
composer update
现在我们可以写一个函数,输入为一个字符串,尝试提取一个合法的电话号码
/** * Parse a string, trying to find a valid telephone number. As soon as it finds a * valid number, it'll return it in E1624 format. If it can't find any, it'll * simply return NULL. * * @param string $text The string to parse * @param string $country_code The two digit country code to use as a "hint" * @return string | NULL */ function findPhoneNumber($text, $country_code = 'GB') { // Get an instance of Google's libphonenumber $phoneUtil = \libphonenumber\PhoneNumberUtil::getInstance(); // Use a simple regular expression to try and find candidate phone numbers preg_match_all('/(\+\d+)?\s*(\(\d+\))?([\s-]?\d+)+/', $text, $matches); // Iterate through the matches foreach ($matches as $match) { foreach ($match as $value) { try { // Attempt to parse the number $number = $phoneUtil->parse(trim($value), $country_code); // Just because we parsed it successfully, doesn't make it vald - so check it if ($phoneUtil->isValidNumber($number)) { // We've found a telephone number. Format using E.164, and exit return $phoneUtil->format($number, \libphonenumber\PhoneNumberFormat::E164); } } catch (\libphonenumber\NumberParseException $e) { // Ignore silently; getting here simply means we found something that isn't a phone number } } } return null; }
希望注释能解释这个函数在干什么。注意如果这个库没能从字符串中解析出一个合法的电话号码它会抛出一个异常。这不是什么问题;我们直接忽略它并继续下一个候选字符。
如果我们找到一个电话号码,我们以E.164的形式返回它。这提供了一个国际化的号码,我们可以用来打电话或者发送SMS。
现在我们可以如下使用:
$text = $tesseract->recognize(); $number = findPhoneNumber($text, 'GB');
我们需要给谷歌电话库提供一个提示来说明这个号码是哪个国家的。你也可以改成你自己的国家。
我们把所有的这些打包在一个新的路由中:
$app->post('/identify-telephone-number', function(Request $request) use ($app) { // Grab the uploaded file $file = $request->files->get('upload'); // Extract some information about the uploaded file $info = new SplFileInfo($file->getClientOriginalName()); // Create a quasi-random filename $filename = sprintf('%d.%s', time(), $info->getExtension()); // Copy the file $file->move(__DIR__.'/../uploads', $filename); // Instantiate the Tessearct library $tesseract = new TesseractOCR(__DIR__ . '/../uploads/' . $filename); // Perform OCR on the uploaded image $text = $tesseract->recognize(); $number = findPhoneNumber($text, 'GB'); return $app->json( [ 'number' => $number, ] ); });
我们现在有简单的API的基础—-也就是JSON响应-—我们可以用来作为一个简单的移动应用的后端,这款应用可以用来从一幅图中添加联系人,打电话。
总结
OCR有许多应用——并且很容易整合进你的应用(超过你的预期)。本文中,我们安装了开源OCR包;并使用一个包装器库,把它整合进一个非常简单的PHP应用。我们只是触及到了所有可能性的表面,希望这能给你一些想法,帮你想想怎么在你自己的应用中使用OCR。