Java实现文字识别OCR案例

wen java案例 2

本文目录导读:

Java实现文字识别OCR案例

  1. 项目依赖配置
  2. 基础OCR实现
  3. 图像预处理类
  4. 业务模型类
  5. 服务类
  6. 测试类
  7. 主程序入口
  8. 配置文件
  9. 使用说明
  10. 优化建议

我来为您提供一个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

使用说明

环境准备

  1. 安装Tesseract OCR引擎

    • Windows: 下载安装 Tesseract
    • Linux: sudo apt-get install tesseract-ocr
    • Mac: brew install tesseract
  2. 下载语言数据

    • tessdata 下载 chi_sim.traineddataeng.traineddata
    • 放入项目的 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"));

优化建议

识别准确率优化

  1. 图像清晰度:确保图像分辨率足够,文字清晰
  2. 预处理加强:根据实际情况调整二值化阈值、去噪参数
  3. 自定义训练:针对特定字体或场景训练自定义模型

性能优化

  1. 缓存预处理结果:对相同图像避免重复预处理
  2. 并行处理:使用线程池处理多个图片
  3. 图像裁剪:只对包含文字的区域进行识别

错误处理

  1. 图片格式检查:提前检查图片格式是否支持
  2. 超时处理:对大图片设置识别超时时间
  3. 降级策略:识别失败时尝试不同的预处理参数

这个案例提供了一个完整的Java OCR解决方案,包括基础识别、图像预处理、业务扩展等功能,您可以根据实际需求进行调整和优化。

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