使用 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 開發提供一個清晰的路線圖。