OpenCV3 识别图中表格-JAVA 实现

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

关于 JAVA 学习 OpenCV 的内容,函数讲解。内容我均整理在 GitHubd的OpenCV3-Study-JAVA

OpenCV 3 识别图中表格-Java 实现

1. 说明

网上大部分资料,都是针对 C++的,python、java 的例子太少了。所以最近在做这个的时候,把他记录下来,也可以帮助一些人少走弯路。

OpenCV 确实强大,强大到每一个方法,都能 google 到一篇专题文章,在写的过程中,参考了许多资料,最终完成了实现和注释。

但是这仅仅是入门,找到表格后的利用才是后面的核心。比如:

  1. 表格的 OCR 识别,识别表头,内容数据,形成结构化数据。
  2. 图片按照顺序,转 Word文档或者保存为 html,这样就可以完成格式的转化,方便在 web 端查看,用户下载。
  3. 其他利用...

本文仅针对效果较好的,无倾斜,背景干净的图片进行识别。复杂的情况会可能无法满足,需要进一步处理。仅仅是个入门。

2. 开发环境

  • macOS Sierra 10.12.4
  • IntelliJ IDEA 2017
  • Junit 4.12
  • JDK 1.8

因为在 mac 下通过 brew 安装的 opencv ,所以包都是跟当前系统匹配的,安装目录也是一致的。

Windows 下需要根据自己的系统环境,位数,修改代码的loadLibraries,决定加载的动态库文件。

3. 代码实现

import org.junit.Test;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;import java.io.File;
import java.util.*;/*** @Author : alexliu* @Description : opencv 测试* @Date : Create at 下午3:12 2018/1/26*/
public class TestOpenCV {String test_file_path = System.getProperty("user.dir") + File.separator + "testFiles";static {//加载动态链接库时,不使用System.loadLibrary(xxx);。 而是使用 绝对路径加载:System.load(xxx);/** 加载动态库** 第一种方式 --------------System.loadLibrary(Core.NATIVE_LIBRARY_NAME);* loadLibrary(Core.NATIVE_LIBRARY_NAME); //使用这种方式加载,需要在 IDE 中配置参数.* Eclipse 配置:http://opencv-java-tutorials.readthedocs.io/en/latest/01-installing-opencv-for-java.html#set-up-opencv-for-java-in-eclipse* IDEA 配置 :http://opencv-java-tutorials.readthedocs.io/en/latest/01-installing-opencv-for-java.html#set-up-opencv-for-java-in-other-ides-experimental** 第二种方式 --------------System.load(path of lib);* System.load(your path of lib) ,方式比较灵活,可根据环境的系统,位数,决定加载内容*/loadLibraries();}/*** 读取 table*/@Testpublic void readTable(){Mat source_image = Imgcodecs.imread(test_file_path + "/table-3.jpg");//灰度处理Mat gray_image = new Mat(source_image.height(), source_image.width(), CvType.CV_8UC1);Imgproc.cvtColor(source_image,gray_image,Imgproc.COLOR_RGB2GRAY);//二值化Mat thresh_image = new Mat(source_image.height(), source_image.width(), CvType.CV_8UC1);// C 负数,取反色,超过阈值的为黑色,其他为白色Imgproc.adaptiveThreshold(gray_image, thresh_image,255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY,7,-2);this.saveImage("out-table/1-thresh.png",thresh_image);//克隆一个 Mat,用于提取水平线Mat horizontal_image = thresh_image.clone();//克隆一个 Mat,用于提取垂直线Mat vertical_image = thresh_image.clone();/** 求水平线* 1. 根据页面的列数(可以理解为宽度),将页面化成若干的扫描区域* 2. 根据扫描区域的宽度,创建一根水平线* 3. 通过腐蚀、膨胀,将满足条件的区域,用水平线勾画出来** scale 越大,识别的线越多,因为,越大,页面划定的区域越小,在腐蚀后,多行文字会形成一个块,那么就会有一条线* 在识别表格时,我们可以理解线是从页面左边 到 页面右边的,那么划定的区域越小,满足的条件越少,线条也更准确*/int scale = 10;int horizontalsize = horizontal_image.cols() / scale;// 为了获取横向的表格线,设置腐蚀和膨胀的操作区域为一个比较大的横向直条Mat horizontalStructure = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(horizontalsize, 1));// 先腐蚀再膨胀 new Point(-1, -1) 以中心原点开始// iterations 最后一个参数,迭代次数,越多,线越多。在页面清晰的情况下1次即可。Imgproc.erode(horizontal_image, horizontal_image, horizontalStructure, new Point(-1, -1),1);Imgproc.dilate(horizontal_image, horizontal_image, horizontalStructure, new Point(-1, -1),1);this.saveImage("out-table/2-horizontal.png",horizontal_image);// 求垂直线scale = 30;int verticalsize = vertical_image.rows() / scale;Mat verticalStructure = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1, verticalsize));Imgproc.erode(vertical_image, vertical_image, verticalStructure, new Point(-1, -1),1);Imgproc.dilate(vertical_image, vertical_image, verticalStructure, new Point(-1, -1),1);this.saveImage("out-table/3-vertical.png",vertical_image);/** 合并线条* 将垂直线,水平线合并为一张图*/Mat mask_image = new Mat();Core.add(horizontal_image,vertical_image,mask_image);this.saveImage("out-table/4-mask.png",mask_image);/** 通过 bitwise_and 定位横线、垂直线交汇的点*/Mat points_image = new Mat();Core.bitwise_and(horizontal_image, vertical_image, points_image);this.saveImage("out-table/5-points.png",points_image);/** 通过 findContours 找轮廓** 第一个参数,是输入图像,图像的格式是8位单通道的图像,并且被解析为二值图像(即图中的所有非零像素之间都是相等的)。* 第二个参数,是一个 MatOfPoint 数组,在多数实际的操作中即是STL vectors的STL vector,这里将使用找到的轮廓的列表进行填充(即,这将是一个contours的vector,其中contours[i]表示一个特定的轮廓,这样,contours[i][j]将表示contour[i]的一个特定的端点)。* 第三个参数,hierarchy,这个参数可以指定,也可以不指定。如果指定的话,输出hierarchy,将会描述输出轮廓树的结构信息。0号元素表示下一个轮廓(同一层级);1号元素表示前一个轮廓(同一层级);2号元素表示第一个子轮廓(下一层级);3号元素表示父轮廓(上一层级)* 第四个参数,轮廓的模式,将会告诉OpenCV你想用何种方式来对轮廓进行提取,有四个可选的值:*      CV_RETR_EXTERNAL (0):表示只提取最外面的轮廓;*      CV_RETR_LIST (1):表示提取所有轮廓并将其放入列表;*      CV_RETR_CCOMP (2):表示提取所有轮廓并将组织成一个两层结构,其中顶层轮廓是外部轮廓,第二层轮廓是“洞”的轮廓;*      CV_RETR_TREE (3):表示提取所有轮廓并组织成轮廓嵌套的完整层级结构。* 第五个参数,见识方法,即轮廓如何呈现的方法,有三种可选的方法:*      CV_CHAIN_APPROX_NONE (1):将轮廓中的所有点的编码转换成点;*      CV_CHAIN_APPROX_SIMPLE (2):压缩水平、垂直和对角直线段,仅保留它们的端点;*      CV_CHAIN_APPROX_TC89_L1  (3)or CV_CHAIN_APPROX_TC89_KCOS(4):应用Teh-Chin链近似算法中的一种风格* 第六个参数,偏移,可选,如果是定,那么返回的轮廓中的所有点均作指定量的偏移*/List<MatOfPoint> contours = new ArrayList<MatOfPoint>();Mat hierarchy = new Mat();Imgproc.findContours(mask_image,contours,hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE,new Point(0,0));List<MatOfPoint> contours_poly = contours;Rect[] boundRect = new Rect[contours.size()];LinkedList<Mat> tables = new LinkedList<Mat>();//循环所有找到的轮廓-点for(int i=0 ; i< contours.size(); i++){MatOfPoint point = contours.get(i);MatOfPoint contours_poly_point = contours_poly.get(i);/** 获取区域的面积* 第一个参数,InputArray contour:输入的点,一般是图像的轮廓点* 第二个参数,bool oriented = false:表示某一个方向上轮廓的的面积值,顺时针或者逆时针,一般选择默认false*/double area = Imgproc.contourArea(contours.get(i));//如果小于某个值就忽略,代表是杂线不是表格if(area < 100){continue;}/** approxPolyDP 函数用来逼近区域成为一个形状,true值表示产生的区域为闭合区域。比如一个带点幅度的曲线,变成折线** MatOfPoint2f curve:像素点的数组数据。* MatOfPoint2f approxCurve:输出像素点转换后数组数据。* double epsilon:判断点到相对应的line segment 的距离的阈值。(距离大于此阈值则舍弃,小于此阈值则保留,epsilon越小,折线的形状越“接近”曲线。)* bool closed:曲线是否闭合的标志位。*/Imgproc.approxPolyDP(new MatOfPoint2f(point.toArray()),new MatOfPoint2f(contours_poly_point.toArray()),3,true);//为将这片区域转化为矩形,此矩形包含输入的形状boundRect[i] = Imgproc.boundingRect(contours_poly.get(i));// 找到交汇处的的表区域对象Mat table_image = points_image.submat(boundRect[i]);List<MatOfPoint> table_contours = new ArrayList<MatOfPoint>();Mat joint_mat = new Mat();Imgproc.findContours(table_image, table_contours,joint_mat, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);//从表格的特性看,如果这片区域的点数小于4,那就代表没有一个完整的表格,忽略掉if (table_contours.size() < 4)continue;//保存图片tables.addFirst(source_image.submat(boundRect[i]).clone());//将矩形画在原图上Imgproc.rectangle(source_image, boundRect[i].tl(), boundRect[i].br(), new Scalar(0, 255, 0), 1, 8, 0);}for(int i=0; i< tables.size(); i++ ){//拿到表格后,可以对表格再次处理,比如 OCR 识别等this.saveImage("out-table/6-table-"+(i+1)+".png",tables.get(i));}this.saveImage("out-table/7-source.png",source_image);}private void saveImage(String path,Mat image){String outPath = this.test_file_path + File.separator + path;File file = new File(outPath);//目录是否存在this.dirIsExist(file.getParent());Imgcodecs.imwrite(outPath,image);}private void dirIsExist(String dirPath){File dir = new File(dirPath);if(!dir.exists()){dir.mkdirs();}}/*** 加载动态库*/private static void loadLibraries() {try {String osName = System.getProperty("os.name");String opencvpath = System.getProperty("user.dir");//windowsif(osName.startsWith("Windows")) {int bitness = Integer.parseInt(System.getProperty("sun.arch.data.model"));//32位系统if(bitness == 32) {opencvpath=opencvpath+"\\opencv\\x86\\Your path to .dll";}//64位系统else if (bitness == 64) {opencvpath=opencvpath+"\\opencv\\x64\\Your path to .dll";} else {opencvpath=opencvpath+"\\opencv\\x86\\Your path to .dll";}}// mac oselse if(osName.equals("Mac OS X")){opencvpath = "/usr/local/Cellar/opencv/3.4.0_1/share/OpenCV/java/libopencv_java340.dylib";}System.out.println(opencvpath);System.load(opencvpath);} catch (Exception e) {throw new RuntimeException("Failed to load opencv native library", e);}}

4. 实现效果

输入图片说明

5. 参考资料

OpenCV处理拍照表格 此文是一个专题,有多篇

OpenCV-检测并提取表格

广告栏: 欢迎关注我的 个人博客

转载于:https://my.oschina.net/u/3767256/blog/1615720

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/278505.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

内存泄露 体现在哪个数字上_Microsoft刚刚泄漏了一个新的开始菜单。 你喜欢哪个?...

内存泄露 体现在哪个数字上NTAuthority on TwitterTwitter上的NTAuthorityMicrosoft messed up today, releasing an internal build of Windows 10 to Windows Insiders. This build was never meant to see the light of day, but it features a new Start menu design—with…

简述 Spring Cloud 是什么

很多同学都了解了Spring &#xff0c;了解了 Spring Boot, 但对于 Spring Cloud 是什么还是比较懵逼的。 本文带你简单的了解下&#xff0c;什么是Spring Cloud。 Spring Cloud 是什么 从字面理解&#xff0c;Spring Cloud 就是致力于分布式系统、云服务的框架。 Spring Cloud …

js DOM节点

元素节点 4种方式获取 var oDiv document.getElementById("box");        //通过Id获取元素var oDiv document.getElementsByClassName("div")[0];   //通过类名获取元素  --》[ 0 ] 必须写var oDiv document.getElementsByTagName("…

python web scraping

2019独角兽企业重金招聘Python工程师标准>>> 最近在看《Web Scraping with Python》&#xff0c;借此来熟悉Python2.7如何开始编程。 发现书上主要使用的 http://example.webscraping.com/ 网站有部分变化&#xff0c;书中的代码有点无法对照使用&#xff0c;因此稍…

傅里叶变换的物理意义

用三角函数表示周期函数 傅里叶的相关理论始于下面假设&#xff1a;对于周期为1的信号$f(t)$&#xff0c;可以由不同频率的三角函数组成&#xff0c; $f(t) \frac{a_0}{2}\displaystyle{\sum^{\infty}_{k1}}(a_kcos(2\pi kt)b_ksin(2\pi kt))$ 组成的基础波形为一个信号对&…

天猫年度总结

2019独角兽企业重金招聘Python工程师标准>>> 鲁大师天猫工作总结 时间&#xff1a;2017年10月22日-1月30日 1、对代理商进行6大区域的划分管理&#xff0c;有专门的客服指导。 2、加班费申请和车费报销制度。 3、简化了特权订金2阶段改成1阶段&#xff0c;极大的方便…

因特网使用期限_Internet死亡时使用PC的其他方式

因特网使用期限Nothing is more annoying than getting your Internet connection shut down, due to weather, or perhaps forgetting to pay your bill. Let’s take a look at some ways you can be productive and entertained without the Internet. 没有什么比由于天气原…

【基础操作】线性基详解

线性基是一个奇妙的集合&#xff08;我摘的原话&#xff09; 这里以非 $OI$ 的角度介绍了线性基 基础部分 模板题 给你 $n$ 个数的集合&#xff0c;让你选出任意多个不重复的数&#xff0c;使得它们的异或和最大。 线性基是什么 我们称集合 $B$ 是集合 $S$ 的线性基&#xff0c…

节省大量教科书的三种潜在风险方法

Photo by Sultry 摄影&#xff1a; Sultry You can always save money on textbooks by buying online, going ebook, or renting what you need. But there are riskier ways to save a buck that just may yield even greater payoff, such as getting the international or …

解决内网搭建本地yum仓库。

2019独角兽企业重金招聘Python工程师标准>>> 一、使用iso镜像搭建本地yum仓库&#xff1b; 1、挂载镜像到/mnt目录下&#xff1a; [rootDasoncheng ~]# mount /dev/cdrom /mnt mount: /dev/sr0 is write-protected, mounting read-only2、备份配置文件&#xff0c;并…

通过用 .NET 生成自定义窗体设计器来定制应用程序

本文讨论&#xff1a; ? 设计时环境基本原理 ? 窗体设计器体系结构 ? Visual Studio .NET 中窗体设计器的实现 ? 为自己的应用程序编写窗体设计器而需要实现的服务 在很多年中&#xff0c;MFC 一直是生成基于 Windows? 的应用程序的流行框架。MFC 包含一个可以使窗体生成、…

airdrop 是 蓝牙吗_您可以在Windows PC或Android手机上使用AirDrop吗?

airdrop 是 蓝牙吗Aleksey Khilko/Shutterstock.comAleksey Khilko / Shutterstock.comApple’s AirDrop is a convenient way to send photos, files, links, and other data between devices. AirDrop only works on Macs, iPhones, and iPads, but similar solutions are av…

vue加百度统计代码(亲测有效)

申请百度统计后&#xff0c;会得到一段JS代码&#xff0c;需要插入到每个网页中去&#xff0c;在Vue.js项目首先想到的可能就是&#xff0c;把统计代码插入到index.html入口文件中&#xff0c;这样就全局插入&#xff0c;每个页面就都有了;这样做就涉及到一个问题&#xff0c;V…

如何将Rant变成生产力电动工具

Ranting doesn’t have to be a waste of breathe and time. You can turn a rant into a powerful tool for productivity. Learn how to transform your sense of victim hood and irritability to self-empowerment and mental clarity. 狂欢不必浪费呼吸和时间。 您可以将r…

linux 下使用 curl post

命令&#xff1a; curl -X POST -d /etc/lazada/lazada_tracking.txt http://localhost:8080/booking/rs/LazadaService/post --header "Content-Type:application/json" -d 后台 / &#xff1a; post 的 body 体 &#xff45;&#xff47;&#xff1a; {"a…

服务治理·理论篇(一)

0、故事主角 呱呱乐 是一家互联网金融公司。主营现金贷、p2p理财、消费分期业务。 公司现有技术人员800名&#xff0c;系统极其庞杂&#xff0c;每日稳定处理25w左右的订单量&#xff0c;有抢购活动时&#xff0c;系统的QPS(Query Per Second)峰值达到了3w。 系统虽然庞杂&…

2019-1-92.4G射频芯片培训资料

2019-1-92.4G射频芯片培训资料 培训 RF 小书匠 欢迎走进zozo的学习之旅。 2.4G芯片选型2.4G芯片开发Q&A2.4G芯片选型 芯片类型 soc防盗标签2.4G无线芯片选型发射器收发器LSD2RF-1600-V1.1 调制方式射频基础 2.4G芯片开发 原理图 发射优先收发均衡PCB topbottomlayout规…

在Outlook 2010中使用对话视图

One of the new features in Outlook 2010 is the ability to use Conversation View for easier management of your email conversations. Here we will take a quick look at how to use the new feature. Outlook 2010中的新功能之一是可以使用“对话视图”来更轻松地管理电…

openresty capture

local args {} args["name"] "张三" args["sex"] "男"local captureRes; if ngx.var.request_method "POST" thencaptureRes ngx.location.capture(/dsideal_yy/test, {method ngx.HTTP_POST, headers { ["Cont…

Day10:html和css

Day10:html和css <html> <body> <h1>标题</h1> <p>段落</p> </body> </html>HTML 是用来描述网页的一种语言&#xff0c;超文本标记语言&#xff0c;不是一种编程语言&#xff0c;而是一种标记语言&#xff0c;是一套标记标签…