使用opencv+tesseract识别图片中的表格

描述

在java环境中使用opencv和tesserac识别一个图片表格

环境:opencv和tesseract安装在linux环境下,docker将运行springboot服务

opencv和tesseract的安装和docker加载可参考之前的文章

过程

将图片进行预处理,过滤掉颜色等干扰元素

提取图片的水平线和垂直线,并进行重叠过滤

得到水平线和垂直线的交点,根据交点构建单元格

对每个单元格进行识别

1.转换

将image转换成mat

private  Mat bufferedImageToMat(BufferedImage bufferedImage) {Mat mat = new Mat();try {// Convert BufferedImage to byte arrayByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();ImageIO.write(bufferedImage, "png", byteArrayOutputStream);byteArrayOutputStream.flush();byte[] imageInByte = byteArrayOutputStream.toByteArray();byteArrayOutputStream.close();// Convert byte array to MatMatOfByte matOfByte = new MatOfByte(imageInByte);mat = Imgcodecs.imdecode(matOfByte, Imgcodecs.IMREAD_UNCHANGED);} catch (IOException e) {e.printStackTrace();}return mat;
}

2.图片预处理

原图:

将图片灰度化,并进行边缘检测

灰度化

 //image为加载的图片Mat imread = bufferedImageToMat(image);Mat gray = new Mat();Imgproc.cvtColor(imread, gray,Imgproc.COLOR_BGR2GRAY);

边缘检测

Mat edges = new Mat();
Imgproc.Canny(gray, edges, 50, 150);

3.检测水平线和垂直线

识别水平线和垂直线

            List<MatOfPoint> verticalLines = new ArrayList<>();List<MatOfPoint> horizontalLines = new ArrayList<>();for (int i = 0; i < lines.rows(); i++) {double[] val = lines.get(i, 0);if (isVertical(val)) {verticalLines.add(new MatOfPoint(new Point(val[0], val[1]), new Point(val[2], val[3])));} else if (isHorizontal(val)) {horizontalLines.add(new MatOfPoint(new Point(val[0], val[1]), new Point(val[2], val[3])));}}

水平线和垂直线的阈值可根据实际情况调节 

    private  boolean isVertical(double[] line) {// 实现判断线是否垂直的逻辑return Math.abs(line[0] - line[2]) < 1.0; // 这里的阈值需要根据实际情况调整}private  boolean isHorizontal(double[] line) {// 实现判断线是否水平的逻辑return Math.abs(line[1] - line[3]) < 1.0; // 这里的阈值需要根据实际情况调整}

 

4.重叠过滤

过滤掉相邻太近,应该为同一条线的线段

    private  List<MatOfPoint> overlappingFilter(List<MatOfPoint> lines, int sortingIndex) {List<MatOfPoint> uniqueLines = new ArrayList<>();// 按照 sortingIndex 进行排序if(sortingIndex == 0){//行,检查y坐标lines.sort(Comparator.comparingDouble(line -> calculateLineCenter(line).y));}else{//列检查x坐标lines.sort(Comparator.comparingDouble(line -> calculateLineCenter(line).x));}double distanceThreshold = 5;for (int i = 0; i < lines.size(); i++) {MatOfPoint line1 = lines.get(i);Point[] pts1 = line1.toArray();// 如果 uniqueLines 为空或当前线与最后一条线不重复,则添加到 uniqueLines 中if (uniqueLines.isEmpty() || !isDuplicate(pts1, uniqueLines.get(uniqueLines.size() - 1).toArray(), distanceThreshold)) {uniqueLines.add(line1);}}return uniqueLines;}private  Point calculateLineCenter(MatOfPoint line) {Point[] pts = line.toArray();return new Point((pts[0].x + pts[1].x) / 2, (pts[0].y + pts[1].y) / 2);}

5.水平线和垂直线的焦点

得到水平线和垂直线的焦点

            List<List<Point>> intersectionList = new ArrayList<>();//交点列表for (MatOfPoint hLine : horizontalLines) {List<Point> intersectionRow = new ArrayList<>();for (MatOfPoint vLine : verticalLines) {Point intersection = getIntersection(hLine, vLine);intersectionRow.add(intersection);}intersectionList.add(intersectionRow);}

获取两条线的焦点

    private Point getIntersection(MatOfPoint line1, MatOfPoint line2) {Point[] points1 = line1.toArray();Point[] points2 = line2.toArray();double x1 = points1[0].x, y1 = points1[0].y, x2 = points1[1].x, y2 = points1[1].y;double x3 = points2[0].x, y3 = points2[0].y, x4 = points2[1].x, y4 = points2[1].y;double det = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);double x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / det;double y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / det;return new Point(x, y);}

6.构建单元格

             List<List<Rect>> cells = new ArrayList<>();// 构建单元格for (int i = 0; i < intersectionList.size() - 1; i++) {List<Rect> rowCells = new ArrayList<>();for (int j = 0; j < intersectionList.get(i).size() - 1; j++) {Point p1 = intersectionList.get(i).get(j);Point p2 = intersectionList.get(i).get(j + 1);Point p3 = intersectionList.get(i + 1).get(j);Rect cell = new Rect((int) p1.x, (int) p1.y, (int) (p2.x - p1.x), (int) (p3.y - p1.y));rowCells.add(cell);}cells.add(rowCells);}

7.对每个单元格进行识别

           for(int i=0;i<cells.size();i++){List<String> row = new ArrayList<>();for(int j=0;j<cells.get(i).size();j++){Rect cell = cells.get(i).get(j);Mat cellImage = new Mat(gray, cell);BufferedImage bufferedImage = matToBufferedImage(cellImage);if(bufferedImage == null)continue;String text = tess.doOCR(bufferedImage);row.add(text);}}
    private  BufferedImage matToBufferedImage(Mat mat) {int type = BufferedImage.TYPE_BYTE_GRAY;if (mat.channels() > 1) {type = BufferedImage.TYPE_3BYTE_BGR;}int bufferSize = mat.channels() * mat.cols() * mat.rows();byte[] buffer = new byte[bufferSize];mat.get(0, 0, buffer); // 获取所有像素值BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type);final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();System.arraycopy(buffer, 0, targetPixels, 0, buffer.length);return image;}

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

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

相关文章

我的2023年度总结(一)

在本文开始之前&#xff0c;先对我2023年的所为进行一些道歉&#xff1a; 部分工作中的客户/合作伙伴&#xff0c;在2023年我可能时长怠慢了您的消息。但我真不是故意的(有时可能在忙其他事情)。2024年&#xff0c;如有任何问题请尽可能抛过来吧。部分粉丝朋友&#xff0c;甚至…

【Java开发岗面试】八股文—Java框架(Spring+SpringMVC+MyBatis+SpringBoot)

声明&#xff1a; 背景&#xff1a;本人为24届双非硕校招生&#xff0c;已经完整经历了一次秋招&#xff0c;拿到了三个offer。本专题旨在分享自己的一些Java开发岗面试经验&#xff08;主要是校招&#xff09;&#xff0c;包括我自己总结的八股文、算法、项目介绍、HR面和面试…

ctfshow 新手必刷菜狗杯 谜之栅栏题解记录

知识点&#xff1a;图片比较 拿到之后&#xff0c;有两个图片&#xff0c;直观看不出什么&#xff0c;尝试用工具比较。 用010editor打开其中一个&#xff0c;010editor的工具里面就有比较文件选项&#xff0c;比较两个文件&#xff0c;发现有一处不同。题目说栅栏&#xff0c…

基于时空的Ramsar湿地自动淹没映射利用Google Earth Engine

题目:Spatiotemporal‑based automated inundation mapping of Ramsar wetlands using Google Earth Engine 期刊:Scientific Reports 第一作者:Manish KumarGoyal 发表单位:University of Nebraska 发表日期:2023年 1. 摘要 研究背景:湿地是生态系统中最关键的组成…

如果你是这样选择项目的,那么再创业也不会成功的。2024新兴创业项目,2024普通人失业怎么创业

普通人怎么创业的&#xff1f;我昨天晚上吃饭的时候想了一个项目&#xff0c;觉得很可以&#xff0c;去干干怎么样&#xff1f;只能说死定了。没有任何数据支撑&#xff0c;想象出来的项目&#xff0c;它的成功率会非常低&#xff0c;因为在博概率&#xff0c;而且成功率不会超…

Avalonia学习(十五)-OxyPlot

今天开始继续Avalonia练习。展示一些样例&#xff0c;尤其是第三方库的使用。 本节&#xff1a;OxyPlot 1.引入OxyPlot.Avalonia 2.项目引入 在Main方法里增加OxyPlotModule.EnsureLoaded()方法调用。 public static void Main(string[] args) {OxyPlotModule.EnsureLoade…

别找了!前端那些好用的网站都在这里了!【文末送书】

&#x1f340;前言 好用的网站千千万万&#xff0c;如果你还发现好用的网站&#xff0c;欢迎在评论区中留言分享&#x1f601;&#xff0c;赠书活动在文末哟&#xff0c;中奖者可以从给出的五本书中任意挑选自己喜欢的那本 文章目录 &#x1f340;前言 &#x1f340;一、渐变…

深度学习 | 注意力机制、自注意力机制

卷积神经网络的思想主要是通过卷积层对图像进行特征提取&#xff0c;从而达到降低计算复杂度的目的&#xff0c;利用的是空间特征信息&#xff1b;循环神级网络主要是对序列数据进行建模&#xff0c;利用的是时间维度的信息。 而第三种 注意力机制 网络&#xff0c;关注的是数据…

认识计算机网络——计算机网络的概念

计算机网络是指将多台计算机通过通信介质连接起来&#xff0c;以便共享资源、交换信息和进行协作的技术体系。在现代社会中&#xff0c;计算机网络已经成为了各个领域的重要基础设施&#xff0c;改变了人们的生活方式和工作方式。本文将介绍计算机网络的基本概念、组成要素和发…

46、激活函数 - Relu 激活

本节介绍一个在神经网络中非常常见的激活函数 - Relu 激活函数。 什么是ReLU激活函数 ReLU 英文名为 Rectified Linear Unit,又称修正线性单元,是一种简单但很有效的激活函数,它的定义如下: 即当输入 x 大于零时,输出等于他自己;当输入小于等于零时,输出为零,下面是re…

OpenCV-Python(29):图像特征

目录 目标 背景介绍 常用特征 应用场景 目标 理解什么是图像特征 为什么图像特征很重要 为什么角点很重要 背景介绍 相信大多数人都玩过拼图游戏吧。首先你们拿到一张图片的一堆碎片&#xff0c;你要做的就是把这些碎片以正确的方式排列起来从而重建这幅图像。问题是&…

操作系统:分页存储管理方式

页式存储管理中&#xff0c;主存空间按页分配&#xff0c;可用一张“位示图”构成主存分配表。假设主存容量为2M字节&#xff0c;页面长度为512字节&#xff0c;若用字长为32位的字作主存分配的“位示图”需要多少个字&#xff1f;如页号从1开始&#xff0c;字号和字内位号&…