ocr
光學字元識別(英語:Optical Character Recognition, OCR)是指對文字資料的影象檔案進行分析識別處理,獲取文字及版面資訊的過程。
Tesseract
Tesseract是Ray Smith於1985到1995年間在惠普布里斯托實驗室開發的一個OCR引擎,曾經在1995 UNLV精確度測試中名列前茅。但1996年後基本停止了開發。2006年,Google邀請Smith加盟,重啟該專案。目前專案的許可證是Apache 2.0。該專案目前支援Windows、Linux和Mac OS等主流平臺。但作為一個引擎,它只提供命令列工具。 現階段的Tesseract由Google負責維護,是最好的開源OCR Engine之一,並且支援中文。
tess-two是Tesseract在Android平臺上的移植。
下載tess-two:
compile 'com.rmtheis:tess-two:8.0.0'複製程式碼
然後將訓練好的eng.traineddata放入android專案的assets資料夾中,就可以識別英文了。
1. 簡單地識別英文
初始化tess-two,載入訓練好的tessdata
private void prepareTesseract() {
try {
prepareDirectory(DATA_PATH + TESSDATA);
} catch (Exception e) {
e.printStackTrace();
}
copyTessDataFiles(TESSDATA);
}
/**
* Prepare directory on external storage
*
* @param path
* @throws Exception
*/
private void prepareDirectory(String path) {
File dir = new File(path);
if (!dir.exists()) {
if (!dir.mkdirs()) {
Log.e(TAG, "ERROR: Creation of directory " + path + " failed, check does Android Manifest have permission to write to external storage.");
}
} else {
Log.i(TAG, "Created directory " + path);
}
}
/**
* Copy tessdata files (located on assets/tessdata) to destination directory
*
* @param path - name of directory with .traineddata files
*/
private void copyTessDataFiles(String path) {
try {
String fileList[] = getAssets().list(path);
for (String fileName : fileList) {
// open file within the assets folder
// if it is not already there copy it to the sdcard
String pathToDataFile = DATA_PATH + path + "/" + fileName;
if (!(new File(pathToDataFile)).exists()) {
InputStream in = getAssets().open(path + "/" + fileName);
OutputStream out = new FileOutputStream(pathToDataFile);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
Log.d(TAG, "Copied " + fileName + "to tessdata");
}
}
} catch (IOException e) {
Log.e(TAG, "Unable to copy files to tessdata " + e.toString());
}
}複製程式碼
拍完照後,呼叫startOCR方法。
private void startOCR(Uri imgUri) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4; // 1 - means max size. 4 - means maxsize/4 size. Don't use value <4, because you need more memory in the heap to store your data.
Bitmap bitmap = BitmapFactory.decodeFile(imgUri.getPath(), options);
String result = extractText(bitmap);
resultView.setText(result);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}複製程式碼
extractText()會呼叫tess-two的api來實現ocr文字識別。
private String extractText(Bitmap bitmap) {
try {
tessBaseApi = new TessBaseAPI();
} catch (Exception e) {
Log.e(TAG, e.getMessage());
if (tessBaseApi == null) {
Log.e(TAG, "TessBaseAPI is null. TessFactory not returning tess object.");
}
}
tessBaseApi.init(DATA_PATH, lang);
tessBaseApi.setImage(bitmap);
String extractedText = "empty result";
try {
extractedText = tessBaseApi.getUTF8Text();
} catch (Exception e) {
Log.e(TAG, "Error in recognizing text.");
}
tessBaseApi.end();
return extractedText;
}複製程式碼
最後,顯示識別的效果,此時的效果還算可以。
2. 識別程式碼
接下來,嘗試用上面的程式識別一段程式碼。
此時,效果一塌糊塗。我們重構一下startOCR(),增加區域性的二值化處理。
private void startOCR(Uri imgUri) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4; // 1 - means max size. 4 - means maxsize/4 size. Don't use value <4, because you need more memory in the heap to store your data.
Bitmap bitmap = BitmapFactory.decodeFile(imgUri.getPath(), options);
CV4JImage cv4JImage = new CV4JImage(bitmap);
Threshold threshold = new Threshold();
threshold.adaptiveThresh((ByteProcessor)(cv4JImage.convert2Gray().getProcessor()), Threshold.ADAPTIVE_C_MEANS_THRESH, 12, 30, Threshold.METHOD_THRESH_BINARY);
Bitmap newBitmap = cv4JImage.getProcessor().getImage().toBitmap(Bitmap.Config.ARGB_8888);
ivImage2.setImageBitmap(newBitmap);
String result = extractText(newBitmap);
resultView.setText(result);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}複製程式碼
在這裡,使用cv4j來實現影象的二值化處理。
CV4JImage cv4JImage = new CV4JImage(bitmap);
Threshold threshold = new Threshold();
threshold.adaptiveThresh((ByteProcessor)(cv4JImage.convert2Gray().getProcessor()), Threshold.ADAPTIVE_C_MEANS_THRESH, 12, 30, Threshold.METHOD_THRESH_BINARY);
Bitmap newBitmap = cv4JImage.getProcessor().getImage().toBitmap(Bitmap.Config.ARGB_8888);複製程式碼
影象二值化就是將影象上的畫素點的灰度值設定為0或255,也就是將整個影象呈現出明顯的黑白效果。影象的二值化有利於影象的進一步處理,使影象變得簡單,而且資料量減小,能凸顯出感興趣的目標的輪廓。
cv4j的github地址:github.com/imageproces…
cv4j 是gloomyfish和我一起開發的影象處理庫,純java實現。
再來試試效果,圖片中間部分是二值化後的效果,此時基本能識別出程式碼的內容。
3. 識別中文
如果要識別中文字型,需要使用中文的資料包。可以去下面的網站上下載。
跟中文相關的資料包有chi_sim.traineddata、chi_tra.traineddata,它們分別表示是簡體中文和繁體中文。
tessBaseApi.init(DATA_PATH, lang);複製程式碼
前面的例子都是識別英文的,所以原先的lang值為"eng",現在要識別簡體中文的話需要將其值改為"chi_sim"。
最後
本專案只是demo級別的演示,離生產環境的使用還差的很遠。
本專案的github地址:github.com/fengzhizi71…
為何說只是demo級別呢?
- 資料包很大,特別是中文的大概有50多M,放在移動端的肯定不合適。一般正確的做法,都是放在雲端。
- 識別文字很慢,特別是中文,工程上還有很多優化的空間。
- 做ocr之前需要做很多預處理的工作,在本例子中只用了二值化,其實還有很多預處理的步驟比如傾斜校正、字元切割等等。
- 為了提高tess-two的識別率,可以自己訓練資料集。