快速入門開發實現訂單類圖片識別結果抽象解析

1、背景

面對訂單數據紙質文件或圖片,僅靠人眼識別的話效率很低,需引入機器學習來識別和解析圖片以提升效率。當前市面上已有收費的圖片識別服務,包括阿里、百度等,識別效果較好,但針對訂單類圖片,不只要關注圖片上的文字,還要關注文字所在的行列,來分出每條數據和數據詳細字段。java

本文主要介紹一種針對訂單類圖片識別結果進行行列解析的抽象流程和方案,幫助提升開發效率。linux

注:本文只提供思路,不提供源碼。另外,本文不介紹人工智能圖片識別,感興趣的同窗能夠上網查詢相關資料。正則表達式

2、解析流程

對於圖像處理,opencv算是比較優秀的工具,所以將其選作本文圖像處理首選軟件。算法

  • 爲了使圖片識別率更高,須要先作圖片矯正,這裏採用較爲簡單的霍夫變換加去噪聲點算法矯正圖片。
  • 圖片矯正後,調用圖片識別服務獲取結果,通常結果格式包括響應碼、錯誤描述、文字塊列表(文字和四點座標)等。
  • 而後使用抽象的俄羅斯方塊法根據識別結果獲取行列信息。
  • 最後根據行列信息組裝每一行數據並顯示。

3、細節處理

3.1 opencv安裝概要

opencv安裝,本文只作簡單提示,不展開介紹,之後有時間單獨發文。ubuntu

1)windowswindows

  • 下載編譯好的包,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 矯正探索

圖片矯正探索之路較爲艱辛,起初咱們想了一個比較簡單的方案:jvm

  • 先調用圖片識別服務,獲取到結果。
  • 而後根據每個字塊的四角座標判斷出每一個字塊的傾斜角。
  • 再根據去燥算法算出平均的傾斜角。

理論上這個方案是可行的,但實踐證實咱們錯了,由於圖片識別服務返回的座標圖片不許確,多數圖片算出的結果都是錯誤的。工具

經查發現霍夫變換有可能解決這個問題,因而開始嘗試學習霍夫變換和去燥算法,最終發現可行,並抽象出公共方法,僅需簡單配置一些參數就能完成矯正。

圖片矯正分爲兩步:

  • 第一步:正反矯正,判斷圖片傾斜角度是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 霍夫變化抽象封裝

基本流程:

  • 定義相關參數
  • 讀取圖片
  • 灰度二值化處理
  • 使用opencv畫出輪廓
  • 根據參數要求屢次畫霍夫變換線,直到線數量知足參數爲止
  • 遍歷畫出的線,分出橫線和豎線,根據配置計算出每條線的角度
  • 使用去噪聲算法(須要根據非0數自動重複計算)算出平均傾斜角度
  • 使用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 俄羅斯方塊法

思路概要:

  • 拿到識別結果數據。
  • 先把全部數據的y座標進行排序。
  • 遍歷排序結果,先把第一條放入第一列結果集中。
  • 從第二條開始和第一列結果集對比。
  • 對比方法:若是在第一列結果集其中一條數據的右側,則認爲是新列;若是在y軸方法和第一列結果集中某些數據重疊了,則認爲是新列。
  • 若是以上兩條都不是,則認爲本條數據還在當前列中,放入第一列結果集。
  • 以此類推,繼續對比,直到對比到最後一列最後一條數據。
  • 按照上述方法,反過來,以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)中間須要保存一些過程圖片,例如矯正過程的若干張圖、俄羅斯方塊法識別結果的連線圖等。畢竟這種項目在查問題時靠日誌是沒用的,還得靠這些中間圖才能更快查到問題。

4、總結

本文提到的方案不能徹底解決全部訂單類圖片解析問題,能夠作到新手快速入門快速開發,若是您有更好思路歡迎交流。

做者:劉鵬飛

來源:宜信技術學院

相關文章
相關標籤/搜索