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

我们将使用 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
这个案例提供了完整的票据识别解决方案,你可以根据实际业务需求进行调整和扩展。