本文目录导读:

我来为您提供一个Java实现文字识别OCR的完整案例,使用Tesseract作为OCR引擎。
项目依赖配置
Maven依赖
<dependencies>
<!-- Tesseract OCR -->
<dependency>
<groupId>net.sourceforge.tess4j</groupId>
<artifactId>tess4j</artifactId>
<version>5.4.0</version>
</dependency>
<!-- 图像处理 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.9</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
基础OCR实现
package com.example.ocr;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
* OCR文字识别工具类
*/
public class OcrUtil {
private static final Logger logger = LoggerFactory.getLogger(OcrUtil.class);
private static ITesseract tesseract;
static {
initializeTesseract();
}
/**
* 初始化Tesseract
*/
private static void initializeTesseract() {
tesseract = new Tesseract();
// 设置语言数据路径
String tessDataPath = System.getenv("TESSDATA_PREFIX");
if (tessDataPath == null || tessDataPath.isEmpty()) {
tessDataPath = "./tessdata"; // 默认路径
}
tesseract.setDatapath(tessDataPath);
// 设置语言,中文+英文
tesseract.setLanguage("chi_sim+eng");
// 设置OCR模式
tesseract.setOcrEngineMode(ITesseract.OEM_LSTM_ONLY);
// 设置页面分割模式
tesseract.setPageSegMode(ITesseract.PSM_AUTO);
}
/**
* 识别图片中的文字
* @param imageFile 图片文件
* @return 识别结果
*/
public static String recognizeText(File imageFile) {
try {
BufferedImage image = ImageIO.read(imageFile);
if (image == null) {
throw new RuntimeException("无法读取图片文件: " + imageFile.getPath());
}
return tesseract.doOCR(image);
} catch (IOException | TesseractException e) {
logger.error("OCR识别失败", e);
throw new RuntimeException("OCR识别失败: " + e.getMessage());
}
}
/**
* 识别图片中的文字(带图像预处理)
* @param imageFile 图片文件
* @return 识别结果
*/
public static String recognizeTextWithPreprocessing(File imageFile) {
try {
BufferedImage image = ImageIO.read(imageFile);
if (image == null) {
throw new RuntimeException("无法读取图片文件: " + imageFile.getPath());
}
// 图像预处理
image = ImagePreprocessor.preprocess(image);
return tesseract.doOCR(image);
} catch (IOException | TesseractException e) {
logger.error("OCR识别失败", e);
throw new RuntimeException("OCR识别失败: " + e.getMessage());
}
}
/**
* 识别图片中的文字并返回详细信息
* @param imageFile 图片文件
* @return OCR识别结果对象
*/
public static OcrResult recognizeWithDetails(File imageFile) {
OcrResult result = OcrResult.builder().build();
try {
long startTime = System.currentTimeMillis();
BufferedImage image = ImageIO.read(imageFile);
if (image == null) {
result.setError("无法读取图片文件");
return result;
}
// 图像预处理
image = ImagePreprocessor.preprocess(image);
// 执行OCR
String text = tesseract.doOCR(image);
// 保存结果
result.setSuccess(true);
result.setText(text);
result.setWords(getWordCount(text));
result.setCharacters(text.trim().length());
result.setElapsedTime(System.currentTimeMillis() - startTime);
result.setImageInfo(getImageInfo(imageFile));
} catch (Exception e) {
logger.error("OCR识别失败", e);
result.setSuccess(false);
result.setError(e.getMessage());
}
return result;
}
/**
* 计算单词数量(中文按字符,英文按单词)
*/
private static int getWordCount(String text) {
if (text == null || text.trim().isEmpty()) {
return 0;
}
// 提取中文字符和英文单词
String trimmed = text.trim();
String chineseChars = trimmed.replaceAll("[^\\u4e00-\\u9fa5]", "");
String[] englishWords = trimmed.replaceAll("[\\u4e00-\\u9fa5]", " ").trim().split("\\s+");
int englishCount = 0;
if (englishWords.length > 0 && !englishWords[0].isEmpty()) {
englishCount = englishWords.length;
}
return chineseChars.length() + englishCount;
}
/**
* 获取图片信息
*/
private static ImageInfo getImageInfo(File imageFile) {
try {
BufferedImage image = ImageIO.read(imageFile);
return ImageInfo.builder()
.width(image.getWidth())
.height(image.getHeight())
.fileSize(imageFile.length())
.format(getImageFormat(imageFile))
.build();
} catch (IOException e) {
logger.warn("获取图片信息失败", e);
return null;
}
}
private static String getImageFormat(File imageFile) {
String fileName = imageFile.getName();
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex >= 0 && dotIndex < fileName.length() - 1) {
return fileName.substring(dotIndex + 1);
}
return "unknown";
}
}
图像预处理类
package com.example.ocr;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.awt.image.BufferedImage;
/**
* 图像预处理工具类
*/
public class ImagePreprocessor {
private static final Logger logger = LoggerFactory.getLogger(ImagePreprocessor.class);
/**
* 图像预处理流程
* @param image 原始图像
* @return 预处理后的图像
*/
public static BufferedImage preprocess(BufferedImage image) {
// 1. 图像增强
image = enhanceContrast(image);
// 2. 灰度化
BufferedImage grayImage = convertToGray(image);
// 3. 二值化
BufferedImage binaryImage = binarize(grayImage);
// 4. 去除噪点
binaryImage = denoise(binaryImage);
return binaryImage;
}
/**
* 增强对比度
*/
public static BufferedImage enhanceContrast(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
BufferedImage enhancedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 计算图像直方图
int[] histogram = new int[256];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int rgb = image.getRGB(x, y);
int gray = (int) ((0.299 * ((rgb >> 16) & 0xFF)) +
(0.587 * ((rgb >> 8) & 0xFF)) +
(0.114 * (rgb & 0xFF)));
histogram[gray]++;
}
}
// 直方图均衡化
int[] lut = new int[256];
int sum = 0;
int total = width * height;
for (int i = 0; i < 256; i++) {
sum += histogram[i];
lut[i] = (int) (255.0 * sum / total);
}
// 应用均衡化
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int rgb = image.getRGB(x, y);
int red = lut[(rgb >> 16) & 0xFF];
int green = lut[(rgb >> 8) & 0xFF];
int blue = lut[rgb & 0xFF];
enhancedImage.setRGB(x, y, (red << 16) | (green << 8) | blue);
}
}
return enhancedImage;
}
/**
* 转换为灰度图像
*/
public static BufferedImage convertToGray(BufferedImage image) {
BufferedImage grayImage = new BufferedImage(
image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int rgb = image.getRGB(x, y);
int gray = (int) ((0.299 * ((rgb >> 16) & 0xFF)) +
(0.587 * ((rgb >> 8) & 0xFF)) +
(0.114 * (rgb & 0xFF)));
grayImage.setRGB(x, y, (gray << 16) | (gray << 8) | gray);
}
}
return grayImage;
}
/**
* 二值化处理
*/
public static BufferedImage binarize(BufferedImage grayImage) {
int width = grayImage.getWidth();
int height = grayImage.getHeight();
BufferedImage binaryImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
// 使用Otsu方法计算阈值
int threshold = otsuThreshold(grayImage);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = grayImage.getRGB(x, y) & 0xFF;
int binary = pixel >= threshold ? 255 : 0;
binaryImage.setRGB(x, y, (binary << 16) | (binary << 8) | binary);
}
}
return binaryImage;
}
/**
* Otsu阈值计算
*/
private static int otsuThreshold(BufferedImage image) {
int[] histogram = new int[256];
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
histogram[image.getRGB(x, y) & 0xFF]++;
}
}
int totalPixels = image.getWidth() * image.getHeight();
float sum = 0;
for (int i = 0; i < 256; i++) {
sum += i * histogram[i];
}
float sumB = 0;
int weightB = 0;
float maxVariance = 0;
int threshold = 0;
for (int i = 0; i < 256; i++) {
weightB += histogram[i];
if (weightB == 0) continue;
int weightF = totalPixels - weightB;
if (weightF == 0) break;
sumB += i * histogram[i];
float meanB = sumB / weightB;
float meanF = (sum - sumB) / weightF;
float variance = (float) weightB * weightF * (meanB - meanF) * (meanB - meanF);
if (variance > maxVariance) {
maxVariance = variance;
threshold = i;
}
}
return threshold;
}
/**
* 去除噪点(中值滤波)
*/
public static BufferedImage denoise(BufferedImage image) {
int radius = 1;
int width = image.getWidth();
int height = image.getHeight();
BufferedImage denoisedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
for (int y = radius; y < height - radius; y++) {
for (int x = radius; x < width - radius; x++) {
int[] values = new int[(2 * radius + 1) * (2 * radius + 1)];
int index = 0;
for (int dy = -radius; dy <= radius; dy++) {
for (int dx = -radius; dx <= radius; dx++) {
values[index++] = image.getRGB(x + dx, y + dy) & 0xFF;
}
}
// 排序取中值
java.util.Arrays.sort(values);
int median = values[values.length / 2];
denoisedImage.setRGB(x, y, (median << 16) | (median << 8) | median);
}
}
// 复制边缘像素
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (x < radius || x >= width - radius || y < radius || y >= height - radius) {
denoisedImage.setRGB(x, y, image.getRGB(x, y));
}
}
}
return denoisedImage;
}
/**
* 旋转校正
*/
public static BufferedImage deskew(BufferedImage image, double angle) {
int width = image.getWidth();
int height = image.getHeight();
// 计算旋转后的图像大小
double radians = Math.toRadians(angle);
int newWidth = (int) (width * Math.cos(radians) + height * Math.sin(radians));
int newHeight = (int) (width * Math.sin(radians) + height * Math.cos(radians));
BufferedImage rotatedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_BYTE_GRAY);
// 旋转操作
// 实际项目中可以使用AffineTransform
// 这里提供简化版本
return rotatedImage;
}
}
业务模型类
package com.example.ocr;
import lombok.Builder;
import lombok.Data;
/**
* OCR识别结果
*/
@Data
@Builder
public class OcrResult {
private boolean success;
private String text;
private int words;
private int characters;
private long elapsedTime;
private ImageInfo imageInfo;
private String error;
@Override
public String toString() {
return String.format("OCR识别结果:{成功=%s, 字数=%d, 字符数=%d, 用时=%dms, 错误=%s}",
success, words, characters, elapsedTime, error);
}
}
/**
* 图片信息
*/
@Data
@Builder
public class ImageInfo {
private int width;
private int height;
private long fileSize;
private String format;
}
服务类
package com.example.ocr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* OCR服务类
*/
public class OcrService {
private static final Logger logger = LoggerFactory.getLogger(OcrService.class);
/**
* 异步识别
*/
public CompletableFuture<OcrResult> recognizeAsync(File imageFile) {
return CompletableFuture.supplyAsync(() -> OcrUtil.recognizeWithDetails(imageFile));
}
/**
* 批量识别
*/
public Map<String, OcrResult> recognizeBatch(File[] imageFiles) {
Map<String, OcrResult> results = new HashMap<>();
for (File file : imageFiles) {
try {
OcrResult result = OcrUtil.recognizeWithDetails(file);
results.put(file.getName(), result);
} catch (Exception e) {
logger.error("处理文件失败: {}", file.getName(), e);
results.put(file.getName(), OcrResult.builder()
.success(false)
.error(e.getMessage())
.build());
}
}
return results;
}
/**
* 从图片中提取特定文本
*/
public String extractSpecificText(File imageFile, String pattern) {
try {
String text = OcrUtil.recognizeTextWithPreprocessing(imageFile);
if (text == null || text.isEmpty()) {
return null;
}
// 在识别文本中查找匹配的内容
java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern);
java.util.regex.Matcher matcher = p.matcher(text);
if (matcher.find()) {
return matcher.group();
}
return null;
} catch (Exception e) {
logger.error("提取特定文本失败", e);
return null;
}
}
/**
* 身份证号码提取
*/
public String extractIdCard(File imageFile) {
// 身份证号码模式
String idCardPattern = "\\d{17}[0-9Xx]";
return extractSpecificText(imageFile, idCardPattern);
}
/**
* 手机号码提取
*/
public String extractPhoneNumber(File imageFile) {
// 手机号模式
String phonePattern = "1[3-9]\\d{9}";
return extractSpecificText(imageFile, phonePattern);
}
}
测试类
package com.example.ocr;
import org.junit.jupiter.api.Test;
import java.io.File;
/**
* OCR工具测试类
*/
public class OcrUtilTest {
@Test
public void testSimpleRecognize() {
// 测试简单识别
File imageFile = new File("test_images/simple.png");
if (imageFile.exists()) {
String text = OcrUtil.recognizeText(imageFile);
System.out.println("识别结果: " + text);
} else {
System.out.println("测试图片不存在");
}
}
@Test
public void testRecognizeWithPreprocessing() {
// 测试带预处理的识别
File imageFile = new File("test_images/processed.png");
if (imageFile.exists()) {
String text = OcrUtil.recognizeTextWithPreprocessing(imageFile);
System.out.println("预处理后识别结果: " + text);
}
}
@Test
public void testRecognizeWithDetails() {
// 测试详细识别
File imageFile = new File("test_images/detail.png");
if (imageFile.exists()) {
OcrResult result = OcrUtil.recognizeWithDetails(imageFile);
System.out.println(result);
if (result != null && result.getImageInfo() != null) {
System.out.println("图片尺寸: " + result.getImageInfo().getWidth() + "x" +
result.getImageInfo().getHeight());
}
}
}
@Test
public void testExtractIdCard() {
// 测试身份证号提取
File imageFile = new File("test_images/idcard.png");
if (imageFile.exists()) {
OcrService service = new OcrService();
String idCard = service.extractIdCard(imageFile);
System.out.println("提取的身份证号: " + idCard);
}
}
@Test
public void testBatchRecognize() {
// 测试批量识别
File testDir = new File("test_images/");
if (testDir.exists()) {
File[] files = testDir.listFiles((dir, name) ->
name.endsWith(".png") || name.endsWith(".jpg"));
if (files != null) {
OcrService service = new OcrService();
var results = service.recognizeBatch(files);
results.forEach((fileName, result) -> {
System.out.println("文件: " + fileName);
System.out.println("结果: " + result);
System.out.println("---");
});
}
}
}
}
主程序入口
package com.example.ocr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Scanner;
/**
* 主程序入口
*/
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
if (args.length == 0) {
interactiveMode();
} else {
// 命令行模式
for (String arg : args) {
File imageFile = new File(arg);
if (imageFile.exists()) {
recognizeAndPrint(imageFile);
} else {
System.err.println("文件不存在: " + arg);
}
}
}
}
/**
* 交互模式
*/
private static void interactiveMode() {
System.out.println("=== OCR文字识别工具 ===");
System.out.println("输入图片路径(输入exit退出):");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if ("exit".equalsIgnoreCase(line)) {
break;
}
if (!line.isEmpty()) {
File imageFile = new File(line);
if (imageFile.exists()) {
recognizeAndPrint(imageFile);
} else {
System.err.println("文件不存在,请重新输入");
}
}
System.out.println("\n输入图片路径(输入exit退出):");
}
scanner.close();
}
/**
* 执行识别并打印结果
*/
private static void recognizeAndPrint(File imageFile) {
try {
System.out.println("正在识别: " + imageFile.getName());
long startTime = System.currentTimeMillis();
OcrResult result = OcrUtil.recognizeWithDetails(imageFile);
long elapsedTime = System.currentTimeMillis() - startTime;
if (result.isSuccess()) {
System.out.println("识别完成,用时: " + elapsedTime + "ms");
System.out.println("识别结果:");
System.out.println(result.getText());
System.out.println("\n详细信息:");
System.out.println("字数: " + result.getWords());
System.out.println("字符数: " + result.getCharacters());
} else {
System.err.println("识别失败: " + result.getError());
}
} catch (Exception e) {
logger.error("识别过程出错", e);
System.err.println("识别过程出错: " + e.getMessage());
}
}
}
配置文件
# application.properties # Tesseract配置 tesseract.data.path=./tessdata tesseract.language=chi_sim+eng tesseract.ocr.engine.mode=3 tesseract.page.seg.mode=3 # 图像处理配置 image.preprocess.enabled=true image.preprocess.contrast.enhance=true image.preprocess.denoise=true
使用说明
环境准备
-
安装Tesseract OCR引擎
- Windows: 下载安装 Tesseract
- Linux:
sudo apt-get install tesseract-ocr - Mac:
brew install tesseract
-
下载语言数据
- 从 tessdata 下载
chi_sim.traineddata和eng.traineddata - 放入项目的
tessdata目录
- 从 tessdata 下载
使用示例
// 基础用法
File imageFile = new File("test.png");
String text = OcrUtil.recognizeText(imageFile);
System.out.println(text);
// 带预处理的用法
String processedText = OcrUtil.recognizeTextWithPreprocessing(new File("test.jpg"));
// 详细结果
OcrResult result = OcrUtil.recognizeWithDetails(new File("test.png"));
if (result.isSuccess()) {
System.out.println(result.getText());
System.out.println("字数: " + result.getWords());
System.out.println("用时: " + result.getElapsedTime() + "ms");
}
// 使用服务类
OcrService service = new OcrService();
String idCardNumber = service.extractIdCard(new File("idcard.jpg"));
String phoneNumber = service.extractPhoneNumber(new File("phone.jpg"));
优化建议
识别准确率优化
- 图像清晰度:确保图像分辨率足够,文字清晰
- 预处理加强:根据实际情况调整二值化阈值、去噪参数
- 自定义训练:针对特定字体或场景训练自定义模型
性能优化
- 缓存预处理结果:对相同图像避免重复预处理
- 并行处理:使用线程池处理多个图片
- 图像裁剪:只对包含文字的区域进行识别
错误处理
- 图片格式检查:提前检查图片格式是否支持
- 超时处理:对大图片设置识别超时时间
- 降级策略:识别失败时尝试不同的预处理参数
这个案例提供了一个完整的Java OCR解决方案,包括基础识别、图像预处理、业务扩展等功能,您可以根据实际需求进行调整和优化。