Android離線身份證等圖片文字識別
部落格地址: https://blog.csdn.net/Weiye__Lee/article/details/80952724
寫在前面
最近因為業務需要,要在Android端實現個掃描身份證識別其中文字的功能,網上溜達了一圈。
Android比較推薦的是:
GitHub: https://github.com/rmtheis/tess-two
當然也有第三方提供的解決方案,比如百度提供的文字識別: http://ai.baidu.com/tech/ocr 。
咱做技術的還是先折騰折騰,特此記錄下過程,也希望能幫助到同樣折騰的人
效果圖(名字打了馬賽克)

拍照

識別
過程
先理下要實現這樣的效果,我們需要做些什麼
- 在Android端需要自定義個含取景框的自定義相機
- 對拍攝的圖片進行灰度化、二值化處理
- 引入tess-two 庫,呼叫相應api將步驟2處理後的圖片傳入處理
1、自定義相機
這一步主要是寫個佈局,使用Camera和SurfaceView做個自定義相機,具體實現可看demo程式碼,裡面有個取景框是專門用來截取出我們關心的區域的
2、圖片處理
機器視覺分為三個階段 : 影象轉化、影象分析、影象理解。若要將一幅影象轉化為方便分析理解的格式,有一個很關鍵的過程就是“影象二值化”。一幅影象能否分析理解的準確很大程度上來說取決於二值化(非黑即白)效果的好壞。而在二值化之前,有一個重要步驟,那便是“影象灰度化”
所以,先將圖片灰度化,這裡有個公式:f(x) = R 0.3+G 0.51+B*0.11,實際需要做的就是將圖片的每個畫素(這裡假定android裡用ARGB表示一個畫素點)的red、green、blue取出並代入此公式算出每個點的灰度值,這樣便實現了灰度化

灰度化之前

灰度化之後
再來看看二值化,二值化的原理就是取一個閾值,然後將每個畫素點的灰度值和這個閾值進行比較,如果大於閾值則定為白色,反之為黑色(這裡假定要識別的影象是白底黑字),如此一來便實現了二值化。可以看到,最重要的是這個閾值,該怎麼取值才合理,最簡單的閾值取定便是取整幅圖畫的均值了:

二值化之前

二值化之後
效果看上去還不錯,實際上用到身份證識別或文字識別上,受陰影等因素的影響,效果就差很多了,因此,優化演算法還是很有必要的,網上流傳著多種二值化算閾值的演算法,這裡目前嘗試了以下幾種:
- 閾值迭代演算法(效果不理想,字型的筆畫容易站在一起,陰影影響大)
https://www.cnblogs.com/gxclmx/p/6916515.html - 基於Otsu閾值二值化(跟上面的迭代演算法效果差不多,陰影影響大)
https://blog.csdn.net/mao0514/article/details/47041597 - 矩陣二值化演算法
https://blog.csdn.net/lj501886285/article/details/52425157
這個演算法的閾值是自適應的,對於每個畫素點都構造了一個小矩陣來衡量閾值的取值,也就是說每個點都跟它周圍的細節相關,這樣比起用單一的整體閾值去衡量每個點,顯然更具說服力。而事實證明,這個演算法應用後的二值化測試效果確實挺不錯的
3、tess-two api識別
這一步就比較簡單了,直接上步驟:
- 引入依賴
dependencies { implementation 'com.rmtheis:tess-two:9.0.0' }
- 呼叫api識別
TessBaseAPI tessBaseAPI = new TessBaseAPI(); tessBaseAPI.init(DATAPATH, "chi_sim");//傳入訓練檔案目錄和訓練檔案 tessBaseAPI.setImage(bitmap); String text = tessBaseAPI.getUTF8Text();
就這樣短短4行程式碼便可識別出文字了,這裡有個坑要注意,看下tessBaseAPI.init這個方法原始碼:
public boolean init(String datapath, String language) { return init(datapath, language, OEM_DEFAULT); }
可以看到又呼叫了另一個init方法
public boolean init(String datapath, String language, @OcrEngineMode int ocrEngineMode) { if (datapath == null) throw new IllegalArgumentException("Data path must not be null!"); if (!datapath.endsWith(File.separator)) datapath += File.separator; File datapathFile = new File(datapath); if (!datapathFile.exists()) throw new IllegalArgumentException("Data path does not exist!"); File tessdata = new File(datapath + "tessdata"); if (!tessdata.exists() || !tessdata.isDirectory()) throw new IllegalArgumentException("Data path must contain subfolder tessdata!"); //noinspection deprecation if (ocrEngineMode != OEM_CUBE_ONLY) { for (String languageCode : language.split("\\+")) { if (!languageCode.startsWith("~")) { File datafile = new File(tessdata + File.separator + languageCode + ".traineddata"); if (!datafile.exists()) throw new IllegalArgumentException("Data file not found at " + datafile); // Catch some common problematic initialization cases. if (languageCode.equals("ara") || (languageCode.equals("hin") && ocrEngineMode == OEM_DEFAULT)) { boolean sampleCubeFileExists = new File(tessdata + File.separator + languageCode + ".cube.params").exists(); if (!sampleCubeFileExists) { throw new IllegalArgumentException("Cube data files not found." + " See https://github.com/rmtheis/tess-two/issues/239"); } } } } } boolean success = nativeInitOem(mNativeData, datapath, language, ocrEngineMode); if (success) { mRecycled = false; } return success; }
這裡注意下面這一段程式碼,api要求DATAPATH目錄下,必須有tessdata這樣一個子目錄,也就是說,訓練檔案必須放在這個子目錄下
File tessdata = new File(datapath + "tessdata"); if (!tessdata.exists() || !tessdata.isDirectory()) throw new IllegalArgumentException("Data path must contain subfolder tessdata!");
一個文字識別的demo就此完成了,稍後會傳上demo到github,demo做的是身份證,所以對圖片的擷取處理是針對身份證的,當然也可應用到其他營業執照啥的了。
最後,在查資料的時候發現一個身份證識別demo: IdCardReconition ,處理效果挺快,效果也蠻不錯的,但是檢視程式碼後發現處理都是在so檔案裡並且處理貌似只針對身份證,也不清楚是怎麼做的,有了解的望告知
附上github: TextOcrExample
待完善
- 影象處理的演算法是有待完善的,特別是速度上
- tess-two api識別的速度上,也可考慮針對特定場景定製訓練檔案,這樣速度上也會有所提升
PS:對於速度上的要求,個人覺得可以在網路暢通的情況下,採用上傳圖片到伺服器去處理,然後再反饋回給客戶端。在服務端可做的事就多了,單臺伺服器計算資源就好過手機太多,況且我們還可以做分散式併發處理呢?速度相信是槓槓的,像百度這些第三方一般也是提供服務上傳圖片來識別的,速度那是相當快。最後感謝閱讀,如果有什麼不對的望大神指正,喜歡就star一下唄,謝謝!