今天想着把以前做過的一個Android的文字檢測識別應用好好的回顧一下,因為以前寫java程序,目的就是能用就行,不會仔細看每一個部分代碼,也不會記他們的用法,不回會去查API,借鑒別人的例程,用過就忘了,現在想着要改變,於是就回顧了一番。
之前檢測用到的是Tesseract_OCR,之所以能在Android的上運行,是因為黑暗伯爵大神已經把tess-two(為android寫的tesseract-tools)編譯好了,然后我直接用的。我還是小白,完全不懂編譯那些,如果讓我自己搞.... 反正最后編譯成so文件(這個是linux平台下的動態鏈接庫,可以類比dll),然后我用編譯好的so文件,以及jar包導入到工程,照葫蘆畫瓢把文字識別部分和自己之前看論文寫的文字檢測部分合到一起,然后百度了怎么調用攝像頭,然后寫了一個攝像頭拍照,然后檢測文字所在區域,處理,然后識別的應用。以后有時間會詳細的重新把應用梳理一遍,然后記錄,當然這不是今天的重點。如果對Android上如何做OCR感興趣,可以參考 這里 。
回到正題,前幾天同學問我以前做的文字識別是咋搞得,他想用用,我於是就想把程序移回來到java上,結果我百度才發現原來還有另外一種思路,那就是執行exe進程。先在pc上安裝好tesseract_orc,然后jvm運行命令行跑識別程序,完成識別后結果寫入txt,最后讀取txt把內容返回到java程序中。這么想想也行,於是便試了試。(PS:我是后來才發現也有Java的API tess4j,參考 這里)
然后就是講自己具體怎么實現:
工程也就兩部分,一部分是GUI,反正我現在對於Java的基礎知識薄弱,然后常用類也用的不多,所以借此機會正好熟悉。
GUI主要用到javax.swing包和java.awt包,swing主要寫組件,awt是事件(其實awt也可以寫組件),但是說swing是對awt中組建的優化,所以現在常用swing。GUI里面比較重要的概念還是容器,組件必須放到容器里面才能顯示,常用frame和dialog。我這次寫GUI用到jframe,jbutton,filedialog,JLabel,imageicon這幾個類
界面如下
- 其中的按鈕用的JButton:"push" 按鈕的實現,可以設置監聽器。
- 按鈕設置了監聽器,“打開圖片”彈出Filedoalog,選擇圖片文件,“識別”新建文字識別類,進行識別返回結果:
FileDialog
類顯示一個對話框窗口,用戶可以從中選擇文件。 - 圖片顯示和文字顯示用的JLabel:
JLabel
對象可以顯示文本、圖像或同時顯示二者。可以通過設置垂直和水平對齊方式,指定標簽顯示區中標簽內容在何處對齊。默認情況下,標簽在其顯示區內垂直居中對齊。默認情況下,只顯示文本的標簽是開始邊對齊;而只顯示圖像的標簽則水平居中對齊。 - Jframe建立整個容器
- Imageicon用來加載圖像。Imageicon:一個 Icon 接口的實現,它根據 Image 繪制 Icon。可使用 MediaTracker 預載根據 URL、文件名或字節數組創建的圖像,以監視該圖像的加載狀態。
界面的具體代碼我就不貼上來了,值得注意的是圖片壓縮顯示有一個小trick。
imagePath = new String(dirPath+fileName); imageico = new ImageIcon(imagePath); int w = imageico.getIconWidth(); int h = imageico.getIconHeight(); double ratio = (double)w/(double)h; if(ratio>4/3){ h = (int)(640*h/w); w = 640; }else{ w = (int)(480*w/h); h = 480; } imageico.setImage(imageico.getImage().getScaledInstance(w,h,imageico.getImage().SCALE_DEFAULT));
另外,就是文字識別的部分,主要還是一個執行進程的過程,那么首先得下載安裝Tesseract_OCR:
安裝下載的工作參考 這里
那么這邊主要的挑戰就是使用java執行進程和做文件io,主要分一下幾點:
1.java操作命令行主要用到processbuilder & process 類,出自java.lang
一般都是ProcessBuilder.start() 和 Runtime.exec(ArrayList<String>) 方法創建一個本機進程,並返回 Process 子類的一個實例,該實例可用來控制進程並獲得相關信息。Process 類提供了執行從進程輸入、執行輸出到進程、等待進程完成、檢查進程的退出狀態以及銷毀(殺掉)進程的方法。
Runtime.getRuntime.exec(ArrayList<String> cmd)
processbuilder pb; pb.command(ArrayList<String> cmd);
這里就是用的processbuilder pb,新建一個實例,並且把運行參數保存到字符串表單cmd里,然后pb.command(ArrayList<String> cmd)執行,結果保存到指定txt中;
2.表示文件的類file類 出自java.io
它是文件和目錄路徑名的抽象表示形式。
主要方法getName(),getPath(),getParentPath();
在執行命令行時,表示輸入的圖片,表示輸出txt都可以用到file類。
3.讀取字節流過程 出自java.io
FileInputStream 從文件系統中的某個文件中獲得輸入字節。哪些文件可用取決於主機環境,這里可以讀取圖片。
new FileInputStream(outputFile.getAbsolutePath()) 新建一個文件輸入字節流
new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()),"UTF-8")。文件輸入字節流變成文件輸入字符流
BufferedReader從字符輸入流中讀取文本,緩沖各個字符,從而實現字符、數組和行的高效讀取。
主要是兩種用法還可以這么使用
BufferedReader in = new BufferedReader(new FileReader("foo.in"));
或者
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("foo.in"),"UTF-8"));
前者不能指定編碼,而后者可以。
在最后把txt中文本讀取到java程序,進而顯示在GUI中用到
識別的代碼如下
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class textRecognizer{ private String textResult; /** * 輸出的結果 */ private final String EOL = System.getProperty("line.separator"); //回車 private String tessPath = "D:\\Tesseract-OCR"; //tessocr程序所在目錄 public textRecognizer(String path) { try { File imagefile = new File(path); textResult = this.recognizeText(imagefile); } catch (Exception e) { e.printStackTrace(); } } public String getResult(){ return textResult; } private String recognizeText(File imageFile) throws Exception { /** * 設置輸出文件的保存的文件目錄 */ File outputFile = new File(imageFile.getParentFile(),"output"); StringBuffer strB = new StringBuffer(); //設置cmd命令行字符串形式 List<String> cmd = new ArrayList<String>(); cmd.add(tessPath + "\\tesseract"); cmd.add(imageFile.getName()); cmd.add(outputFile.getName()); cmd.add("-l"); cmd.add("eng"); //啟動exe進程 ProcessBuilder pb = new ProcessBuilder(); pb.directory(imageFile.getParentFile()); pb.command(cmd); pb.redirectErrorStream(true); Process process = pb.start(); //等待此進程完成 int w = process.waitFor(); if (w == 0){// 0代表正常退出 BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()+ ".txt"),"UTF-8")); String str; while ((str = in.readLine()) != null) { strB.append(str).append(EOL); } in.close(); } else{ String msg; switch (w){ case 1: msg = "Errors accessing files. There may be spaces in your image's filename."; break; case 29: msg = "Cannot recognize the image or its selected region."; break; case 31: msg = "Unsupported image format."; break; default: msg = "Errors occurred."; } throw new RuntimeException(msg); } new File(outputFile.getAbsolutePath()+ ".txt").delete(); /** * 如果做驗證碼 * return strB.toString().replaceAll("\\s*", ""); */ return strB.toString(); } }
可惜的是最后檢測的結果一般。
今天寫程序期間還有其他的有意思的地方我也有記錄。
- java foreach 遍歷,for(File file :testDataDir.listFiles()),jdk1.6后支持。
- private final String EOL = System.getProperty("line.separator"); 回車換行的字符串表示