使用 C++/OpenCV 构建中文 OCR 系统:实现账单、发票及 PDF 读取
在当今数字化浪潮中,自动从文档中提取信息至关重要,尤其是在处理大量账单、发票和 PDF 文件时。光学字符识别(OCR)技术是实现这一目标的核心。本文将详细介绍如何利用 C++ 和强大的计算机视觉库 OpenCV,构建一个专门用于读取中文账单、发票和 PDF 的 OCR 系统。
核心技术栈
我们的系统将主要围绕以下核心技术构建:
- C++: 作为主要的编程语言,C++ 以其高性能和对系统底层资源的强大控制力而著称,非常适合构建计算密集型的计算机视觉应用。
- OpenCV: 一个开源的计算机视觉和机器学习软件库。它提供了丰富的图像处理和计算机视觉算法,是本项目的基石。
- OCR 引擎: 我们将重点探讨两种主流的、支持中文识别的 OCR 引擎:Tesseract 和 PaddleOCR。它们都可以与 C++ 和 OpenCV 良好集成。
- PDF 处理库 (Poppler): 由于 OCR 引擎通常处理的是图像文件,我们需要一个库来将 PDF 文件页面转换为可供 OpenCV 处理的图像。Poppler 是一个优秀的选择。
系统构建流程
构建一个完整的文档 OCR 系统,大致可以分为以下几个步骤:
- 环境搭建: 配置 C++ 编译环境,安装 OpenCV、Tesseract 或 PaddleOCR,以及 Poppler 库。
- 输入处理: 读取图像文件或将 PDF 页面转换为 OpenCV 的
Mat
图像格式。 - 图像预处理: 对图像进行一系列优化操作,以提高 OCR 的准确率。
- 文本检测与定位: 在图像中找到包含文本的区域。
- 文本识别: 对检测到的文本区域进行 OCR 识别。
- 版面分析与结构化输出 (关键步骤): 对于账单和发票等结构化文档,识别出关键字段(如发票号码、日期、金额等)并以结构化数据(如 JSON)格式输出。
第一步:环境搭建
在开始编码之前,您需要确保开发环境中已经安装了所有必要的库。
对于 Tesseract:
您需要安装 Tesseract OCR 引擎及其开发库,同时下载中文语言包 (chi_sim.traineddata
)。
- 在 Ubuntu/Debian 系统中:
sudo apt-get update sudo apt-get install -y tesseract-ocr libtesseract-dev sudo apt-get install -y tesseract-ocr-chi-sim
- 在 Windows 系统中:
可以从 Tesseract 的官方 GitHub 仓库下载预编译的二进制文件,并确保将 Tesseract 的安装路径添加到系统环境变量中。同时,需要获取中文语言包。
对于 PaddleOCR:
您需要下载 PaddleOCR 的 C++ 预测库。PaddleOCR 的 GitHub 仓库中提供了详细的编译和部署文档,包括如何在 Windows (Visual Studio) 和 Linux 环境下进行编译。
安装 OpenCV:
可以从 OpenCV 官网下载源码进行编译,也可以使用包管理器进行安装。
安装 Poppler:
- 在 Ubuntu/Debian 系统中:
sudo apt-get install -y libpoppler-cpp-dev
- 在 Windows 系统中:
可以下载预编译的二进制文件,并配置好头文件和库文件的路径。
第二-三步:输入处理与图像预处理
处理 PDF 文件
OCR 引擎直接处理的是图像。因此,第一步是将 PDF 文件转换为图像。借助 Poppler
库,我们可以轻松实现这一点。
#include <iostream>
#include <opencv2/opencv.hpp>
#include <poppler-cpp.h>
cv::Mat convert_pdf_page_to_image(const std::string& pdf_file, int page_number, int dpi = 300) {
poppler::document* doc = poppler::document::load_from_file(pdf_file);
if (!doc || doc->is_locked()) {
std::cerr << "Error: Cannot open PDF file." << std::endl;
return cv::Mat();
}
if (page_number < 0 || page_number >= doc->num_pages()) {
std::cerr << "Error: Invalid page number." << std::endl;
delete doc;
return cv::Mat();
}
poppler::page* page = doc->create_page(page_number);
if (!page) {
std::cerr << "Error: Cannot create page." << std::endl;
delete doc;
return cv::Mat();
}
poppler::page_renderer renderer;
renderer.set_render_hint(poppler::page_renderer::antialiasing, true);
renderer.set_render_hint(poppler::page_renderer::text_antialiasing, true);
poppler::image image = renderer.render_page(page, dpi, dpi);
if (!image.is_valid()) {
std::cerr << "Error: Cannot render page." << std::endl;
delete page;
delete doc;
return cv::Mat();
}
cv::Mat cv_image;
if (image.format() == poppler::image::format_rgb24) {
cv_image = cv::Mat(image.height(), image.width(), CV_8UC3, image.data());
cv::cvtColor(cv_image, cv_image, cv::COLOR_RGB2BGR); // Poppler a RGB, OpenCV a BGR
} else if (image.format() == poppler::image::format_argb32) {
cv_image = cv::Mat(image.height(), image.width(), CV_8UC4, image.data());
cv::cvtColor(cv_image, cv_image, cv::COLOR_BGRA2BGR);
} else {
std::cerr << "Error: Unsupported image format from PDF." << std::endl;
}
delete page;
delete doc;
return cv_image.clone();
}
图像预处理
高质量的图像是 OCR 成功的关键。对于扫描的账单和发票,通常需要进行以下预处理步骤:
- 灰度化: 将彩色图像转换为灰度图像,以减少计算复杂性。
- 二值化: 将灰度图像转换为黑白图像。自适应阈值二值化(
cv::adaptiveThreshold
)对于处理光照不均的文档尤为有效。 - 噪声消除: 使用高斯模糊 (
cv::GaussianBlur
) 或中值滤波 (cv::medianBlur
) 去除图像中的随机噪声。 - 倾斜校正: 检测文档的倾斜角度并进行旋转校正。可以通过霍夫变换 (
cv::HoughLinesP
) 检测直线或使用最小面积外接矩形 (cv::minAreaRect
) 来实现。
cv::Mat preprocess_image(const cv::Mat& input_image) {
cv::Mat gray, blurred, thresholded;
// 1. 灰度化
cv::cvtColor(input_image, gray, cv::COLOR_BGR2GRAY);
// 2. 高斯模糊去噪
cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);
// 3. 自适应阈值二值化
cv::adaptiveThreshold(blurred, thresholded, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C,
cv::THRESH_BINARY, 11, 2);
// 可选:进行倾斜校正等更复杂的操作
return thresholded;
}
第四-五步:文本检测与识别
在进行 OCR 之前,先检测出文本所在的位置可以显著提高效率和准确性,避免对非文本区域进行识别。
文本检测
OpenCV 的 DNN 模块提供了多种预训练的文本检测模型,其中 EAST (Efficient and Accurate Scene Text Detector) 模型是一个不错的选择。
使用 PaddleOCR 进行识别
PaddleOCR 提供了集成的文本检测和识别功能,其 C++ 部署方案性能优越,对中文场景有很好的优化。使用 PaddleOCR,您可以直接输入预处理后的图像,它会返回包含位置和文本内容的结构化结果。
使用 Tesseract 进行识别
如果您选择使用 Tesseract,可以先用 EAST 模型检测出文本框,然后将每个文本框的区域 cv::Rect
传入 Tesseract 进行识别。
#include <tesseract/baseapi.h>
// ... 假设已经通过文本检测获得了文本框 a_text_roi (cv::Rect) ...
// 初始化 Tesseract
tesseract::TessBaseAPI* ocr = new tesseract::TessBaseAPI();
// "chi_sim" 代表简体中文
if (ocr->Init(NULL, "chi_sim", tesseract::OEM_LSTM_ONLY)) {
std::cerr << "Could not initialize tesseract." << std::endl;
// ... 错误处理 ...
}
ocr->SetPageSegMode(tesseract::PSM_SINGLE_BLOCK);
// 提取 ROI
cv::Mat roi_image = preprocessed_image(a_text_roi);
// 设置图像进行识别
ocr->SetImage(roi_image.data, roi_image.cols, roi_image.rows, roi_image.channels(), roi_image.step);
// 获取识别结果
char* out_text = ocr->GetUTF8Text();
std::string result_text = std::string(out_text);
// 销毁 Tesseract 实例
ocr->End();
delete ocr;
delete[] out_text;
第六步:版面分析与结构化输出
对于账单和发票,仅仅获取所有文字是不够的,我们还需要理解它们的含义。版面分析是实现这一目标的关键。
基于规则和模板的方法
对于格式相对固定的发票,这是一种简单有效的方法。
- 定义关键字段: 首先确定您需要提取的关键信息,例如“发票代码”、“发票号码”、“开票日期”、“金额合计”等。
- 定位关键字: 在 OCR 结果中搜索这些关键字。
- 相对位置提取: 根据关键字的位置,利用其相对空间关系来定位和提取目标信息。例如,“发票号码”通常位于“发票号码:”这个标签的右侧或下方。
机器学习方法
对于格式多变的文档,可以训练机器学习模型(如基于图神经网络 GNN 的模型)来理解文档布局和字段间的关系,但这需要大量的标注数据和更复杂的实现。
结构化输出
将提取到的信息以 JSON 格式输出,便于后续的系统集成和数据分析。
{
"invoice_code": "010002100311",
"invoice_number": "81804581",
"issue_date": "2025-06-20",
"total_amount": "1170.00",
"items": [
{
"description": "技术服务费",
"amount": "1000.00"
},
{
"description": "税额",
"amount": "170.00"
}
]
}
总结与展望
使用 C++ 和 OpenCV 构建一个中文文档 OCR 系统是一个涉及多个步骤的综合性项目。通过结合强大的开源工具如 Tesseract、PaddleOCR 和 Poppler,我们可以创建一个高效、准确的解决方案来自动化处理账单、发票和 PDF 文件。
对于追求更高准确率和更强泛化能力的场景,可以进一步探索:
- 深度学习驱动的版面分析: 训练专门用于文档理解的深度学习模型。
- 模型微调: 使用您自己收集和标注的数据对预训练的 OCR 模型进行微调,以适应特定类型的文档。
- 自然语言处理 (NLP) 后处理: 利用 NLP 技术对 OCR 结果进行校正和信息提取,进一步提升最终输出的质量。
希望这篇指南能为您在 C++ 环境下进行中文 OCR 开发提供一个清晰的路线图。