AICon 上海站|日程100%上线,解锁Al未来! 了解详情
写点什么

快速入门开发实现订单类图片识别结果抽象解析

  • 2020-02-06
  • 本文字数:3645 字

    阅读完需:约 12 分钟

快速入门开发实现订单类图片识别结果抽象解析

一、背景

面对订单数据纸质文件或图片,紧靠人眼识别效率低,需引入机器学习来识别图片并解析来提高效率。当前市面已有收费的图片识别服务,包括阿里、百度等,识别效果较好,但针对订单类图片,不仅关注图片上的文字,还关注文字所在的行列,来分出每条数据和数据详细字段。


本篇内容主要介绍一种针对识别完结果进行行列解析的抽象流程和方案,来提高开发效率。


本文只提供思路,不提供源码。另外,不是搞人工智能专业,图片识别太深奥,不过有兴趣的同学请关注大神们的文章,这里推荐“宜信技术学院官网文章 AI 板块”。

二、解析流程

对于图像处理,opencv 算是比较优秀的,所以选做本文图像处理首选软件。为了使图片识别率更高,需要先做图片矫正,这里采用较为简单的霍夫变换加去噪声点算法矫正图片。图片矫正后,调用图片识别服务获取结果,一般结果格式包括响应码、错误描述、文字块列表(文字和四点坐标)等。根据识别结果使用抽象的俄罗斯方块法来识别结果获取行列信息。最后根据行列信息组装每一行数据并显示。

三、细节处理

3.1、opencv 安装概要

opencv 安装简单提示,这里不细说,以后有时间单独发文。


windows


  1. 下载编译好的包,https://opencv.org/releases/

  2. 解压缩到自定义文件夹


linux


  1. 推荐使用 ubuntu,并且最好是全新的系统,因为 opencv 会依赖很多包,对版本要求也高,解决冲突会很麻烦

  2. 下载源码

  3. 安装依赖包

  4. 编译安装


我们使用 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; }</double>
复制代码

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 轴方法和第一列结果其中某些数据重叠了,则认为是新列。


  1. 如果以上两条都不是,则认为本条数据还在当前列中,放入第一列结果集

  2. 以此类推,继续对比直到对比到最后一列最后一条数据

  3. 按照上面方法,反过来,以 x 轴为标准,能够得到行结果集


思路图如下:


1573468661345056731.jpeg


概要代码如下:



// 按照最左上角的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. 第二种方法:把每一行数据以间隔符号分割拼到一起,使用正则表达式来‘扣’数据,因为一般同类型订单图片,关键字段的位置是有特点的,例如金额格式、借贷方向、日期等,这种方法通用,但识别率不高。

  5. *具体使用哪种方法,还需要根据图片特点进行取舍。

  6. 俄罗斯方块法提供一些微调参数,用于适配一些特殊场景,例如换行列阀值之类的。

  7. 中间需要保存一些过程图片,例如矫正过程的若干张图、俄罗斯方块法识别结果的连线图等,毕竟这种项目,查问题时靠日志是没用的,还得靠这些中间图才能更快查到问题。

  8. 四、总结


  9. 本文提到方案不能完全解决所有订单类图片解析问题,可以做到新手快速入门快速开发,如果您有更好思路欢迎交流。


本文转载自宜信技术学院网站。


原文链接:http://college.creditease.cn/detail/323


2020-02-06 21:331042

评论

发布
暂无评论
发现更多内容

面向对象设计模式课程小结

梅子黄时雨

极客大学架构师训练营

Zookeeper集群模式启动

tunsuy

zookeeper 源码分析 socket 分布式集群

Zookeeper的数据剖析

tunsuy

zookeeper 日志分析 事务 快照 数据恢复

Oracle SQL调优系列之看懂执行计划explain

Nicky.Ma

sql

产品失败了,产品经理要不要承担责任?

涛哥 数字产品和业务架构

产品经理

让你眼前一亮的 10 大 TS 项目

阿宝哥

Java typescript 开源 大前端 Web

第三周总结

晨光

[架构师训练营] Week01 -学习总结

谭方敏

rodert单排学习redis进阶【白银一】

JavaPub

Java nosql redis

【非原创】微服务设计

Axe

极客大学架构师训练营 框架开发 模式与重构 JUnit、Spring、Hive核心源码解析 第6课

John(易筋)

spring 极客时间 极客大学 极客大学架构师训练营 JUnit

windows使用docker运行mysql等工具(一)windows安装docker

Java旅途

MySQL Docker

windows使用docker运行mysql等工具(二)安装运行mysql

Java旅途

MySQL Docker

良心推荐 | LeetCode(力扣),算法、数据结构的学习良伴

YoungZY

算法

架构师是怎样炼成的-3-2-设计模式

闷骚程序员

组合设计模式编码&手写单例模式

吴建中

极客大学架构师训练营

Zookeeper通信协议详解

tunsuy

zookeeper TCP/IP 通信协议

组合模式应用

yupi

区块链改变数字营销与广告市场

CECBC

区块链技术 广告业 精准投放 去中介 公开透明

架构师训练营第三周作业和小记

tuuezzy

架构师 极客大学架构师训练营

架构师训练营 第三周 命题作业

RZC

架构师训练营第四周

Melo

手写单例模式

yupi

极客大学架构师训练营 框架开发 第三次作业

John(易筋)

极客时间 设计模式 极客大学 极客大学架构师训练营 框架开发

架构师训练营 第三周 学习总结

RZC

数字货币监管当体现“中国之治”

CECBC

数字货币 CECBC 区块链技术 技术标准 准入和监管

一个汉字占几个字节你真的记住了吗?

Java旅途

第三周手写单例模式(饿汉模式)

吴建中

极客大学架构师训练营

第三周-设计模式-学习总结

吴建中

极客大学架构师训练营

太赞了!一份适合程序员的精选面试题清单。

JackTian

GitHub 开源 编程 程序员 面试

第三周作业

晨光

快速入门开发实现订单类图片识别结果抽象解析_行业深度_刘鹏飞_InfoQ精选文章