Java票据识别案例怎么实操

wen java案例 34

Java票据识别实操案例

下面我将通过一个完整的实操案例,展示如何使用Java实现票据识别(OCR)功能。

Java票据识别案例怎么实操

我们将使用 Tesseract OCR 引擎实现票据文字识别,包括:

  • 图片预处理(提高识别率)
  • 文字提取
  • 结构化数据解析

环境准备

安装Tesseract OCR引擎

Windows:

# 下载安装包
https://github.com/UB-Mannheim/tesseract/wiki
# 安装后配置环境变量
# 添加 TESSDATA_PREFIX 环境变量
# 值: C:\Program Files\Tesseract-OCR\tessdata

Mac:

brew install tesseract
brew install tesseract-lang  # 中文语言包

Linux:

sudo apt-get install tesseract-ocr
sudo apt-get install tesseract-ocr-chi-sim  # 中文简体

创建Maven项目

pom.xml依赖配置:

<dependencies>
    <!-- Tesseract OCR -->
    <dependency>
        <groupId>net.sourceforge.tess4j</groupId>
        <artifactId>tess4j</artifactId>
        <version>5.4.0</version>
    </dependency>
    <!-- 图片处理 -->
    <dependency>
        <groupId>net.coobird</groupId>
        <artifactId>thumbnailator</artifactId>
        <version>0.4.20</version>
    </dependency>
    <!-- 测试 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.0</version>
        <scope>test</scope>
    </dependency>
    <!-- JSON处理 -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>
</dependencies>

核心代码实现

票据识别服务类

package com.example.invoice;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class InvoiceOCRService {
    private final ITesseract tesseract;
    public InvoiceOCRService() {
        this.tesseract = new Tesseract();
        // 设置语言包目录(根据实际安装路径修改)
        this.tesseract.setDatapath("D:/Program Files/Tesseract-OCR/tessdata");
        // 设置识别语言:中文+英文
        this.tesseract.setLanguage("chi_sim+eng");
        // 设置OCR引擎模式
        this.tesseract.setOcrEngineMode(1);  // LSTM引擎
        // 设置页面分割模式
        this.tesseract.setPageSegMode(6);    // 假定为统一的文本块
    }
    /**
     * 识别票据图片
     * @param imagePath 图片路径
     * @return 识别出的原始文本
     */
    public String recognizeInvoice(String imagePath) {
        try {
            File imageFile = new File(imagePath);
            // 预处理图片
            BufferedImage processedImage = preprocessImage(imageFile);
            // 执行OCR识别
            String result = tesseract.doOCR(processedImage);
            return result;
        } catch (TesseractException | IOException e) {
            e.printStackTrace();
            return "识别失败: " + e.getMessage();
        }
    }
    /**
     * 图片预处理(提高识别率)
     */
    private BufferedImage preprocessImage(File imageFile) throws IOException {
        BufferedImage originalImage = ImageIO.read(imageFile);
        BufferedImage processedImage = new BufferedImage(
            originalImage.getWidth(), 
            originalImage.getHeight(), 
            BufferedImage.TYPE_BYTE_GRAY  // 转为灰度图
        );
        // 灰度化
        Graphics2D graphics = processedImage.createGraphics();
        graphics.drawImage(originalImage, 0, 0, null);
        graphics.dispose();
        // 二值化处理(简单的阈值方法)
        for (int y = 0; y < processedImage.getHeight(); y++) {
            for (int x = 0; x < processedImage.getWidth(); x++) {
                int rgb = processedImage.getRGB(x, y);
                int gray = (rgb >> 16) & 0xFF;  // 获取灰度值
                // 二值化阈值(可根据实际情况调整)
                if (gray < 128) {
                    processedImage.setRGB(x, y, Color.BLACK.getRGB());
                } else {
                    processedImage.setRGB(x, y, Color.WHITE.getRGB());
                }
            }
        }
        return processedImage;
    }
}

票据数据解析类

package com.example.invoice;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class InvoiceDataParser {
    /**
     * 解析发票信息
     * @param rawText OCR识别的原始文本
     * @return 解析后的发票信息
     */
    public InvoiceInfo parseInvoiceInfo(String rawText) {
        InvoiceInfo info = new InvoiceInfo();
        // 提取发票号码
        info.setInvoiceNo(extractByPattern(rawText, "发票号码[:: ]*(\\w+)"));
        info.setInvoiceNo(extractByPattern(rawText, "发票代码[:: ]*(\\d+)"));
        // 提取日期
        info.setDate(extractByPattern(rawText, "开票日期[:: ]*(\\d{4}[-年]\\d{1,2}[-月]\\d{1,2})"));
        // 提取金额
        String amountStr = extractByPattern(rawText, "价税合计[:: ]*[¥¥]?(\\d+\\.?\\d*)");
        if (amountStr.isEmpty()) {
            amountStr = extractByPattern(rawText, "合计[:: ]*[¥¥]?(\\d+\\.?\\d*)");
        }
        if (!amountStr.isEmpty()) {
            info.setTotalAmount(Double.parseDouble(amountStr));
        }
        // 提取购买方信息
        info.setBuyerName(extractByPattern(rawText, "购买方[名称:: ]*([\\u4e00-\\u9fa5]+)"));
        info.setBuyerTaxId(extractByPattern(rawText, "纳税人识别号[:: ]*(\\d{15,20})"));
        // 提取商品/服务明细(简化版)
        info.setGoodsDescription(extractByPattern(rawText, "货物或应税劳务、服务名称[:: ]*([^\\n]+)"));
        return info;
    }
    /**
     * 使用正则表达式提取信息
     */
    private String extractByPattern(String text, String regex) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        if (matcher.find()) {
            return matcher.group(1).trim();
        }
        return "";
    }
}

发票信息实体类

package com.example.invoice;
public class InvoiceInfo {
    private String invoiceNo;          // 发票号码
    private String invoiceCode;        // 发票代码
    private String date;               // 开票日期
    private Double totalAmount;        // 价税合计
    private String buyerName;          // 购买方名称
    private String buyerTaxId;         // 购买方税号
    private String goodsDescription;   // 商品描述
    // Getters and Setters
    public String getInvoiceNo() { return invoiceNo; }
    public void setInvoiceNo(String invoiceNo) { this.invoiceNo = invoiceNo; }
    public String getInvoiceCode() { return invoiceCode; }
    public void setInvoiceCode(String invoiceCode) { this.invoiceCode = invoiceCode; }
    public String getDate() { return date; }
    public void setDate(String date) { this.date = date; }
    public Double getTotalAmount() { return totalAmount; }
    public void setTotalAmount(Double totalAmount) { this.totalAmount = totalAmount; }
    public String getBuyerName() { return buyerName; }
    public void setBuyerName(String buyerName) { this.buyerName = buyerName; }
    public String getBuyerTaxId() { return buyerTaxId; }
    public void setBuyerTaxId(String buyerTaxId) { this.buyerTaxId = buyerTaxId; }
    public String getGoodsDescription() { return goodsDescription; }
    public void setGoodsDescription(String goodsDescription) { this.goodsDescription = goodsDescription; }
    @Override
    public String toString() {
        return "InvoiceInfo{" +
                "invoiceNo='" + invoiceNo + '\'' +
                ", invoiceCode='" + invoiceCode + '\'' +
                ", date='" + date + '\'' +
                ", totalAmount=" + totalAmount +
                ", buyerName='" + buyerName + '\'' +
                ", buyerTaxId='" + buyerTaxId + '\'' +
                ", goodsDescription='" + goodsDescription + '\'' +
                '}';
    }
}

主程序入口

package com.example.invoice;
public class InvoiceRecognitionApp {
    public static void main(String[] args) {
        // 票据图片路径
        String imagePath = "D:/invoices/sample_invoice.jpg";
        // 创建OCR服务
        InvoiceOCRService ocrService = new InvoiceOCRService();
        // 执行识别
        System.out.println("开始识别票据...");
        String rawText = ocrService.recognizeInvoice(imagePath);
        System.out.println("原始识别结果:");
        System.out.println(rawText);
        System.out.println("==================================");
        // 解析结构化数据
        InvoiceDataParser parser = new InvoiceDataParser();
        InvoiceInfo invoiceInfo = parser.parseInvoiceInfo(rawText);
        // 输出结果
        System.out.println("解析后的发票信息:");
        System.out.println(invoiceInfo);
    }
}

批量处理示例

package com.example.invoice;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class BatchInvoiceProcessor {
    public static void main(String[] args) {
        String invoiceDir = "D:/invoices/";
        InvoiceOCRService ocrService = new InvoiceOCRService();
        InvoiceDataParser parser = new InvoiceDataParser();
        List<InvoiceInfo> results = new ArrayList<>();
        File dir = new File(invoiceDir);
        File[] files = dir.listFiles((d, name) -> 
            name.endsWith(".jpg") || name.endsWith(".png") || name.endsWith(".jpeg"));
        if (files != null) {
            for (File file : files) {
                System.out.println("处理文件: " + file.getName());
                String rawText = ocrService.recognizeInvoice(file.getAbsolutePath());
                InvoiceInfo info = parser.parseInvoiceInfo(rawText);
                results.add(info);
            }
        }
        // 输出汇总结果
        System.out.println("处理完成,共识别 " + results.size() + " 张票据");
        results.forEach(System.out::println);
    }
}

进阶优化建议

提高识别准确率的技巧

// 更精细的图片预处理
public BufferedImage advancedPreprocess(BufferedImage image) {
    // 1. 去噪
    BufferedImage denoised = applyMedianFilter(image);
    // 2. 对比度增强
    BufferedImage enhanced = enhanceContrast(denoised);
    // 3. 倾斜校正
    BufferedImage corrected = deskew(enhanced);
    // 4. 自适应二值化
    BufferedImage binary = adaptiveThreshold(corrected);
    return binary;
}

使用OpenCV进行高级处理

<!-- 添加OpenCV依赖 -->
<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5.9</version>
</dependency>

集成深度学习模型

对于更复杂的票据类型,建议使用专用的深度学习模型:

  • 百度飞桨PaddleOCR:有Java调用接口
  • 华为OCR:提供云服务API
  • 自训练模型:使用TensorFlow/PyTorch训练自定义模型

测试用例

package com.example.invoice;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class InvoiceOCRTest {
    @Test
    public void testInvoiceRecognition() {
        InvoiceOCRService ocrService = new InvoiceOCRService();
        String result = ocrService.recognizeInvoice("src/test/resources/test_invoice.jpg");
        assertNotNull(result);
        assertFalse(result.contains("识别失败"));
        System.out.println("识别结果:");
        System.out.println(result);
    }
    @Test
    public void testInvoiceDataParser() {
        String mockText = "发票号码:12345678\n开票日期:2024-01-15\n价税合计:¥1560.00";
        InvoiceDataParser parser = new InvoiceDataParser();
        InvoiceInfo info = parser.parseInvoiceInfo(mockText);
        assertEquals("12345678", info.getInvoiceNo());
        assertEquals("2024-01-15", info.getDate());
        assertEquals(1560.0, info.getTotalAmount(), 0.01);
    }
}

常见问题解决

识别中文失败

  • 确认已安装中文语言包:tesseract --list-langs
  • 检查语言设置:tesseract.setLanguage("chi_sim")
  • 图片文字方向:可以尝试旋转90度

识别率低

  • 确保图片清晰,分辨率不低于300dpi
  • 预处理时调整二值化阈值
  • 尝试不同的PageSegMode值(3-13)

性能优化

  • 使用多线程并行处理批量图片
  • 调整Tesseract线程数:tesseract.setThreadCount(4)
  • 对于清晰图片,可以跳过预处理步骤

项目结构建议

invoice-ocr/
├── src/
│   ├── main/java/com/example/invoice/
│   │   ├── InvoiceOCRService.java      # OCR服务
│   │   ├── InvoiceDataParser.java      # 数据解析
│   │   ├── InvoiceInfo.java            # 数据实体
│   │   └── InvoiceRecognitionApp.java  # 主程序
│   └── test/java/com/example/invoice/
│       └── InvoiceOCRTest.java         # 测试类
├── resources/
│   └── test_invoice.jpg                # 测试图片
├── tessdata/                           # Tesseract语言包
└── pom.xml

这个案例提供了完整的票据识别解决方案,你可以根据实际业务需求进行调整和扩展。

抱歉,评论功能暂时关闭!