如何在安卓系统里使用python结合Tess-Two进行OCR识别

前几个月因为想了解一下统计学习算法,本来是用MATLAB,但是后来网上都推荐用tenserflow,反正我的C++也没学完,C也基本都忘了,就只会用MFC写点小程序,就转向重新学习python了,python的确很方便,安装过程嘛,就是各种install,apt,get,非常符合我这种懒人特性,后来统计学习算法遇到一些障碍,一直没法优化,感觉整体就会三板斧,调参、改算法什么都不会,还要继续学习,手上的小米note刚好的淘汰,就想先用Python结合小米note进行各种APP的每日签到活动,本来是电脑adb连接手机,控制手机签到,这一步利用tesseract基本实现了,但是!为了每天不到一块钱,就要我的笔记本一直运行,实在是有点浪费电脑性能,就想移植到andriod系统上,手机自己运行程序,adb移值还好实现,但是tesseract转到andriod系统的过程中,因为没有太多的前人经验可以借鉴(因为是使用python环境,如果是C++和ndk+elipse,就可以参考网上教程了),所以遇到了许多的坑,经过断断续续的不懈努力(几度想放弃了),总算实现了基本功能,虽然Python+Tess-Two这个结合比较奇怪,但本着分享学习、共同进步的初衷,也将过程写下来给后面有需求的人一点提示,这就是写本篇文档的原因。

一、tess-two介绍

光学字符识别(OCR,Optical Character Recognition)是指对文本资料进行扫描,然后对图像文件进行分析处理,获取文字及版面信息的过程。Tesseract的OCR引擎最先由HP实验室于 1985年开始研发,至1995年时已经成为OCR业内最准确的三款识别引擎之一。然而,HP不久便决定放弃OCR业务,Tesseract也从此尘封。
数年以后,HP意识到,与其将Tesseract束之高阁,不如贡献给开源软件业,让其重焕新生--2005年,Tesseract由美国内华达州信息技术研究所获得,并求诸于Google对Tesseract进行改进、消除Bug、优化工作。
Tesseract目前已作为开源项目发布在Google Project,其项目主页在这里查看,其最新版本3.0已经支持中文OCR,并提供了一个命令行工具。
因为Tesseract使用C++实现的,在Android中不能直接使用,需要封装JavaAPI才能在Android平台中进行调用,这里我们直接使用TessTwo项目,tess-two是TesseraToolsForAndroid的一个git分支,使用简单,切集成了leptonica,在使用之前需要先从git上下载源码进行编译,不能简单使用别人编译好的.so,因为你要自己写调用接口方法。
Tess-two在git上地址为:https://github.com/rmtheis/tess-two
字库资源
英文字符,eng.traineddata https://github.com/tesseract-ocr/langdata
中文字符,chi_sim.traineddata http://download.csdn.net/download/fu_shuwu/10021639

二、运行环境准备

手机系统:小米miui(andriod7.0),qpython,python3.0
电脑系统:win10,cygwin,android-ndk-r16b
电脑因为是win10,编译环境需要下载cygwin软件,这个直接搜索下载就是,安装过程大体可参考这个文档:https://blog.csdn.net/chunleixiahe/article/details/55666792
建议不要下载整个devel,不知道是不是连接外网的原因,速率有时会卡,就在上面文档的基础上再上一个devel-make就差不多了,设置系统环境。
然后下载android-ndk-r16b解压,修改你的cygwin位置/home/你的用户名/.bash_profile,添加NDK=/cygdrive/e/pycode/android-ndk-r16b export NDK export PATH=NDK:PATH,其中e/pycode/改过你android-ndk-r16b的安装位置。运行cygwin,输入$NDK应该用会显示下面这样。
如何在安卓系统里使用python结合Tess-Two进行OCR识别
从github上下载Tess-two源码,修改\tess-two-master\tess-two\jni\Application.mk,其中APP_ABI := armeabi-v7就可以了,其它X86什么的可以直接删了,不用那么多版本。
在cygwin里cd 到源码下的jni文件夹,输入$NDK/ndk-build.cmd就可以实现编译了编译后会在libs/armeabi-v7a下生成4个so文件,相当于windows下的dll。正常大概会像下面这样,
如何在安卓系统里使用python结合Tess-Two进行OCR识别
这其中的几个坑是:
(一)cygwin安装,前期在devel那是直接选择全部安装install,导致几个包卡住进行不下去,浪费了一晚上时间等待,只选择devel下编译相关的几个包,C,C++,gun,make,gcc等,后面有需要再运行一次cygwin再更新其它包就行,原有的会一直在的。
(二)cygwin文件添加如下文件,NDK=/cygdrive/e/pycode/android-ndk-r16b export NDK export PATH=NDK:PATH,网上找到有的不会全,老是找不到ndk那边,另外中间不要有换行符/b/r/n等,多试几次。
(三)我前面使用的是android-ndk-r5,编译过程中老是报错unrecognized command line option “-std=c++11”,网上说是gcc版本太低,我用gcc -v一看,都7.3了,应该大于4.8就行啊,后来又找了一下,说是可能是因为存在多个gcc版本,且系统默认的gcc版本是低版本的,当时不知道其它gcc在哪里,只有全电脑搜gcc了,发现cygwin和android-ndk-r5里有gcc,不过我把cygwin下面的gcc,C++拷到android-ndk-r5,编译还是报错,最后无奈到官网上https://developer.android.google.cn/ndk/downloads/,下载更新到android-ndk-r16b,然后竟然神奇地通过了……
三、为python添加Tess-two接口函数及调用
网上的教程里,都是将这几个.so添加到elipse工程里调用,但我用python,没法这么做,只好学习python调用C++的方法,自己加载了.so了,如下所示
from ctypes import *
import ctypes
import os
#参数为生成的.so文件所在的绝对路径
libjpeg = cdll.LoadLibrary(‘/data/libjpgt.so’)
libpngt = cdll.LoadLibrary(‘/data/libpngt.so’)
liblept = cdll.LoadLibrary(‘/data/liblept.so’)
libtess = cdll.LoadLibrary(‘/data/libtess.so’)
这几个.so我本来放在/sdcard根目录下,但一加载就报权限问题,查了一下说是在/sdcard下没有执行权,放在/data/下就行了,这个需要手机ES软件+root权限显示隐藏文件就行了。
然后在\tess-two-master\tess-two\jni\com_googlecode_tesseract_android\src\api\下面的baseapi.cpp里添加下面的接口,然后重新编译得到.so文件,放到/data下。
extern “C” {
TessBaseAPI* tessbaseapi_new(){
return new TessBaseAPI();
}
//初始化设置字库路径和名称
void init(TessBaseAPI* foo,const char* datapath, const char* language) {
foo->Init(datapath,language);
}
//设置OCR图片
void SetImage(TessBaseAPI* foo,Pix* pix) {
foo->SetImage(pix);
}
//得到OCR输出文件
char* GetUTF8Text(TessBaseAPI* foo) {
Return foo->GetUTF8Text();
}
}
在py文件里添加如下代码,就实现了调用接口。
class tessbaseapi(object):
def init(self):
self.obj = libtess.tessbaseapi_new()
#python方法没有参数类型这一选项
def init(self,datapath,language):
libtess.init(self.obj,datapath,language)
#设置OCR图片
def SetImage(self,pix):
libtess.SetImage(self.obj,pix)
#得到OCR输出文件
def getUTF8Text(self):
return libtess.GetUTF8Text(self.obj)
调用时的原来设想就会像下面这样简单了,代码就如下所示了。
tess = tessbaseapi()
#初始化
tess.init(“/sdcard”,”eng”)
#设置OCR图片
tess.SetImage(“/sdcard/1.png”)
#得到OCR输出文件
text = tess.getUTF8Text()
print(text)
可是这根本就不行,接下来才是最大一片坑。。。。。。。
首先,tess-two接口函数参数类型要求是char *,你传进去”/sdcard”、”eng”,他就只能识别一个“/”,和一个”e”,然后告诉你,找不到e.traindata,所以初始化init参数要改成如下格式,
language = bytes(“eng”,encoding = ‘utf-8’)
address = bytes(“/sdcard”,encoding = ‘utf-8’)
#初始化
tess.init(address,language)
好,现在eng.traindata找到了,然后告诉我SetImage参数类型不对(我本来是直接open打开图片给它的),进去源码一下,Pix*是什么类型?我去哪去找这个类型的图片文件给你,转到定义IDE说找不到定义,下面那么多c、c++、h文件,我一个一个点,也没有找到Pix结构定义在哪了,崩溃中,上网也没找到,都想放弃了,看到一句话,leptonica是进行图片处理的,我好像在哪看到过leptonica,一想原来编译过leptlib.so,赶紧到com_googlecode_leptonica_android里找找看,果然找到了PIx的定义,
struct Pix
{
l_uint32 w; /!< width in pixels /
l_uint32 h; /!< height in pixels /
l_uint32 d; /!< depth in bits (bpp) /
l_uint32 spp; /!< number of samples per pixel /
l_uint32 wpl; /!< 32-bit words/line /
l_uint32 refcount; /!< reference count (1 if no clones) /
l_int32 xres; /!< image res (ppi) in x direction /
/!< (use 0 if unknown) /
l_int32 yres; /!< image res (ppi) in y direction /
/!< (use 0 if unknown) /
l_int32 informat; /!< input file format, IFF_ */
l_int32 special; /!< special instructions for I/O, etc /
char text; /!< text string associated with pix */
struct PixColormap colormap; /!< colormap (may be null) */
l_uint32 data; /!< the image data */
};
你妹的,这么复杂,我怎么搞,上天呢,参考看了一下elipse下的做法,
//options是为了缩放图片,这个酌情缩放,图片小的话可以不缩放
BitmapFactory.Options options=new BitmapFactory.Options();
//缩小为原来的1/2
options.inSampleSize=2;
//bitmap,我这里是以流的形式,只要能形成Bitmap就行
Bitmap bitmap = BitmapFactory.decodeStream(instream,null,options);
instream.close();
//设置要ocr的图片bitmap
baseApi.setImage(bitmap);
又去找BitmapFactory有没有python的可调包,发现没有找到,找了一下源码里的graphics.cpp,也没有相关函数可以用,到这里怒得我差点都想找BitmapFactory的C源码也编译一遍再来调用了,没办法,上网上看看大神有没有什么办法吧,各种更换关键字,查了半天也没什么进展,突然看到一句pixRead,赶紧去找源码,果然有一个pixReadStreamPng方法,而且已经extern C了,直接调用
content = liblept.pixReadStreamPng(bitmap)
但这个函数的输入是FILE*型参数,这次有经验了,再在源码里找,果然找到了另一个fopenReadStream,一调用就可以了,因此设置输入图片整体代码如下,
#打开文件,返回FILE*对象
filename = bytes(“/sdcard/1.png”,encoding = ‘utf-8’)
bitmap = liblept.fopenReadStream(filename)
#转换为PIX*格式型对象
content = liblept.pixReadStreamPng(bitmap)
#设置OCR图片
tess.SetImage(content)
然后就是第三个大坑,getUTF8Text,前面python与C的交互主要的给C接口方法参数,接下来是得到返回值,按网上的说法改成了如下格式
rst = tess.getUTF8Text()
size = -1
rst = ctypes.string_at(rst, size)
print(rst.decode(‘utf-8’))
但识别结果总是一串乱码,不知道问题出在哪里,更改输入图片了后发现,这串乱码与输入无关,那就不是识别结果了,继续到Tess-Two源码里看,添加一下tprint后发现,Tess-Two是有识别的,只是传回来时不对,看了一下别人的经验,说是可能C++函数调用完后,内存就销掉了,所以没有传回char 类型参数,于是我把接口改成const char结果也不行,再往源口改!改源码的getUTF8Text(),但一改就是一串关联函数,编译通不过,根本没法实现,都进行到最后一步了,卡在这里放弃真是不甘心啊,但解决不了啊,带着郁闷的心情去看了一下电脑上tesseract的使用,命令行格式
【语法】: tesseract imagename outputbase [-l lang] [-psm pagesegmode] [configfile…]
突然想到了一下取巧的办法,跟tesseract学习,在接口方法里把识别结果写到txt去,再在python里读这个txt,就是跳过两种语音传参了吗?而且这么改只是改动了接口方法,对源码没有变动,十分符合我这种强迫症患者啊,前面改源码的getUTF8Text()我就十分不情愿,这不完美啊!!!
于是Tess-Two的接口改为如下:
//得到OCR输出文件
char* GetUTF8Text(TessBaseAPI* foo,char* output_filename) {
char* str_temp = foo->GetUTF8Text();
std::ofstream outfile(output_filename,std::ios::trunc);
outfile << str_temp;
outfile.close();
return str_temp;
}
Python接口改成
#得到OCR输出文件
def getUTF8Text(self,outfile,isFileSave):
libtess.GetUTF8Text(self.obj,outfile)
with open(outfile,encoding = ‘utf-8’) as f:
text = f.read().strip()
if isFileSave != 1:
os.remove(outfile)
return text
调用时就写成
#得到OCR输出文件
outfilename = bytes(“/sdcard/1.txt”,encoding = ‘utf-8’)
text = tess.getUTF8Text(outfilename,1)
print(text)
结果完美识别了。撒花、撒花、撒花!!!
最后根据tesseract上的经验,我继续看tess-two源码的相关函数,查找有关识别模式-psm 7和有关白名单等方法,添加到接口中,
//设置图片模式为单行文本
void SetPageSegMode_SingleLine(TessBaseAPI* foo) {
foo->SetPageSegMode(PSM_SINGLE_LINE);
}
//设置黑白名单 Eg SetVariable(“tessedit_char_blacklist”, “xyz”); to ignore x, y and z. tessedit_char_whitelist
bool SetVariable(TessBaseAPI* foo,const char* name, const char* value) {
return foo->SetVariable(name,value);
}
//设置OCR范围
void SetRectangle(TessBaseAPI* foo,int left, int top, int width, int height) {
foo->SetRectangle(left,top,width,height);
}
Python方面的添加
#设置图片模式为单行文本
def SetPageSegMode_SingleLine(self):
libtess.SetPageSegMode_SingleLine(self.obj)
#设置黑白名单 Eg SetVariable(“tessedit_char_blacklist”, “xyz”); to ignore x, y and z.
def SetVariable(self,name,value):
libtess.SetVariable(self.obj,name,value)
#设置OCR范围
def SetRectangle(self,left,top,width,height):
libtess.SetRectangle(self.obj,left,top,width,height)
调用时
#设置图片模式为单行文本
tess.SetPageSegMode_SingleLine()
#设置白名单
avariable_mode = bytes(“tessedit_char_whitelist”,encoding = ‘utf-8’)
whitelist = bytes(“abcdedfhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”,encoding = ‘utf-8’)
test = tess.SetVariable(avariable_mode,whitelist)
这里讲道理,test返回应该是1,实际打印时却成了None,本来我还怀疑白名单设置不成功,但经测试没问题啊,白名单是起效果的,所以C++回传Python到底要怎么解决,还要继续探讨学习。
至此,手机系统通过Python+Tess-Two进行OCR识别可能基本完成了,本来是准备进行验证码识别的,但有的验证码太过变态,在电脑上就识别率不高,后面还要继续学习图片去噪、旋转、分割等处理,提高识别率问题。

四、一点体会

以前调用别人的第三方库,总是网上搜索直接调用已有方法,这次tess-two调用根本找不到太多类似内容,连单行模式、白名单设置等也是自己看源码对着英文注释找到的,因为大学毕后因为各种原因,已经近六七年没有动过编程了,这次重新捡起来当作一种个人的爱好,从决定移值到手机上到最后初步完成共用了6天时间,刚开始看的确还比较累,在这当中,按网上的步骤搭环境,自己编译、写接口、转参数类型,最后找到解决方法,最终一步一步实现的时候自己也感到很开心吧。
如果有需要一起探计的地方,可以发[email protected]联系,虽然长年不用这个邮箱,但还是不时登陆一下看看注册验证码的。

五、参考

https://blog.csdn.net/duanbokan/article/details/50738711
https://blog.csdn.net/yd_yandong/article/details/52860487
https://blog.csdn.net/fu_shuwu/article/details/78263214?locationNum=4&fps=1
https://www.cnblogs.com/muyun/archive/2012/06/12/2546693.html
http://www.cnblogs.com/hangxin1940/archive/2012/01/13/2321507.html

相关文章:

  • 2021-05-31
  • 2021-12-10
  • 2022-01-11
  • 2022-12-23
  • 2021-11-23
  • 2021-12-31
  • 2022-12-23
猜你喜欢
  • 2021-08-02
  • 2021-12-04
  • 2022-12-23
  • 2022-12-23
  • 2022-01-13
  • 2021-11-07
  • 2021-11-02
相关资源
相似解决方案