因为研究生毕业项目须要完毕一个基于移动终端的场景文字识别系统。尽管离毕业尚早,但出于兴趣的缘故,近一段抽时间完毕了这样一套系统。
主要的架构例如以下:
client:Android应用实现拍摄场景图片,大致划出感兴趣文字区域,通过socket通信上传server端识别;
服务器端:Python server进行socket通信监听,连通后调用文字识别引擎(exe可运行程序),将识别结果返回;
以下是系统执行演示样例图:
1. client
包括两个Activity,: MainActivity主界面如上图左1,选择拍摄后调用系统内部的拍照服务如上图左2;拍照完毕进入KernelActivity如上图左3,大致划出感兴趣文字区域后上传server端,获取识别结果如上图左4。
client拍照和建立网络通信须要注意在AndroidManifestxml文件里声明权限
<!-- 授权使用相机 --> <uses-permission android:name="android.permission.CAMERA" /> <!-- 在SD卡中创建与删除文件的权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <!-- 向SD卡中写入数据权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 授权訪问互联网 --> <uses-permission android:name="android.permission.INTERNET" />
clientAndroid编程时连接server端利用socket进行通信,在编程过程中有几点须要注意的问题。不然非常easy导致出错:
1.1 主线程中不能直接建立网络连接
为了防止线程堵塞,android 4.0以上的版本号不同意直接在主线程建立网络连接(socket通信须要连接网络)。因此,在须要socket通信时。应新开线程用于网络连接,使用示比例如以下
new Thread(){//主线程不能直接连接网络,需新建子线程 @Override public void run(){ //TODO } }.start();
1.2 clientsocket收发数据流不宜使用close()方法
client须要同一时候接受和发送数据,因此会有例如以下两个对象out和in
//開始socket通信 Socket socket = new Socket("210.77.27.123", 9058); //此处IP依据自己的server端设定 //写输出(上传)数据 DataOutputStream out = new DataOutputStream(socket.getOutputStream()); //写输入(获得)数据 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "gb2312")); //设置字符编码格式,以便正确读取中文
假设在out向server端发送完数据后,想着out已经不须要了,则可能会想使用 out.close() 关闭对象。假设这样。那么在使用in对象接收数据时便会抛出异常“socket closed”!原因是,in或out对象的close()方法使用后会造成socket的关闭。所以。假设须要关闭,能够考虑使用shutdownInput()和shutdownOutput()方法。或者不处理而待最后socket.close()就可以。
1.3 子线程不能更新UI
获得server端识别结果时。须要使用这个内容更新clientUI(左下角的TextView),假设在上面说到的进行socket通信的子线程中直接使用 text.setText()方法试图更新UI的话。结论是不可行的。
所以,为了更新UI须要借助Handler对象。在其handleMessage()方法内更新UI。
子线程中将server端结果存入message并向提前定义的handler对象发送消息
String response = in.readLine(); Message msg = handler.obtainMessage();//子线程中不能更新UI。需借助handler msg.obj = response; handler.sendMessage(msg);
紧接着handler会调用其 handleMessage()方法更新UI,handleMessage()定义例如以下
handler = new Handler(){ @Override public void handleMessage(Message msg){ String message = (String)msg.obj; if(message != null) text.setText(message); else text.setText("抱歉,未能识别"); super.handleMessage(msg);//这一句必需要有!不然无法更新UI } };
2.server端
服务器端主要部署python脚本进行socket监听,一旦建立连接则调用开发好的场景文字识别引擎(STREngine.exe),并将结果返回client。
python server脚本内容例如以下
##################################### # # # STC Recognition Server # # # ##################################### import socket import os s = socket.socket() host = socket.gethostname() port = 9058 s.bind((host, port)) s.listen(5) while True: c, addr = s.accept() print 'Got connection from' , addr #get uploaded params params = c.recv(19) lst = params.split(' ') if len(lst) < 4: c.close() continue print 'x=%d, y=%d, w=%d, h=%d' % (int(lst[0]),int(lst[1]),int(lst[2]),int(lst[3])) #get uploaded image size info size = int(c.recv(4)) #save uploaded image f = open('.\\upload\\tmp.jpg', 'wb') for i in range(size): f.write(c.recv(1024)) f.close() print 'Image received, size %dKB' % size #execute STREngine on server and send back the result result = os.popen('STREngine ./upload/tmp.jpg ' + params).read() print 'Recognition result:', result c.send(result + '\n') print 'Close connection with', addr, '\n' c.close()
文字识别引擎的内部代码不便分享。但会将可运行文件分享给感兴趣的朋友。
server端有一点非常重要的地方。不注意的话可能会给你带来无尽的烦恼,我就花了两个晚上才发现这个非常小的问题,在此分享以免再浪费别人的时间:
在server端返回识别结果给client时,一定要在字符串后面加换行符‘\n’结尾!
假设不加,则client in.readline()方法会因找不到行末而一直堵塞。则无法利用返回结果更新clientUI。这个问题很恼人,由于假设你在服务端返回不加‘\n'的话,在本地电脑上利用android虚拟机是没问题的,虚拟机上正常更新UI,可是一旦换到手机上就没反应了(不更新UI)。
总结:
事实上。client手动划取感兴趣文字上传后。这个区域位置在ImageView和实际图像中是须要细致换算的。这里就不细说了。有须要的朋友直接看代码吧。
或者强烈建议自己分析推导一番,自己找出这个关系。会对加深对ImageView和Bitmap的理解有极大帮助。
此外。因为不是做产品。对效率的考虑并没有在意。如今的话是将手机拍照后的整幅图片上传,尽管进行了压缩,但一副图仍有几百KB大小。这对流量实在太过浪费。解决的话也简单。仅仅要将划取的文字区域单独取出来上传就可以(但四周须要一定程度的扩展),大小应该减小几十倍。
client和服务端源码(包含识别引擎可运行程序)已分享至CSDN,有问题欢迎随时随时交流指正。