如何从 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
之间。您可以使用 start
和 end
分隔符提取该数据。
@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 应用程序中的图像中提取发票数据?的主要内容,如果未能解决你的问题,请参考以下文章