快速入門開發實現訂單類圖片識別結果抽象解析
一、背景
面對訂單數據紙質文件或圖片,僅靠人眼識別的話效率低,需引入機器學習來識別和解析圖片以提高效率。當前市面已有收費的圖片識別服務,包括阿里、百度等,識別效果較好,但針對訂單類圖片,不僅需要關注圖片上的文字,還需要關注文字所在的行列,來分出每條數據和數據詳細字段。
本篇內容主要介紹一種針對識別完結果進行行列解析的抽象流程和方案,來提高開發效率。
本文只提供思路,不提供源碼。另外,本文不介紹人工智能圖片識別,有興趣的同學請關注大神們的文章,這里推薦“宜信技術學院官網文章AI板塊”。
二、解析流程
對于圖像處理,opencv算是比較優秀的,所以選做本文圖像處理首選軟件。為了使圖片識別率更高,需要先做圖片矯正,這里采用較為簡單的霍夫變換加去噪聲點算法矯正圖片。圖片矯正后,調用圖片識別服務獲取結果,一般結果格式包括響應碼、錯誤描述、文字塊列表(文字和四點坐標)等。根據識別結果使用抽象的俄羅斯方塊法來識別結果獲取行列信息。最后根據行列信息組裝每一行數據并顯示。
三、處理細節
3.1 opencv安裝概要
opencv安裝簡單提示,這里不細說,以后有時間單獨發文。
1)windows
- 下載編譯好的包:https://opencv.org/releases/
- 解壓縮到自定義文件夾。
2)linux
- 推薦使用ubuntu,并且最好是全新的系統,因為opencv會依賴很多包,對版本要求也高,解決沖突會很麻煩。
- 下載源碼
- 安裝依賴包
- 編譯安裝
我們使用java調用opencv,這里需要安裝獲取到開發包,windows為opencv_javaxxx.dll,linux為libopencv_javaxxx.so,程序初始化時需要加載到jvm。詳細代碼如下:
- System.load(PropertieUtil.getPropertie("這里是dll或so的完整路徑");
3.2 圖片矯正
3.2.1 矯正探索
圖片矯正探索之路較為艱辛,起初我們想了一個比較簡單的方案:先調用圖片識別服務,獲取到結果,再根據每一個字塊的四角坐標判斷出每個字塊的傾斜角,然后再根據去噪算法算出平均的傾斜角。理論上這個方案是可行的,但實踐證明我們是錯的,因為圖片識別服務返回的坐標圖片不準確,多數的圖片算出的結果都是錯誤的。
經查發現霍夫變換有可能解決這個問題,于是開始嘗試學習霍夫變換和去燥算法,最終發現可行,并抽象出公共方法,僅需簡單配置一些參數就能完成矯正。
圖片矯正分為兩步:
第一步:正反矯正,判斷圖片傾斜角度是90°、180°、270°、0°,這個通過數學方法是無法判斷的,需要引用機器學習。
第二步:角度微調,一般為確定圖片是正的,且傾斜角度在+-30°左右。
需要注意:上面說的辦法不可能通過一套參數來對所有圖片進行微調,但是線上數據證明,針對一類圖片,一套參數基本能讓大多數圖片都矯正正確。
3.2.2 霍夫變換概要
霍夫變換是數學界經典空間變換算法,用于檢測直線,通過大量檢測到的直線的斜率就能計算出圖片傾斜角度。先進行二值化和邊緣檢測,再進行霍夫變換效果更佳。詳細算法內容請自行搜索,本文不細聊。
3.2.3 去噪聲點算法
基本公式:
上限=均值+n*標準差
下限=均值-n*標準差
其中n取值一般為1-4,數值越大表示篩選率越高。最后再將符合的數據求均值。
核心代碼如下:
- /**
- * 利用標準差篩選
- * @param values
- * @return
- */
- private static double[] calcBestCornList(double[] values) {
- // 計算標準差
- StandardDeviation variance = new StandardDeviation();
- double evaluate = variance.evaluate(values);
- Mean mean = new Mean();
- double meanValue = mean.evaluate(values);
- double biggerValue = meanValue + CHOOSE_POWER * evaluate;
- double smallerValue = meanValue - CHOOSE_POWER * evaluate;
- List<Double> selected = Lists.newArrayList();
- for (double value : values) {
- if (value >= smallerValue && value <= biggerValue) {
- selected.add(value);
- }
- }
- double[] selectedValue = new double[selected.size()];
- for (int i = 0; i < selected.size(); i++) {
- selectedValue[i] = selected.get(i);
- }
- logger.info("占比:{}%,篩選后角度數組:{}", (selectedValue.length / (float)values.length) * 100F, selected);
- return selectedValue;
- }
3.2.4 霍夫變化抽象封裝
基本流程:
1、定義相關參數。
2、讀取圖片。
3、灰度二值化處理。
4、使用opencv畫出輪廓。
5、根據參數要求多次畫霍夫變換線,直到線數量滿足參數為止。
6、遍歷畫出的線,分出橫線和豎線,根據配置計算出每條線角度。
7、使用去噪聲算法(需要根據非0數自動重復計算)算出平均傾斜角度。
8、使用opencv旋轉圖片。
核心代碼如下:
- /**
- * 矯正圖片,通過霍夫變換矯正
- * @param oldImg 原始圖片
- * @param rotateParam 旋轉參數
- * @return
- */
- public static String rotateHoughLines(File oldFile, String oldImg, RotateParam rotateParam, String cid, String bankCode) throws Exception {
- Mat src= Imgcodecs.imread(oldFile.getAbsolutePath());
- //讀取圖像到矩陣中
- if(src.empty()){
- throw new Exception("no file " + oldFile.getAbsolutePath());
- }
- // 用于計算的圖片矩陣
- Mat mathImg = src.clone();
- // 灰度化
- Imgproc.cvtColor(src, mathImg, Imgproc.COLOR_BGR2GRAY);
- logger.info("二值化完成");
- // 獲取輪廓
- Imgproc.Canny(src, mathImg, rotateParam.getCvtThreshould1(), rotateParam.getCvtThreshould2());
- logger.info("輪廓完成");
- // 霍夫變換獲取角度,詳細代碼略
- double corn = houghLines(mathImg, rotateParam, cid);
- logger.info("霍夫變換完成,角度:{}", corn);
- if(corn == 0) {
- return oldImg;
- }
- return rotateOpenv(oldFile, corn, cid, bankCode);
- }
3.3 常用圖片識別方案
阿里、百度都有提供圖片識別服務,另外如果有實力也可以自己實現,當然不建議自研,因為樣本需求量巨大,時間成本過高。
3.4 識別結果解析
3.4.1 探索之路
本章節為本文重點內容,因為前面所提到的都是較為基礎的服務和算法,大量開發內容都在本章。前期要開發的訂單圖片類型巨量(大于100種),每一類圖片區別很大,我們有幾個人分類型開發,但是每個人所用的方法都不同,且張三開發出來的李四看不懂,不過畢竟面對的是圖片,比較抽象,是可以理解的。
開發一段時間后,我們發現了問題。每種類型最快也要一周才能開發完成,而且解析成功率極低。開發出一套抽象的方法來把行列數據提取出來迫在眉睫。
通過調研發現大家常用兩種方法來提取行列數據,分別為坐標法和標題法,但是這兩種方法解析率都不高。經過幾周思考,終于想出了一套較好的方法,命名為俄羅斯方塊法,解決了問題。
3.4.2 俄羅斯方塊法
思路概要:
1. 拿到識別結果數據。
2. 先把所有數據的y坐標進行排序。
3. 遍歷排序結果,先把第一條放入第一列結果集中。
4. 從第二條開始和第一列結果集對比。
5. 對比方法:
--如果在第一列結果集其中一條數據的右側,則認為是新列。
--如果在y軸方法和第一列結果其中某些數據重疊了,則認為是新列。
6. 如果以上兩條都不是,則認為本條數據還在當前列中,放入第一列結果集。
7. 以此類推,繼續對比,直到對比到最后一列最后一條數據。
8. 按照上面方法,反過來,以x軸為標準,能夠得到行結果集。
思路圖如下:
概要代碼如下:
- // 按照最左上角的x坐標排序
- OcrWordInfo[] sortL = NoTableParseResult.ParseUtil.bubbleSortX(ocrResponse.getPrism_wordsInfo(), false);
- NoTableParseResult ntpr = new NoTableParseResult(param);
- ntpr.setHeight(converImg.height());
- ntpr.setWight(converImg.width());
- for (int i = 0; i < sortL.length; i++) {
- // 當前要比較的數據
- OcrWordInfo ocrWordInfo = sortL[i];
- // 處理當前列數據
- ntpr.getUtil().testCurColData(ocrWordInfo);
- }
- // 處理最后一列
- ntpr.lastCol();
- /**
- * 判斷是否為下一列,并處理
- * @param ocrWordInfo
- * @return
- */
- public void testCurColData(OcrWordInfo ocrWordInfo) {
- // 遍歷當前列已存在的所有數據
- int size = this.test.getCol().size();
- if(size == 0) {
- this.test.addCol(ocrWordInfo);
- return;
- }
- for (int i = 0; i < size; i++) {
- OcrWordInfo temp = this.test.getCol().get(i);
- // 最右邊的數據
- int x1 = temp.getPos().get(1).getX();
- int x2 = temp.getPos().get(2).getX();
- // 當前數據最左邊
- int xx0 = ocrWordInfo.getPos().get(0).getX();
- int xx3 = ocrWordInfo.getPos().get(3).getX();
- int threholdx = this.test.param == null ? 0 : this.test.param.getCoverColXThrehold();
- if(xx0 >= (x1 - threholdx) && xx0 >= (x2 - threholdx) && xx3 >= (x1 - threholdx) && xx3 >= (x2 - threholdx)) {
- // 當前數據在右邊,說明換列了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- this.test.colAdd();
- this.test.addCol(ocrWordInfo);
- return;
- } else {
- // 判斷是否覆蓋坐標
- int y0 = temp.getPos().get(0).getY();
- int y3 = temp.getPos().get(3).getY();
- int yy0 = ocrWordInfo.getPos().get(0).getY();
- int yy3 = ocrWordInfo.getPos().get(3).getY();
- int threhold = (int)Math.round((y3 - y0) * (this.test.param == null ? 0.25 : this.test.param.getCoverThrehold()));
- if(!(yy3 <= (y0 + threhold) || yy0 >= (y3 - threhold))) {
- // 當前列表數據重疊,說明換列了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- this.test.colAdd();
- this.test.addCol(ocrWordInfo);
- return;
- }
- }
- }
- // 執行到這說明沒覆蓋
- this.test.addCol(ocrWordInfo);
- }
3.4.3 解析行數據技巧
技巧總結:
1、俄羅斯方塊法提供去除干擾項的參數,可以根據圖片特點,去除上下左右干擾數據來減少串行列現象。
2、解析數據大致有兩種方法:
- 根據標題列號來判斷數據,這種方法不通用,簡單、規范的圖片識別率高,但是無法適配亂的圖。
- 把每一行數據以間隔符號分割拼到一起,使用正則表達式來‘扣’數據,因為一般同類型訂單圖片,關鍵字段的位置是有特點的,例如金額格式、借貸方向、日期等,這種方法通用,但識別率不高。
*具體使用哪種方法,還需要根據圖片特點進行取舍。
3、 俄羅斯方塊法提供一些微調參數,用于適配一些特殊場景,例如換行列閥值之類的。
4、中間需要保存一些過程圖片,例如矯正過程的若干張圖、俄羅斯方塊法識別結果的連線圖等,畢竟這種項目,查問題時靠日志是沒用的,還得靠這些中間圖才能更快查到問題。
四、總結
本文提到的方案不能完全解決所有訂單類圖片解析問題,但可以做到新手快速入門快速開發,如果您有更好思路歡迎交流。
【本文是51CTO專欄機構宜信技術學院的原創文章,微信公眾號“宜信技術學院( id: CE_TECH)”】