1. 程式人生 > >java實現的身份證照片臉部識別(頭像截圖) 以及OCR字型識別

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的伺服器下載,沒有FQ的話可能下載失敗,需要的童鞋可以到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/ 不用FQ可以下得到)

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可提升的空間還很大,如果有時間的話多用用不同大小的字型來訓練可以獲得更高的識別率。