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

news2025/1/22 23:00:57

描述

在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 array
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         
            ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
      
            byteArrayOutputStream.flush();

            byte[] imageInByte = byteArrayOutputStream.toByteArray();

            byteArrayOutputStream.close();

            // Convert byte array to Mat
            MatOfByte 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.coloradmin.cn/o/1347452.html

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

相关文章

2023 年四川省职业院校技能大赛“信息安全管理与评估”样题

2023 年四川省职业院校技能大赛&#xff08;高等职业教育&#xff09; “信息安全管理与评估”样题 竞赛需要完成三个阶段的任务&#xff0c;分别完成三个模块&#xff0c;总分共计 1000分。三个模块内容和分值分别是&#xff1a; 第一阶段&#xff1a;模块一 网络平台搭建与设…

Windows系统历史版本简介详细版

学习目标&#xff1a; 目录 学习目标&#xff1a; 学习内容&#xff1a; 学习产出&#xff1a; Windows 11的全新用户界面设计&#xff1a;学习新的任务栏、开始菜单、窗口管理等界面元素的使用与操作。 Windows 11的新功能和特点&#xff1a;学习新的虚拟桌面、Microsoft Team…

【MMdetection】MMdetection从入门到进阶

基础环境安装 步骤 0. 从官方网站下载并安装 Miniconda。 步骤 1. 创建并激活一个 conda 环境。 conda create --name openmmlab python3.8 -y conda activate openmmlab步骤 2. 基于 PyTorch 官方说明安装 PyTorch。 pip install torch2.0.1 torchvision0.15.2 torchaudio…

前端--基础 目录文件夹和根目录 VScode打开目录文件夹

目录 目录文件夹和根目录 &#xff1a; 目录文件夹 &#xff1a; 根目录 &#xff1a; VScode 打开目录文件夹 &#xff1a; VScode 打开文件夹 &#xff1a; 拖拽目录文件夹 &#xff1a; 目录文件夹和根目录 &#xff1a; 我们都清楚&#xff0c;在实际的工作中会…

[python]python使用M-LSD直线检测算法onnx部署模型实时检测

介绍 github地址&#xff1a;https://github.com/navervision/mlsd LSD (M-LSD)一种用于资源受限环境的实时轻量线段检测器。它利用了极其高效的 LSD 架构和新颖的训练方案&#xff0c;包括 SoL 增强和几何学习方案。模型可以在GPU、CPU甚至移动设备上实时运行。算法已开源&a…

ChatGPT 对SEO的影响

ChatGPT 的兴起是否预示着 SEO 的终结&#xff1f; 一点也不。事实上&#xff0c;如果使用得当&#xff0c;它可以让你的 SEO 工作变得更加容易。 强调“正确使用时”。 你可以使用ChatGPT来帮助进行关键字研究的头脑风暴部分、重新措辞你的内容、生成架构标记等等。 但你不…

LeetCode每日一题.02(螺旋矩阵||)

螺旋矩阵&#xff1a; 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]] 示例 2&#xff1a; 输入…

HarmonyOS应用程序包快速修复

快速修复概述 快速修复是HarmonyOS系统提供给开发者的一种技术手段&#xff0c;支持开发者以远快于应用升级的方式对应用程序包进行缺陷修复。和全量应用升级软件版本相比&#xff0c;快速修复的主要优势在小、快和用户体验好。在较短的时间内不中断正在运行的应用的情况下&am…

<Icon-ResizER>support

If you get any questions in using app, email me caohechunhotmail.com.

【leetcode100-025】【链表/快慢指针】环形链表

【题干】 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&a…

AI电商时代开始:阿里能否反杀拼多多

“AI电商时代刚刚开始&#xff0c;对谁都是机会&#xff0c;也是挑战。” 针对阿里员工对于拼多多财报和电商等的讨论&#xff0c;马云在阿里内网罕见地参与了谈论并发言。 阿里巴巴一向雷厉风行&#xff0c;已打响了AI电商的“第一炮”。 根据《晚点LatePost》报道&#xff…

C Primer Plus 第6版 编程练习 chapter 12

文章目录 1. 第1题1.1 题目描述1.2 编程源码1.3 结果显示 2. 第2题2.1 题目描述2.2 编程源码2.2.1 pe12-2a.h源码2.2.2 pe12-2a.c源码2.2.3 pe12-2b.c源码 2.3 结果显示 3. 第3题3.1 题目描述3.2 编程源码3.2.1 pe12-2a.h源码3.2.2 pe12-2a.c源码3.2.3 pe12-2b.c源码 3.3 结果显…

2024年【安全员-B证】考试报名及安全员-B证新版试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-B证考试报名考前必练&#xff01;安全生产模拟考试一点通每个月更新安全员-B证新版试题题目及答案&#xff01;多做几遍&#xff0c;其实通过安全员-B证考试试题很简单。 1、【多选题】《中华人民共和国消防法…

液晶时钟设计

#include<reg51.h> //包含单片机寄存器的头文件 #include<stdlib.h> //包含随机函数rand()的定义文件 #include<intrins.h> //包含_nop_()函数定义的头文件 sbit RSP2^0; //寄存器选择位&#xff0c;将RS位定义为P2.0引脚 sbit RWP2^1; //读写选…

《Python机器学习原理与算法实现》学习笔记

以下为《Python机器学习原理与算法实现》&#xff08;杨维忠 张甜 著 2023年2月新书 清华大学出版社&#xff09;的学习笔记。 根据输入数据是否具有“响应变量”信息&#xff0c;机器学习被分为“监督式学习”和“非监督式学习”。 “监督式学习”即输入数据中即有X变量&…

如何通过易舟云财务软件,查看账簿的总账?

如何通过易舟云财务软件&#xff0c;查看账簿的总账&#xff1f; 前言财务软件操作步骤 前言 总账是会计中的一个重要概念&#xff0c;是指记录一个企业在一定时期内所有经济业务活动的账簿。总账记录了企业的资产、负债、所有者权益、收入和费用等各项会计科目的变动情况。 …

详解数组的轮转

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

软件测试/测试开发丨Python 常用第三方库 yaml

YAML 一种数据序列化格式用于人类的可读性和与脚本语言的交互一种被认为可以超越 XML、JSON 的配置文件 YAML 基本语法规则 大小写敏感使用缩进表示层级关系缩进时不允许使用 Tab 键&#xff0c;只允许使用空格缩进的空格数目不重要&#xff0c;只要相同层级的元素左侧对齐即…

mixins混淆请求字典封装库

摘要&#xff1a; 页面请求要使用到很多重点的查询&#xff0c;写在本页面的逻辑代码太混乱&#xff0c;所以可以抽离封装成功一个js库混淆进来&#xff01; commonMixins.js: import {Toast} from "vant"; export const oplistMix {mounted() {this.GETSTORE_LOCA…

vue-springboot基于JavaWeb的宠物店兽医站管理系统

ide工具&#xff1a;IDEA 或者eclipse 编程语言: java 数据库: mysql5.7 框架&#xff1a;ssmspringboot都有 前端&#xff1a;vue.jsElementUI 详细技术&#xff1a;HTMLCSSJSspringbootSSMvueMYSQLMAVEN 数据库工具&#xff1a;Navicat结合现有兽医站体系的特点&#xff0c;运…