⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Fight/SpringBootyijiantiqushenfenzhengyuyingyezhizhaoxinxi/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

反向定位:90% 项目其实不需要云 OCR

「身份证 / 营业执照 OCR」这件事,市面上的标准答案是调云 API——阿里云 / 百度智能云 / 腾讯云全都有「证件 OCR」服务,按调用次数收费。

但用云 API 有 3 个常被忽视的代价:

  • 数据出本地:身份证号 / 法人姓名 / 公司注册号——这些全是个人敏感信息。送到第三方云 API 就是一次数据出境,金融 / 政企 / 隐私敏感场景立刻不合规;
  • 网络依赖:内网部署项目对外调用 API 还要专门走代理 / 白名单;
  • 按量付费:单次几分钱看着不贵,但每天 1 万次就是几百块,年化几万。

90% 的项目真实需求其实只是:用户上传身份证 → 提取身份证号 → 自动填表。这种规模上完全可以本地搞定——精度差一点没关系,识别失败可以让用户重传。

这次推荐的是 Spring Boot + Tesseract + OpenCV 的本地 OCR 方案:

  • 完全开源免费:MIT / Apache 协议;
  • 数据不出本地:图片处理全在 JVM 进程里;
  • 预处理 + 识别一条龙:OpenCV 做图像增强、Tesseract 做文字识别、正则提取关键字段;
  • Spring Boot 集成:一个 Controller 加一个 Service,业务对接简单。

横向对比:4 个 OCR 方案怎么选

不是所有场景都该自托管。先看自己在哪个象限:

方案 精度 数据走向 单次成本 致命短板
阿里云 / 百度 / 腾讯云 OCR ⭐⭐⭐⭐⭐ 云端 ¥0.01-0.1/次 数据出境 + 流量 + 月成本
PaddleOCR(百度开源) ⭐⭐⭐⭐ 本地 0 需要 Python 环境 / 模型大
Tesseract + OpenCV(本文) ⭐⭐⭐ 本地 0 精度比云 OCR 略低
Apple Vision / Microsoft Read ⭐⭐⭐⭐ 本地 / 端 0 平台绑定

判断很简单:

  • 政企 / 金融 / 隐私敏感 + 中等精度可接受:Tesseract + OpenCV 是最稳的方案;
  • 高精度刚需 + 数据可出境:直接调云 API 别折腾;
  • 高精度 + 不能出境:PaddleOCR 是更强的选择(Python 服务 + Java 调 HTTP);
  • macOS / iOS 客户端:直接用系统 Vision API,零依赖。

3 个真实业务场景

场景 1:商家入驻——营业执照 OCR 自动填表

商家上传营业执照图片 → 系统自动识别统一社会信用代码、公司名称、法人、注册地址 → 表单自动填好。

痛点:手工填错率高(统一信用代码 18 位),用户放弃率上升。
收益:表单填写时间从 3 分钟压到 30 秒。

场景 2:用户实名——身份证号自动提取

用户上传身份证正面 → 提取身份证号 + 姓名 → 与公安二要素核验接口对接。

痛点:手工填身份证号容易输错,造成核验失败循环。
收益:核验通过率显著提升。

场景 3:财务报销——发票编号 / 金额提取

发票图片上传 → 提取发票号 / 金额 / 日期 → 自动入财务系统。

痛点:财务人员每张手工录入,月底集中加班。
收益:自动入账,财务人效大幅提升。

接入 4 步:依赖 + 预处理 + 识别 + 提取

第 1 步:Maven 依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>1.5.6</version>
</dependency>

第 2 步:OpenCV 图像预处理

OCR 精度的 80% 来自预处理——直接把原图丢给 Tesseract 精度很差。预处理三件事:灰度化、降噪、边缘增强。

import org.bytedeco.opencv.opencv_core.Mat;
import static org.bytedeco.opencv.global.opencv_imgcodecs.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;

@Service
public class ImagePreprocessor {

public Mat preprocess(String imagePath) {
Mat image = imread(imagePath);

Mat gray = new Mat();
cvtColor(image, gray, COLOR_BGR2GRAY); // ① 灰度化

Mat blurred = new Mat();
GaussianBlur(gray, blurred, new Size(3, 3), 0); // ② 高斯模糊降噪

Mat edged = new Mat();
Canny(blurred, edged, 75, 200); // ③ Canny 边缘检测

return edged;
}
}

第 3 步:Tesseract OCR 识别

import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.tesseract.TessBaseAPI;

@Service
public class OcrService {

private final ImagePreprocessor preprocessor;

public OcrService(ImagePreprocessor preprocessor) {
this.preprocessor = preprocessor;
}

public String doOcr(String imagePath) {
TessBaseAPI tess = new TessBaseAPI();
try {
// 注意:第二个参数是语言包,识别中文要用 "chi_sim"
if (tess.Init(".", "chi_sim+eng") != 0) {
throw new IllegalStateException("Tesseract 初始化失败");
}

Mat image = preprocessor.preprocess(imagePath);
tess.SetImage(image.data(), image.cols(), image.rows(), 1, image.step());

BytePointer outText = tess.GetUTF8Text();
try {
return outText.getString();
} finally {
outText.deallocate();
}
} finally {
tess.End();
}
}
}

关键点:tess.End() 必须放 finally——Tesseract 是 native 资源,泄漏一次内存就涨一次。

第 4 步:正则提取关键字段

@Service
public class InfoExtractor {

private static final Pattern ID_CARD = Pattern.compile("[1-9]\\d{16}[\\dXx]");
private static final Pattern BIZ_LICENSE = Pattern.compile("[0-9A-HJ-NPQRTUWXY]{18}");

public String extractIdCard(String text) {
Matcher m = ID_CARD.matcher(text);
return m.find() ? m.group() : null;
}

public String extractBizLicense(String text) {
Matcher m = BIZ_LICENSE.matcher(text);
return m.find() ? m.group() : null;
}
}

注意:身份证号正则不要用 [0-9]{18}|[0-9]{15}——18 位身份证最后一位可能是 X / x,15 位是老身份证早就停发了,不要再支持。

Controller 层串起来:

@RestController
@RequestMapping("/ocr")
public class OcrController {

private final OcrService ocrService;
private final InfoExtractor extractor;

@PostMapping("/idcard")
public Map<String, String> idCard(@RequestParam MultipartFile file) throws IOException {
Path tmp = Files.createTempFile("idcard-", ".jpg");
file.transferTo(tmp);
try {
String text = ocrService.doOcr(tmp.toString());
return Map.of("idNumber", extractor.extractIdCard(text));
} finally {
Files.deleteIfExists(tmp);
}
}
}

临时文件务必 delete——OCR 处理后的图片是敏感数据,留在磁盘上是合规隐患。

4 个真实使用边界

边界 1:中文识别精度依赖语言包(最常见)

tess.Init(".", "ENG") 只能识别英文——中文身份证识别必须装 chi_sim 语言包。下载地址:github.com/tesseract-ocr/tessdata,放到 tessdata 目录下,初始化时用 "chi_sim+eng" 中英混合识别。

边界 2:旋转 / 倾斜图片识别率暴跌(常见)

用户上传的照片经常横拍 / 倾斜,Tesseract 精度直接断崖。修法:预处理加自动旋转矫正(minAreaRect + warpAffine),或前端强制竖屏 + 提示「请把证件水平放置」。

边界 3:Native 资源不释放会泄漏(少见但破坏力大)

TessBaseAPI / Mat / BytePointer 都是 JNI 对象——没主动 close 就是 native heap 泄漏。生产环境用 try-with-resources / finally 严格管理生命周期。压测时 top 看 RSS 内存涨得快就是漏了。

边界 4:精度永远不如云 OCR(高级场景)

Tesseract 在理想条件下(高清 / 摆正 / 光照均匀)精度能到 95%+,但真实用户上传的照片精度通常在 80-90%。如果业务对识别精度要求极高(如银行 KYC),建议:① 本地预筛 → 失败回退云 OCR;② 本地识别 + 用户二次确认。

说到底

OCR 方案的选择不是「哪个精度最高」——是「精度、合规、成本」三者怎么平衡。云 API 牺牲合规和成本换精度;本地方案牺牲一点精度换合规和成本。好的技术选型不是默认上最强方案,是先问「我真的需要那么强吗」。

仓库参考:Tesseract OCR | JavaCV

文章目录
  1. 1. 反向定位:90% 项目其实不需要云 OCR
  2. 2. 横向对比:4 个 OCR 方案怎么选
  3. 3. 3 个真实业务场景
  4. 4. 接入 4 步:依赖 + 预处理 + 识别 + 提取
  5. 5. 4 个真实使用边界
  6. 6. 说到底