写点什么

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

  • 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:33882

评论

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

设计电商秒杀系统

大眼喵

「架构实战营」

ORACLE进阶(一) 通过EXPDP IMPDP命令实现导dmp

No Silver Bullet

oracle DMP 7月月更

Jenkins抛弃Java 8拥抱Java 11

FunTester

TOGAF认证自学宝典V2.0

涛哥 数字产品和业务架构

企业架构 TOGAF

远程办公之大家一同实现合作编辑资料和开发文档 | 社区征文

Tech技术攻关

远程办公 协同办公 7月日更 初夏征文

自动渗透测试工具核心功能简述

穿过生命散发芬芳

渗透测试 7月月更

聊聊支付流程的设计与实现逻辑

Java 架构

毕业总结

天琪实刚亮

聊聊Flink框架中的状态管理机制

百思不得小赵

大数据 flink 状态 7月月更

「Docker 那些事儿」还不会安装Docker?建议看这篇就够了

Albert Edison

7月月更

OpenHarmony应用开发之ETS开发方式中的Image组件

坚果

HarmonyOS Open Harmony OpenHarmony 3.1 Release 7月月更 harmony

NFT新的契机,多媒体NFT聚合平台OKALEIDO即将上线

西柚子

项目协作的进度如何推进| 社区征文

卢卡多多

初夏征文

疫情常态化大背景下,关于远程办公的思考|社区征文

如浴春风

初夏征文

远程办公之如何推进跨部门项目协作 | 社区征文

Tech技术攻关

远程办公 7月日更 项目协调 初夏征文 工作协调

rxjs Observable filter Operator 的实现原理介绍

汪子熙

typescript 响应式编程 angular RXJS 7月月更

cgroup简介

总想做点什么

Cgroups

NFT新的契机,多媒体NFT聚合平台OKALEIDO即将上线

小哈区块

Python|函数和模块

AXYZdong

7月月更

远程办公工具分享|社区征文

如浴春风

初夏征文

x86汇编语言-从实模式到保护模式 笔记

贾献华

7月月更

SpingCloud集成zookeeper实现服务注册并访问

AI乔治

模块九作业

天琪实刚亮

Vuex(二)

小恺

7月月更

深入理解 SQL 中的 Grouping Sets 语句

元闰子

sql spark spark SQL

TCP拥塞控制详解 | 3. 设计空间

俞凡

算法 网络 TCP拥塞控制

Python 入门指南之开胃菜

海拥(haiyong.site)

7月月更

毕业总结

大眼喵

「架构实战营」

【LeetCode】在每个树行中找最大值Java题解

Albert

LeetCode 7月月更

一入“远程”终不悔,几人欢喜几人愁。| 社区征文

法医

初夏征文

架构实战营 - 第 6 期 毕业总结

乐邦

「架构实战营」

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