編輯:關於Android編程
由於研究生畢業項目需要完成一個基於移動終端的場景文字識別系統,雖然離畢業尚早,但出於興趣的緣故,近一段抽時間完成了這樣一套系統。基本的架構如下:
客戶端:Android應用實現拍攝場景圖片,大致劃出感興趣文字區域,通過socket通信上傳服務器端識別;
服務器端:Python server進行socket通信監聽,連通後調用文字識別引擎(exe可執行程序),將識別結果返回;
下面是系統運行示例圖:
1. 客戶端
包含兩個Activity,: MainActivity主界面如上圖左1,選擇拍攝後調用系統內部的拍照服務如上圖左2;拍照完成進入KernelActivity如上圖左3,大致劃出感興趣文字區域後上傳服務器端,獲取識別結果如上圖左4。
客戶端拍照和建立網絡通信需要注意在AndroidManifestxml文件中聲明權限
客戶端Android編程時連接服務器端利用socket進行通信,在編程過程中有幾點需要注意的問題,不然很容易導致出錯:
1.1 主線程中不能直接建立網絡連接
為了防止線程阻塞,android 4.0以上的版本不允許直接在主線程建立網絡連接(socket通信需要連接網絡)。因此,在需要socket通信時,應新開線程用於網絡連接,使用示例如下
new Thread(){//主線程不能直接連接網絡,需新建子線程 @Override public void run(){ //TODO } }.start();
1.2 客戶端socket收發數據流不宜使用close()方法
客戶端需要同時接受和發送數據,因此會有如下兩個對象out和in
//開始socket通信 Socket socket = new Socket("210.77.27.123", 9058); //此處IP根據自己的服務器端設定 //寫輸出(上傳)數據 DataOutputStream out = new DataOutputStream(socket.getOutputStream()); //寫輸入(獲得)數據 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "gb2312")); //設置字符編碼格式,以便正確讀取中文
如果在out向服務器端發送完數據後,想著out已經不需要了,則可能會想使用 out.close() 關閉對象。如果這樣,那麼在使用in對象接收數據時便會拋出異常“socket closed”!原因是,in或out對象的close()方法使用後會造成socket的關閉。所以,如果需要關閉,可以考慮使用shutdownInput()和shutdownOutput()方法,或者不處理而待最後socket.close()即可。
1.3 子線程不能更新UI
獲得服務器端識別結果時,需要使用這個內容更新客戶端UI(左下角的TextView),如果在上面說到的進行socket通信的子線程中直接使用 text.setText()方法試圖更新UI的話,結論是不可行的。所以,為了更新UI需要借助Handler對象,在其handleMessage()方法內更新UI。
子線程中將服務器端結果存入message並向預定義的handler對象發送消息
String response = in.readLine(); Message msg = handler.obtainMessage();//子線程中不能更新UI,需借助handler msg.obj = response; handler.sendMessage(msg);
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.服務器端
服務器端主要部署python腳本進行socket監聽,一旦建立連接則調用開發好的場景文字識別引擎(STREngine.exe),並將結果返回客戶端。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()
文字識別引擎的內部代碼不便分享,但會將可執行文件分享給感興趣的朋友。
服務器端有一點非常重要的地方,不注意的話可能會給你帶來無盡的煩惱,我就花了兩個晚上才發現這個很小的問題,在此分享以免再浪費別人的時間:
在服務器端返回識別結果給客戶端時,一定要在字符串後面加換行符‘\n’結尾!如果不加,則客戶端 in.readline()方法會因找不到行末而一直阻塞,則無法利用返回結果更新客戶端UI。這個問題非常惱人,因為如果你在服務端返回不加‘\n'的話,在本地電腦上利用android虛擬機是沒問題的,虛擬機上正常更新UI,但是一旦換到手機上就沒反應了(不更新UI)。
總結:
其實,客戶端手動劃取感興趣文字上傳後,這個區域位置在ImageView和實際圖像中是需要仔細換算的,這裡就不細說了,有需要的朋友直接看代碼吧。或者強烈建議自己分析推導一番,自己找出這個關系,會對加深對ImageView和Bitmap的理解有極大幫助。
此外,由於不是做產品,對效率的考慮並沒有在意。現在的話是將手機拍照後的整幅圖片上傳,雖然進行了壓縮,但一副圖仍有幾百KB大小,這對流量實在太過浪費。解決的話也簡單,只要將劃取的文字區域單獨取出來上傳即可(但四周需要一定程度的擴展),大小應該減小幾十倍。
客戶端和服務端源代碼(包括識別引擎可執行程序)已分享至CSDN,有問題歡迎隨時隨時交流指正。
一、概述在Android的開發中,經常聽到“內存洩漏”這個詞。“內存洩漏”就是一個對象已經不需要再使用了,但是因為其它的對象持有該對象的引用,導致它的內存不能被回收。“內
上一篇介紹了幾個滑動導航菜單效果的快速構建,這篇文章來總結“當下”如何按照Android的設計標准去設計滑動導航菜單,我為什麼說的“當
今天帶來的是兩列並排ListView關聯滑動,這裡面有兩個知識點:1、兩個ListView如何並列顯示。2、如何關聯滑動。 第一個問題,好像我之前的博客提到過,就是讓Li
一:Android中activity之間的常見跳轉實現 1)startActivity(Intent intent); 2)startActivityForResult(