Java使用Opencv + spire.ocr 识别基础表格

Java使用Opencv + spire.ocr 识别表格

最近有做到需要表格图片识别的需求,开始直接用的百度云API,但是要收费,而且需求不算复杂,只要是截图或保存的Excel生成的图片即可,不涉及复杂的表格,有需要的可以了解一下。使用到三方库opencv(视觉库)+ spire.ocr (免费好用的OCR识别文字)
直接看效果:
在这里插入图片描述
19行7列的表格,返回数据:
{“msg”:“操作成功”,“code”:200,“data”:[{“word”:“”,“row”:1,“cal”:1},{“word”:“”,“row”:1,“cal”:2},{“word”:“”,“row”:1,“cal”:3},{“word”:“”,“row”:1,“cal”:4},{“word”:“”,“row”:1,“cal”:5},{“word”:“”,“row”:1,“cal”:6},{“word”:“”,“row”:1,“cal”:7},{“word”:“”,“row”:2,“cal”:1},{“word”:“A-T1-S1M2”,“row”:2,“cal”:2},{“word”:“”,“row”:2,“cal”:3},{“word”:“”,“row”:2,“cal”:4},{“word”:“”,“row”:2,“cal”:5},{“word”:“”,“row”:2,“cal”:6},{“word”:“”,“row”:2,“cal”:7},{“word”:“”,“row”:3,“cal”:1},{“word”:“”,“row”:3,“cal”:2},{“word”:“”,“row”:3,“cal”:3},{“word”:“B-T2-S1M1”,“row”:3,“cal”:4},{“word”:“A-T1-S1M2”,“row”:3,“cal”:5},{“word”:“”,“row”:3,“cal”:6},{“word”:“”,“row”:3,“cal”:7},{“word”:“”,“row”:4,“cal”:1},{“word”:“”,“row”:4,“cal”:2},{“word”:“”,“row”:4,“cal”:3},{“word”:“”,“row”:4,“cal”:4},{“word”:“”,“row”:4,“cal”:5},{“word”:“”,“row”:4,“cal”:6},{“word”:“”,“row”:4,“cal”:7},{“word”:“”,“row”:5,“cal”:1},{“word”:“”,“row”:5,“cal”:2},{“word”:“”,“row”:5,“cal”:3},{“word”:“”,“row”:5,“cal”:4},{“word”:“”,“row”:5,“cal”:5},{“word”:“”,“row”:5,“cal”:6},{“word”:“”,“row”:5,“cal”:7},{“word”:“”,“row”:6,“cal”:1},{“word”:“”,“row”:6,“cal”:2},{“word”:“”,“row”:6,“cal”:3},{“word”:“”,“row”:6,“cal”:4},{“word”:“”,“row”:6,“cal”:5},{“word”:“”,“row”:6,“cal”:6},{“word”:“”,“row”:6,“cal”:7},{“word”:“”,“row”:7,“cal”:1},{“word”:“”,“row”:7,“cal”:2},{“word”:“”,“row”:7,“cal”:3},{“word”:“”,“row”:7,“cal”:4},{“word”:“”,“row”:7,“cal”:5},{“word”:“”,“row”:7,“cal”:6},{“word”:“”,“row”:7,“cal”:7},{“word”:“”,“row”:8,“cal”:1},{“word”:“”,“row”:8,“cal”:2},{“word”:“”,“row”:8,“cal”:3},{“word”:“”,“row”:8,“cal”:4},{“word”:“”,“row”:8,“cal”:5},{“word”:“”,“row”:8,“cal”:6},{“word”:“”,“row”:8,“cal”:7},{“word”:“”,“row”:9,“cal”:1},{“word”:“”,“row”:9,“cal”:2},{“word”:“”,“row”:9,“cal”:3},{“word”:“”,“row”:9,“cal”:4},{“word”:“”,“row”:9,“cal”:5},{“word”:“”,“row”:9,“cal”:6},{“word”:“”,“row”:9,“cal”:7},{“word”:“”,“row”:10,“cal”:1},{“word”:“”,“row”:10,“cal”:2},{“word”:“”,“row”:10,“cal”:3},{“word”:“”,“row”:10,“cal”:4},{“word”:“”,“row”:10,“cal”:5},{“word”:“”,“row”:10,“cal”:6},{“word”:“”,“row”:10,“cal”:7},{“word”:“”,“row”:11,“cal”:1},{“word”:“”,“row”:11,“cal”:2},{“word”:“”,“row”:11,“cal”:3},{“word”:“”,“row”:11,“cal”:4},{“word”:“”,“row”:11,“cal”:5},{“word”:“”,“row”:11,“cal”:6},{“word”:“”,“row”:11,“cal”:7},{“word”:“”,“row”:12,“cal”:1},{“word”:“”,“row”:12,“cal”:2},{“word”:“”,“row”:12,“cal”:3},{“word”:“”,“row”:12,“cal”:4},{“word”:“”,“row”:12,“cal”:5},{“word”:“”,“row”:12,“cal”:6},{“word”:“”,“row”:12,“cal”:7},{“word”:“”,“row”:13,“cal”:1},{“word”:“”,“row”:13,“cal”:2},{“word”:“”,“row”:13,“cal”:3},{“word”:“”,“row”:13,“cal”:4},{“word”:“”,“row”:13,“cal”:5},{“word”:“”,“row”:13,“cal”:6},{“word”:“”,“row”:13,“cal”:7},{“word”:“”,“row”:14,“cal”:1},{“word”:“”,“row”:14,“cal”:2},{“word”:“”,“row”:14,“cal”:3},{“word”:“”,“row”:14,“cal”:4},{“word”:“”,“row”:14,“cal”:5},{“word”:“”,“row”:14,“cal”:6},{“word”:“”,“row”:14,“cal”:7},{“word”:“”,“row”:15,“cal”:1},{“word”:“”,“row”:15,“cal”:2},{“word”:“”,“row”:15,“cal”:3},{“word”:“”,“row”:15,“cal”:4},{“word”:“”,“row”:15,“cal”:5},{“word”:“”,“row”:15,“cal”:6},{“word”:“”,“row”:15,“cal”:7},{“word”:“”,“row”:16,“cal”:1},{“word”:“”,“row”:16,“cal”:2},{“word”:“”,“row”:16,“cal”:3},{“word”:“”,“row”:16,“cal”:4},{“word”:“”,“row”:16,“cal”:5},{“word”:“”,“row”:16,“cal”:6},{“word”:“”,“row”:16,“cal”:7},{“word”:“”,“row”:17,“cal”:1},{“word”:“”,“row”:17,“cal”:2},{“word”:“”,“row”:17,“cal”:3},{“word”:“”,“row”:17,“cal”:4},{“word”:“”,“row”:17,“cal”:5},{“word”:“”,“row”:17,“cal”:6},{“word”:“”,“row”:17,“cal”:7},{“word”:“”,“row”:18,“cal”:1},{“word”:“”,“row”:18,“cal”:2},{“word”:“”,“row”:18,“cal”:3},{“word”:“”,“row”:18,“cal”:4},{“word”:“”,“row”:18,“cal”:5},{“word”:“”,“row”:18,“cal”:6},{“word”:“”,“row”:18,“cal”:7},{“word”:“”,“row”:19,“cal”:1},{“word”:“”,“row”:19,“cal”:2},{“word”:“”,“row”:19,“cal”:3},{“word”:“”,“row”:19,“cal”:4},{“word”:“”,“row”:19,“cal”:5},{“word”:“”,“row”:19,“cal”:6},{“word”:“”,“row”:19,“cal”:7}]}

当然无边框的也可以的,但是要注意,最好把数据设定为适合的行高和列宽,居中对齐,从excel中截图可以使用微信来截图,或者用Snipaste这个小工具,好了,让我们现在开始。

集成spire.ocr进行图片文字识别

使用 OCR 技术扫描识别是获取图片上文字的主要方式。Spire.OCR for Java 能够帮助开发者在 Java 项目中快速批量识别并提取图片上的文字,实现高效的文字提取功能。
由于开源的 tesseract 识别效果不是很好,就采用该OCR工具来识别图片,使用起来效果还是感觉不错的。
官方地址:
https://www.e-iceblue.cn/spire_ocr_java/how-to-scan-and-recognize-text-from-images-in-java-projects.html

pom引入Spire.OCR的依赖,我用的最新版本

<!--ocr图片识别文字-->
<dependency>
    <groupId>e-iceblue</groupId>
    <artifactId>spire.ocr</artifactId>
    <version>${spire.ocr.version}</version>
</dependency>

在application.yml中配置相关参数,主要是必要模型的地址,注意需要换成自己的地址,需要相关文件的可以私信,或者在上面的官网地址下载即可

ocr-scanner:
  language: 'Chinese'
  model-path-linux: '/root/sunpro/ocr'
  model-path-windows: 'E:\spireocr'

在这里插入图片描述
在这里插入图片描述
配置一下扫描类,加入到Spring容器

package com.ruoyi.ocr.config;

import com.spire.ocr.ConfigureOptions;
import com.spire.ocr.OcrException;
import com.spire.ocr.OcrScanner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName:OcrScannerConfig
 * @Author: mjg
 * @Date: 2025/2/12 14:06
 * @Description: ocr 扫描器配置类
 */
@Configuration
public class OcrScannerConfig {

    @Value("${ocr-scanner.model-path-linux}")
    public String modelPathLinux;

    @Value("${ocr-scanner.model-path-windows}")
    public String modelPathWindows;


    @Value("${ocr-scanner.language}")
    public String language;

    @Bean
    public OcrScanner ocrScanner() throws OcrException {
        OcrScanner scanner = new OcrScanner();
        ConfigureOptions configureOptions = new ConfigureOptions();
        // 设置许可证密钥(如有需要)
        // LicenseProvider.setLicenseKey("your-license-key");
        // 创建并配置扫描器
        configureOptions.setLanguage(language);
        if(System.getProperty("os.name").toLowerCase().contains("windows")) {
            configureOptions.setModelPath(modelPathWindows);
        }else{
            configureOptions.setModelPath(modelPathLinux);
        }
        scanner.ConfigureDependencies(configureOptions);
        return scanner;
    }



}

最后编写个Service方法,供Controller接口调用

/**
 * Spire OCR 图片文字识别,限于白底黑字才能识别效果好
 * @param path 图片路径
 **/
@Override
public String ocrWordBySpireString(String path) {

    ocrScanner.scan(path);
    String scannedText = null;
    try {
        scannedText = ocrScanner.getText().toString();
    } catch (OcrException e) {
        LOGGER.error("识别出错:{}",e.getMessage());
        return "";
    }
    if(org.springframework.util.StringUtils.isEmpty(scannedText)){
        return "";
    }
    return scannedText.replaceAll("Evaluation Warning : The version can be used only for evaluation purpose...", "")
            .replaceAll("\\r", "")
            .replaceAll("\n","");

}
@RestController
@RequestMapping("ocr/v2")
public class Ocr2Controller {


    @Resource
    private OcrService ocrService;


    @PostMapping(value = "/recognize", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public AjaxResult recognizeText(@RequestParam("file") MultipartFile file) throws OcrException {
        // 创建临时文件
        File tempFile = null;
        try {
            // 生成临时文件,使用原始文件名和后缀(避免格式问题)
            String originalFilename = file.getOriginalFilename();
            String suffix = originalFilename != null ? originalFilename.substring(originalFilename.lastIndexOf('.')) : ".tmp";
            tempFile = File.createTempFile("spireocr-", suffix);

            // 将上传的文件内容写入临时文件
            file.transferTo(tempFile);
            return AjaxResult.success( ocrService.ocrWordBySpireString(tempFile.getAbsolutePath())); // 返回识别结果
        } catch (IOException e) {
            throw new OcrException("文件处理失败", e);
        } finally {
            // 确保删除临时文件
            if (tempFile != null && tempFile.exists()) {
                try {
                    tempFile.delete();
                } catch (SecurityException e) {
                    System.err.println("无法删除临时文件: " + tempFile.getAbsolutePath());
                }
            }
        }
    }

}

看的人还是挺多的,所以立刻更新了,接着上面,完成了OCR识别文字的接口,让我们试试效果
测试图片
在这里插入图片描述

可以看到效果非常好,1秒接口就有响应结果。

通过opencv处理表格图片,边缘检测

导入opencv的依赖,版本我用的4.5.5-1

    <!--opencv视觉库-->
    <dependency>
        <groupId>org.openpnp</groupId>
        <artifactId>opencv</artifactId>
        <version>${opencv.version}</version>
    </dependency>

opencv的配置,application.yml:

opencv:
  model-path-linux: '/root/sunpro/opencv/libopencv_java454.so'
  model-path-windows: 'E:\BaiduNetdiskDownload\opencv\build\java\x64\opencv_java453.dll'
  widows-temp-upload: 'E:\BaiduNetdiskDownload\opencv\build\java\x64\temp'
  linux-temp-upload: '/root/sunpro/opencv/temp'

由于使用opencv视觉库也是需要对应的模型,也就是dll文件(linux部署需要so文件),这些都可以在opencv官网进行下载https://opencv.org/,如有需要私信联系博主也可以提供,我使用的是临时文件的方式去识别表格,所以配置了两个不同的临时文件位置,以及模型的位置。配置好后,就可以用opencv提供的api,对上传的图片进行二值化、灰度化、边缘检测等操作,识别表格每一个格子,最后拿来ocr识别文字即可,废话不多说,直接上代码:
Controller:

package com.ruoyi.ocr.controller;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.ocr.opencv.IOpencvService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;

/**
 * @ClassName:FormRecognitionController
 * @Author: mjg
 * @Date: 2025/2/13 14:33
 * @Description: 表格识别控制器 识别Excel表格所转成图片
 */
@RestController
@RequestMapping("formRecognition")
public class FormRecognitionController {

    @Resource
    private IOpencvService opencvService;
    /**
     *  上传图片并识别
     **/
    @PostMapping(value = "/identify",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public AjaxResult identify(@RequestParam("file") MultipartFile file){
        // 创建临时文件
        File tempFile = null;
        try {
            //检查文件为空或不为图片格式时返回错误
            if (file == null || file.isEmpty()) {
                return AjaxResult.error("上传文件为空");
            }
            if (!file.getContentType().contains("image")) {
                return AjaxResult.error("上传文件类型不正确,上传文件类型" + file.getContentType() + ",允许类型:image");
            }

            // 生成临时文件,使用原始文件名和后缀(避免格式问题)
            String originalFilename = file.getOriginalFilename();
            String suffix = originalFilename != null ? originalFilename.substring(originalFilename.lastIndexOf('.')) : ".tmp";
            tempFile = File.createTempFile("spireocr-", suffix);

            // 将上传的文件内容写入临时文件
            file.transferTo(tempFile);

            // 使用临时文件路径进行识别
            return   AjaxResult.success(opencvService.readTableByEdgeDetection(tempFile.getAbsolutePath()));

        } catch (IOException e) {
            throw new RuntimeException("文件处理失败", e);
        } finally {
            // 确保删除临时文件
            if (tempFile != null && tempFile.exists()) {
                try {
                    tempFile.delete();
                } catch (SecurityException e) {
                    System.err.println("无法删除临时文件: " + tempFile.getAbsolutePath());
                }
            }
        }
    }


}

服务层接口:

/**
 * @ClassName:IOpencvService
 * @Author: mjg
 * @Date: 2025/2/14 12:31
 * @Description: opencv服务层
 * @Version 1.0
 * @Since JDK 1.8
 */
public interface IOpencvService {

    /**
     * opencv 识别表格 根据边缘检测
     * @param path 路径
    **/
    public List<FormRecognitionVo> readTableByEdgeDetection(String path);
}

服务层实现(主要)

package com.ruoyi.ocr.opencv;

import com.ruoyi.ocr.entity.FormRecognitionVo;
import com.ruoyi.ocr.entity.TableVo;
import com.ruoyi.ocr.service.OcrService;
import com.spire.ocr.OcrException;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.concurrent.*;

/**
 * @ClassName:OpencvService
 * @Author: mjg
 * @Date: 2025/2/13 14:37
 * @Description: opencv服务层
 */
@Service
public class OpencvServiceImpl  implements IOpencvService{

    private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(OpencvServiceImpl.class);

    @Value("${opencv.model-path-linux}")
    private String modelPathLinux;

    @Value("${opencv.model-path-windows}")
    private String modelPathWindows;

    @Value("${opencv.widows-temp-upload}")
    private String windowsTempUpload;

    @Value("${opencv.linux-temp-upload}")
    private String linuxTempUpload;


    @Resource
    private ThreadPoolExecutor threadPoolExecutor;


    @Resource
    private OcrService ocrService;


    /*
     * 加载动态库
     *
     * 第一种方式 --------------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) ,方式比较灵活,可根据环境的系统,位数,决定加载内容
     * //加载动态链接库时,不使用System.loadLibrary(xxx);。 而是使用 绝对路径加载:System.load(xxx);
     */
    @PostConstruct
    public void loadLibraries() {
        try {
            String osName = System.getProperty("os.name");
            String opencvpath = System.getProperty("user.dir");

            //windows
            if (osName.startsWith("Windows")) {
                opencvpath = modelPathWindows;

            } else {
                opencvpath = modelPathLinux;
            }
            System.load(opencvpath);
        } catch (Exception e) {
            throw new RuntimeException("Failed to load opencv native library", e);
        }
        LOG.info("opencv视觉库加载完成");
    }

 

    @Override
    public List<FormRecognitionVo> readTableByEdgeDetection(String path) {
        List<FormRecognitionVo> list = new ArrayList<>();
        Mat source_image = Imgcodecs.imread(path);
        //灰度处理
        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,
                3,
                2);


        Mat edges = new Mat();

        Imgproc.Canny(thresh_image, edges, 50, 150);

        //边缘检测
        LinkedList<TableVo> tables = extractCells(edges, source_image);

        Map<Integer,Integer> rowMap = new HashMap<>();
        Map<Integer,Integer> colMap = new HashMap<>();

        int rowBegin = 0;
        int colBegin = 0;

        CountDownLatch latch = new CountDownLatch(tables.size());

        for (int i = 0; i < tables.size(); i++) {
            TableVo tableVo = tables.get(i);


            if(!rowMap.containsKey(tableVo.getY())){
                rowMap.put(tableVo.getY(), ++rowBegin);
            }
            if(!colMap.containsKey(tableVo.getX())){
                colMap.put(tableVo.getX(), ++colBegin);
            }

            int row = rowMap.get(tableVo.getY());

            int col = colMap.get(tableVo.getX());

            Mat blankImage = new Mat(tableVo.getMat().size(), tableVo.getMat().type(), Scalar.all(255)); // 创建一张空白图像
            Mat diffImage = new Mat();
            Core.absdiff(tableVo.getMat(), blankImage, diffImage);
            Scalar meanDiff = Core.mean(diffImage);
            if (meanDiff.val[0] >= 1) { // 阈值可以根据实际情况调整
                //有文字再去识别
                tableVo.setOcr(true);
            }

            threadPoolExecutor.execute(() -> {
                //生成临时文件
                String tempImageName = UUID.randomUUID() + ".png";

                saveImage(tempImageName, tableVo.getMat());
                String s = "";
                if (tableVo.isOcr()) {
                    s = ocrService.ocrWordBySpireString(getUploadPath() + File.separator + tempImageName);
                }
                FormRecognitionVo recognitionVo = new FormRecognitionVo(s, row, col);
                list.add(recognitionVo);
                threadPoolExecutor.execute(() -> this.removeImage(tempImageName));
                latch.countDown();
            });

        }
        try {
            latch.await(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Collections.sort(list);
        return list;
    }


    /**
     * 表格边缘检测
     * @param edges 处理后的图片检查轮廓
     * @param source 原始图片
    **/
    public static LinkedList<TableVo> extractCells(Mat edges,Mat source) {
        // 寻找轮廓
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();
        Imgproc.findContours(edges, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
        LinkedList<TableVo> tables = new LinkedList<TableVo>();
        for (MatOfPoint contour : contours) {
            // 限制单元格的最小和最大尺寸
            if (Imgproc.contourArea(contour) > 1000 && Imgproc.contourArea(contour)<10000) {
                // 获取边界矩形,提取单元格
                Rect rect = Imgproc.boundingRect(contour);
                Mat cell = new Mat(source, rect);
                int x = rect.x;
                int y = rect.y;
                tables.addFirst(new TableVo(cell,x,y));
            }
        }
        return tables;
    }


    public String getUploadPath() {
        if (System.getProperty("os.name").toLowerCase().contains("windows")) {
            return windowsTempUpload;
        } else {
            return linuxTempUpload;
        }
    }

    private void saveImage(String path, Mat image) {

        String outPath = getUploadPath() + File.separator + path;

        File file = new File(outPath);
        //目录是否存在
        this.dirIsExist(file.getParent());

        Imgcodecs.imwrite(outPath, image);

    }


    private void removeImage(String path) {

        String outPath = getUploadPath() + File.separator + path;

        File file = new File(outPath);
        if (file.exists()) {
            file.delete();
        }
    }


    private void dirIsExist(String dirPath) {
        File dir = new File(dirPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }


}

最后封装的实体类:

package com.ruoyi.ocr.entity;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

import java.io.Serializable;

/**
 * @ClassName:FormRecognitionVo
 * @Author: mjg
 * @Date: 2025/2/13 14:51
 * @Description: 表格识别VO
 */
public class FormRecognitionVo implements Serializable, Comparable<FormRecognitionVo> {

    private static final long serialVersionUID = 1L;

    /**
     * 识别文字
     **/
    private String word;

    /**
     * 识别行
     **/
    private Integer row;

    /**
     * 识别列
     **/
    private Integer cal;


    public FormRecognitionVo(String word, Integer row, Integer cal) {
        this.word = word;
        this.row = row;
        this.cal = cal;
    }

    public String getWord() {
        return word;
    }

    public void setWord(String word) {
        this.word = word;
    }

    public Integer getRow() {
        return row;
    }

    public void setRow(Integer row) {
        this.row = row;
    }

    public Integer getCal() {
        return cal;
    }

    public void setCal(Integer cal) {
        this.cal = cal;
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this, SerializerFeature.WriteMapNullValue);
    }


    @Override
    public int compareTo(FormRecognitionVo o) {
        // 首先比较 row
        int rowCompare = this.row.compareTo(o.row);
        if (rowCompare != 0) {
            return rowCompare; // 如果 row 不相等,直接返回比较
        }
        // 如果 row 相等,再比较 col
        return this.cal.compareTo(o.cal);
    }
}

TableVO

package com.ruoyi.ocr.entity;

import org.opencv.core.Mat;

/**
 * @ClassName:TableVo
 * @Author: mjg
 * @Date: 2025/2/13 11:39
 * @Description: 必须描述类做什么事情, 实现什么功能
 */
public class TableVo {

    private Mat mat;

    private int x;

    private int y;


    private boolean isOcr = false;


    public TableVo(Mat mat, int x, int y) {
        this.mat = mat;
        this.x = x;
        this.y = y;
    }



    public boolean isOcr() {
        return isOcr;
    }

    public void setOcr(boolean ocr) {
        isOcr = ocr;
    }

    public Mat getMat() {
        return mat;
    }

    public void setMat(Mat mat) {
        this.mat = mat;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "TableVo{" +
                "mat=" + mat +
                ", x=" + x +
                ", y=" + y +
                '}';
    }
}

ok,试下效果:
上传图片:
在这里插入图片描述

实际执行3秒多,因为ocr识别文字是非常耗时的,代码内部已经开了多个线程去跑,这个时间也可以接受,换张格子多数据少的图片再试下:
在这里插入图片描述

在这里插入图片描述
0.4S出结果,至此完成了 java调用opencv+ocr来识别表格图片。原创不易,如果对您有帮助请点个赞吧!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值