java實現的身份證照片臉部識別(頭像截圖) 以及OCR字體識別


斷斷續續地折騰了大半個月,終於把身份證照片臉部識別以及OCR字體識別功能用Java實現了,需求很簡單:通過攝像頭所照的一張放在黑色底板上的身份證照,識別照片上身份證里面的人名和地址(OCR中文),再截取身份證上的頭像用Base64編碼。生成一個規定格式的XML然后把人名,地址和頭像照片的編碼放到XML里面。

其中用到了OpenCV, Tesseract-OCR 還有一些對BufferedImage進行圖像處理的東西。代碼倒也不算很復雜,但是其中幾個問題的確很燒腦細胞,花了不少時間才解決(Jedi本人辛苦開發+原創碼字博文共享,希望能):

1.  相片里面身份證的位置不確定問題: 底板比較大身份證可以在上面隨意位置擺放

解決方法很簡單,人臉識別時候把人臉的位置坐標返回出來,利用這個坐標來確定身份證位置也大大縮小需要字體OCR識別的區域。不需要整張照片做OCR也節省了許多運算時間。

2.  OpenCV人臉識別的容錯率問題(有時候一張照片可以識別出三個頭像來, 連個模糊的色塊也能識別為一個頭像,汗啊),當然了,識別人臉時候需要的lbpcascade_frontalface.xml是必須的。上網找就有不少地方有的下載。運行時需要這個xml位於你的可運行jar所在的同個路徑下

解決方法是思路就是指定進行人臉識別的最大和最小像素范圍minSize和maxSize(就是多大尺寸以內才去分析是不是人臉,當然要具體情況具體設置,minsize最好設大一點點,不然一個模糊小色塊都會可能被誤當作是人臉,T_T),然后設置參數scaleFactor,minNeighbors和flags來提高識別正確率,具體代碼如下:

      public int[] detectFace(String imageFileName) {

            int[] RectPosition = new int[4];

            CascadeClassifier faceDetector = new CascadeClassifier("lbpcascade_frontalface.xml");

            Mat image = Highgui.imread(imageFileName);

            MatOfRect faceDetections = new MatOfRect();

            //指定人臉識別的最大和最小像素范圍

            Size minSize = new Size(120, 120);

            Size maxSize = new Size(250, 250);

//參數設置為scaleFactor=1.1f, minNeighbors=4, flags=0 以此來增加識別人臉的正確率

            faceDetector.detectMultiScale(image, faceDetections, 1.1f, 4, 0, minSize, maxSize);

            //對識別出來的頭像畫個方框,並且返回這個方框的位置坐標和大小

            for (Rect rect : faceDetections.toArray()) {

                  Core.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x

                              + rect.width, rect.y + rect.height), new Scalar(0, 255, 0));

                  RectPosition[0]=rect.x;

                  RectPosition[1]=rect.y;

                  RectPosition[2]=rect.width;

                  RectPosition[3]=rect.height;

                  System.out.println(rect.x +" "+ rect.y + " "+rect.width+" "+rect.height);

            }

// 下面注釋掉的三行可以用來生成識別出的人臉圖像,保存下來以便Debug用

//          String filename = "face.png";

//          System.out.println(String.format("Writing %s", filename));

//          Highgui.imwrite(filename, image);

            return RectPosition;

}

3.  Tesseract-OCR對身份證上的字體識別率比較低的問題。Tesseract自帶的chi_sim.traineddatad(識別庫)對身份證上字體的識別率偏低,想了很多辦法,也用了很多時間精力去用身份證專用的華文細黑字體庫來做訓練 (關於如何使用jTessBoxEditor-1.2來生成tif圖片和矯正每個字體的識別,還有如何用命令行進行訓練生成tessdata識別庫的問題,學到的經驗多到幾乎可以寫一大篇文檔了。這個要等有時間再總結了),搗騰了很多次之后發現出來的tessdata並沒有比原生自帶的chi_sim識別率提高多少,可能是需要從大到小不同字體都進行訓練才行。

4.  身份證照片解析度不高,1600x1200的攝像頭照出來的效果不敢恭維而且身份證相片上面有很多弧形干擾線導致識別率更加低

為了提高解析度消除干擾線,我用了Tess4J自帶的ImageHelper.convertImageToBinary()把圖像處理成黑白照片,一次性解決了字體受干擾的問題,不過由照片上筆畫比較多的字體筆畫間隙出現了模糊,我在轉換黑背之前加上了一個圖像處理的步驟,消除字體筆畫間隙的干擾讓字體更加清晰,方法如下:

      public BufferedImage replaceWithWhiteColor(BufferedImage bi) {

            int[] rgb = new int[3];

            int width = bi.getWidth();

            int height = bi.getHeight();

            int minx = bi.getMinX();

            int miny = bi.getMinY();

            /**

            * 遍歷圖片的像素,為處理圖片上的雜色,所以要把指定像素上的顏色換成目標白色 用二層循環遍歷長和寬上的每個像素

            */

            int hitCount = 0;

            for (int i = minx; i < width-1; i++) {

                  for (int j = miny; j < height; j++) {

                        /**

                        * 得到指定像素(i,j)上的RGB值,

                        */

                        int pixel = bi.getRGB(i, j);

                        int pixelNext = bi.getRGB(i+1, j);

                        /**

                        * 分別進行位操作得到 r g b上的值

                        */

                        rgb[0] = (pixel & 0xff0000) >> 16;

                        rgb[1] = (pixel & 0xff00) >> 8;

                        rgb[2] = (pixel & 0xff);

                        /**

                        * 進行換色操作,我這里是要換成白底,那么就判斷圖片中rgb值是否在范圍內的像素

                        */

//經過不斷嘗試,RGB數值相互間相差15以內的都基本上是灰色,

//對以身份證來說特別是介於73到78之間,還有大於100的部分RGB值都是干擾色,將它們一次性轉變成白色

                        if ((Math.abs(rgb[0] - rgb[1]) < 15)

                                    && (Math.abs(rgb[0] - rgb[2]) < 15)

                                    && (Math.abs(rgb[1] - rgb[2]) < 15) &&

                                    (((rgb[0] > 73)&& (rgb[0] < 78))||(rgb[0] > 100))) {

                              //進行換色操作,0xffffff是白色

                              bi.setRGB(i, j, 0xffffff);         

                        }

                  }

            }

            return bi;

}

5.  關於Java調用DLL的問題,OpenCV和Tesseract-OCR都不是Java原生的,需要去load外部的DLL動態鏈接庫文件

OpenCV需要用到opencv_java2410.dll (這個DLL和相關的opencv-2410.jar可以下載並安裝opencv-2.4.10.exe之后在openCV的安裝路徑下找到opencv\build\java\, 里面有64位和32位不同的版本,需要根據你運行時的JRE/JDK是否為64位來決定),而且需要在運行時候用System.load("C:\\opencv_java2410.dll") 導入,這里我用了絕對路徑去load這個opencv_java2410.dll. 所以在發布的時候可以用一個bat判斷是否在C:\根目錄下是否存在這個dll,沒有的話就立馬copy一個過去再運行java。(bat批處理來調用java的時候可以指定jre,也可以避免java版本64位或者32位的問題)

而相對來說Tess4j就簡單許多,首先只需要下載tesseract-ocr-setup-3.02.02.exe並安裝上(默認的chi_sim. Traineddatad大概40M,安裝程序自動連接到Google的服務器下載,沒有翻牆的話可能下載失敗,需要的童鞋可以到CSDN下載http://download.csdn.net/download/java_mamad/7308769,下載了之后放到C:\Program Files (x86)\Tesseract-OCR\tessdata\就行。然后把安裝目錄下的liblept168.dll, liblept168d.dll 和libtesseract302.dll拷貝到你的java項目根目錄就行了,打包的時候這三個文件也是必須的,必須放到你的可運行jar包的相同路徑下。(剛發現tesseract-OCR的所有東西都可以到SourceForge下載:http://sourceforge.net/projects/tesseract-ocr-alt/files/ 不用翻牆可以下得到)

6.  打包jar包發布的時候如果用一個大的jar包里面包含了所有的依賴包(包括jai-imageio.jar)就會出現

sun.misc.ServiceConfigurationError: javax.imageio.spi.ImageOutputStreamSpi: Provider com.sun.med

ia.imageioimpl.stream.ChannelImageOutputStreamSpi could not be instantiated: java.lang.IllegalArgumentException: vendorName == null!

這個是由於imageIO的調用失敗導致的,我嘗試了很多做法,譬如修改Manifest .MF文件都還是不行T_T,最后還是選擇分開單獨打包jar和依賴包的方法才行。一個可運行jar包走天下的願望泡湯了。

7.   調試中發現一張大照片來做OCR識別字體的結果比你限制一個小的區域單獨做OCR的效果差很多,所以如果有可能的話還是盡量縮小你要OCR識別字體的范圍,不斷識別率提高了而且處理起來速度也快很多。我就是通過定位身份證頭像大致位置來估算身份證文字位置大小區域來實現的。

8.  在開發中我為了識別完每張圖片生成XML之后刪除圖片,用了file.deleteOnExit(); 結果發現幾張圖處理完之后全部圖都刪除了剩下最后一張圖沒法刪除。

研究了大半天才發現問題所在:BufferedImage image = ImageIO.read(new FileInputStream(imgPath)); 

這樣寫的代碼是存在問題的,因為這個FileInputStream在被GC之前是沒有辦法去close掉。沒有close就會導致了文件無法被刪除而且file.deleteOnExie()是不會有IOException拋出的.  查找BUG會相當的confuse費時費力.  正確的寫法應該是:

FileInputStream fins = new FileInputStream(imgPath);

BufferedImage image = ImageIO.read(fins);

最后用fins.close(); 關閉這個FileInputStream, 文件就能刪除了,良好的編程代碼習慣還是要慢慢養成,不然要花很多時間在Debug上面。

9.  另外我也試過了手機上的App掃描全能王和PC上的漢王OCR,感覺都是挺不錯的成熟產品識別率挺高的,可惜沒有提供第三方可調用的接口(當然不開放了,不然人家怎么賺錢^_^)Google的Tesseract可提升的空間還很大,如果有時間的話多用用不同大小的字體來訓練可以獲得更高的識別率。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM