如何从 Android 应用程序中的图像中提取发票数据?

Posted

技术标签:

【中文标题】如何从 Android 应用程序中的图像中提取发票数据?【英文标题】:How to extract invoices data from an image in android app? 【发布时间】:2020-03-28 03:11:43 【问题描述】:

我的任务是从扫描的文档/JPG 中提取文本,然后只获取下面提到的 6 个值,以便我可以在下一个屏幕/活动中自动填写表单数据。

我在带有 Blaze 版本(付费)的 android 应用程序中使用了 google cloud vision api,结果是文本块,但我只想从中提取一些信息,我该如何实现?

账单或收据可能一直都不同,但我想要 Ex 的所有发票文本块中的 6 件东西 -

    供应商 帐户 说明 截止日期 发票编号 金额

是否有任何可用的工具/第 3 方库,以便我可以在我的 android 开发中使用。

注意 - 我认为不需要任何收据或账单图像样本,因为它可以是任何类型的账单或发票,我们只需要从提取的文本中提取 6 个提到的内容。

【问题讨论】:

github.com/tesseract-ocr/tesseract这个很好。如果没有适用于 Android 的接口,您可以制作一个 php 脚本,该脚本通过简单的 POST(来自您的应用程序)接收您的图像,对其进行处理,然后将带有请求数据(供应商、帐户等)的有效 JSON 发送回您的应用程序. 如果您已经收到文本,您可以“过滤”它。例如,您为供应商建立了一个起始分隔符和一个结束分隔符。然后你解析接收到的文本并提取你需要的内容。输入数据示例:Vendor name: Baj Bussiness Phone: +4546464446546。字段vendor 的数据介于Vendor name:Phone 之间。您可以使用 startend 分隔符提取该数据。 @besciualex,如果我只使用单一格式的收据,这是一个好方法,但在我的情况下,我可以有 n 个账单/收据格式,所以这种逻辑肯定会失败。 账单格式的数量是有限的?还是完全未知?两周前,我为一位朋友做了类似的事情,他有大约 10 个独特的 PDF 模板(表单字段中的值不同)。我们做了 10 个模板,每个模板都有自己的分隔符。每个 PDF 都有一些独特的东西,可用于识别正确的分隔符模板。如果您的帐单格式数量有限,您可以实现相同的逻辑。 一个像你这样的应用程序,我们在我们的商场里有。您将扫描您的账单,然后获得积分。如果应用程序未能从账单中识别信息,它会告诉用户他的图像将由某人手动处理。处理图像的人还将负责创建新的分隔符模板。据我所知,你想要什么(对于无限格式自动 100%)它需要人工智能。 【参考方案1】:

在接下来的场景中,我将创建两种虚构的账单格式,然后编写代码算法来解析它们。因为不懂JAVA,所以只写算法。

在第一列,我们有两张钞票的精美图片。在第二列中,我们有从 OCR 软件获得的文本数据。它就像一个简单的文本文件,没有实现任何逻辑。但是我们知道某些可以使它有意义的关键字。 Bellow 是将无意义的文件翻译成完美的逻辑 JSON 的算法。

// Text obtained from BILL format 1
var TEXT_FROM_OCR = "Invoice no 12 Amount 55$
Vendor name BusinessTest 1 Account No 1213113
Due date 2019-12-07  
Description Lorem ipsum dolor est"




// Text obtained from BILL format 2
var TEXT_FROM_OCR ="    BusinessTest22        
Invoice no    19    Amount    12$
Account    4564544    Due date    2019-12-15
Description            
Lorem ipsum dolor est            
Another description line            
Last description line"




// This is a valid JSON object which describes the logic behind the text
var TEMPLATES = 


    "bill_template_1": 
        "vendor":
            "line_no_start": null,                // This means is unknown and will be ignored by our text parsers
            "line_no_end": null,                  // This means is unknown and will be ignored by our text parsers
            "start_delimiter": "Vendor name",     // Searched value starts immediatedly after this start_delimiters
            "end_delimiter": "Account"            // Searched value ends just before this end_delimter
            "value_found": null                   // Save here the value we found
        ,
        "account": 
            "line_no_start": null,                // This means is unknown and will be ignored by our text parsers
            "line_no_end": null,                  // This means is unknown and will be ignored by our text parsers
            "start_delimiter": "Account No",      // Searched value starts immediatedly after this start_delimiters
            "end_delimiter": null                 // Extract everything untill the end of current line
            "value_found": null                   // Save here the value we found
        ,
        "description": 
            // apply same logic as above
        ,
        "due_date" 
            // apply same logic as above
        ,
        "invoice_number" 
            // apply same logic as above
        ,
        "amount" 
            // apply same logic as above
        ,
    ,


    "bill_template_2": 
        "vendor":
            "line_no_start": 0,                    // Extract data from line zero
            "line_no_end": 0,                      // Extract data untill line zero
            "start_delimiter": null,               // Ignore this, because our delimiter is a complete line
            "end_delimiter": null                  // Ignore this, because our delimiter is a complete line
            "value_found": null                    // Save here the value we found
        ,
        "account": 
            "line_no_start": null,                // This means is unknown and will be ignored by our text parsers
            "line_no_end": null,                  // This means is unknown and will be ignored by our text parsers
            "start_delimiter": "Account",         // Searched value starts immediatedly after this start_delimiters
            "end_delimiter": "Due date"           // Searched value ends just before this end_delimter
            "value_found": null                   // Save here the value we found
        ,
        "description": 
            "line_no_start": 6,                   // Extract data from line zero
            "line_no_end": 99999,                 // Extract data untill line 99999 (a very big number which means EOF)
            "start_delimiter": null,              // Ignore this, because our delimiter is a complete line
            "end_delimiter": null                 // Ignore this, because our delimiter is a complete line
            "value_found": null                   // Save here the value we found
        ,
        "due_date" 
            // apply same logic as above
        ,
        "invoice_number" 
            // apply same logic as above
        ,
        "amount" 
            // apply same logic as above
        ,
    



// ALGORITHM

// 1. convert into an array the TEXT_FROM_OCR variable (each index, means a new line in file)
// in javascript we would do something like this:

TEXT_FROM_OCR = TEXT_FROM_OCR.split("\r\n");


var MAXIMUM_SCORE = 6; // we are looking to extract 6 values, out of 6


foreach TEMPLATES as TEMPLATE_TO_PARSE => PARSE_METADATA

    SCORE = 0; // for each field we find, we increment score


    foreach PARSE_METADATA as SEARCHED_FIELD_NAME => DELIMITERS_METADATA

        // Search by line first
        if (DELIMITERS_METADATA['line_no_start'] !== NULL && DELIMITERS_METADATA['line_no_end'] !== NULL)

            // Initiate value with an empty string
            DELIMITERS_METADATA['value_found'] = '';

            // Concatenate the value found across these lines
            for (LINE_NO = DELIMITERS_METADATA['line_no_start']; LINE_NO <= DELIMITERS_METADATA['line_no_end']; LINE_NO++)

                // Add line, one by one as defined by your delimiters
                DELIMITERS_METADATA['value_found'] += TEXT_FROM_OCR[ LINE_NO ];

            

            // We have found a good value, continue to next field
            SCORE++;
            continue;
        



        // Search by text delimiters
        if (DELIMITERS_METADATA['start_delimiter'] !== NULL)



            // Search for text inside each line of the file
            foreach TEXT_FROM_OCR as LINE_CONTENT

                // If we found start_delimiter on this line, then let's parse it
                if (LINE_CONTENT.indexOf(DELIMITERS_METADATA['start_delimiter']) > -1)

                    // START POSITION OF OUR SEARCHED VALUE IS THE OFFSET WE FOUND + THE TOTAL LENGTH OF START DELIMITER
                    START_POSITION = LINE_CONTENT.indexOf(DELIMITERS_METADATA['start_delimiter']) + LENGTH( DELIMITERS_METADATA['start_delimiter'] );


                    // by default we try to extract all data from START_POSITION untill the end of current line
                    END_POSITION = 999999999999; // till the end of line


                    // HOWEVER, IF THERE IS AN END DELIMITER DEFINED, WE WILL USE THAT
                    if (DELIMITERS_METADATA['end_delimiter'] !== NULL)

                        // IF WE FOUND THE END DELIMITER ON THIS LINE, WE WILL USE ITS OFFSET as END_POSITION
                        if (LINE_CONTENT.indexOf(DELIMITERS_METADATA['end_delimiter']) > -1)

                            END_POSITION = LINE_CONTENT.indexOf(DELIMITERS_METADATA['end_delimiter']);

                        
                    


                    // SUBSTRACT THE VALUE WE FOUND
                    DELIMITERS_METADATA['value_found'] = LINE_CONTENT.substr(START_POSITION, END_POSITION);

                    // We have found a good value earlier, increment the score
                    SCORE++;

                    // break this foreach as we found a good value, and we need to move to next field
                    break;
                

            

        
    


    print(TEMPLATE_TO_PARSE obtained a score of SCORE out of MAXIMUM_SCORE):

最后,您将知道哪个模板提取了大部分数据,并根据该模板用于该账单。随时在 cmets 中提出任何问题。如果我留下 45 分钟来写这个答案,我肯定也会回复你的 cmets。 :)

【讨论】:

如果我们可以提取 6 个提到的东西,或者我们需要使用我不知道的机器学习部分,是否有任何第三方工具或库可用。 学习人工智能需要时间。这就是为什么我推荐上面的算法。其中使用的库(从 IMAGE => BLOCK OF TEXT 翻译的库)称为github.com/tesseract-ocr/tesseract

以上是关于如何从 Android 应用程序中的图像中提取发票数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何从android中的jpeg图像中提取文本[关闭]

从 pdf 或图像格式的发票中提取数据

如何从图库中的图像中获取(提取)文本并搜索该文本 - Android?

如何从图像 Android 应用程序中提取文本

如何从图像Android应用程序中提取文本

应付发票捕获或提取自动化[关闭]