一、整体思路
- 使用 QT 创建一个用户界面,允许用户选择 PDF 扫描件所在的目录,定义需要识别的区域(可以通过鼠标绘制区域),并开始处理流程。
- 利用 Poppler 库将 PDF 文件转换为图像文件。
- 使用飞桨的 OCR 模型对用户指定的区域进行内容识别。
- 从识别结果中提取表格数据,并将其导出为表格文件格式(如 CSV 或 Excel)。
二、环境搭建
1. QT 开发环境安装:
- 从 QT 官方网站(Try Qt | Develop Applications and Embedded Systems | Qt)下载并安装适合您操作系统的 QT 开发工具包。根据不同的操作系统和编译器,进行相应的配置。
2. 安装 Poppler 库:
- Linux 系统:
bash
sudo apt-get install libpoppler-cpp-dev - Windows 系统:
- 从 Poppler 官方网站(例如:Poppler)下载预编译的库。
- 在 QT 项目的
.pro文件中添加库和头文件的路径:plaintext
INCLUDEPATH += path/to/poppler/include LIBS += -Lpath/to/poppler/lib -lpoppler-cpp
3. 安装飞桨库:
- 打开命令行终端,输入以下命令安装飞桨库:
pip install paddlepaddle - 从飞桨的官方网站下载所需的 OCR 预训练模型,并将其放置在项目可访问的位置。
三、QT 界面设计与实现
1. 创建主窗口类:
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QFileDialog>
#include <QListWidget>
#include <QProgressBar>
#include <QLineEdit>
#include <QLabel>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <iostream>
#include <vector>
#include <poppler/cpp/poppler-document.h>
#include <poppler/cpp/poppler-page.h>
#include <poppler/cpp/poppler-image.h>
class PDFTableExtractor : public QWidget {
Q_OBJECT
public:
PDFTableExtractor(QWidget *parent = nullptr) : QWidget(parent) {
// 创建界面元素
selectButton = new QPushButton("Select PDF Directory", this);
fileList = new QListWidget(this);
progressBar = new QProgressBar(this);
processButton = new QPushButton("Start Processing", this);
regionLabel = new QLabel("Specify Region (x,y,width,height):", this);
regionEdit = new QLineEdit(this);
graphicsView = new QGraphicsView(this);
scene = new QGraphicsScene(this);
graphicsView->setScene(scene);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(selectButton);
layout->addWidget(fileList);
layout->addWidget(regionLabel);
layout->addWidget(regionEdit);
layout->addWidget(graphicsView);
layout->addWidget(progressBar);
layout->addWidget(processButton);
// 连接按钮点击信号
connect(selectButton, &QPushButton::clicked, this, &PDFTableExtractor::selectDirectory);
connect(processButton, &QPushButton::clicked, this, &PDFTableExtractor::startProcessing);
}
private slots:
void selectDirectory() {
QString dir = QFileDialog::getExistingDirectory(this, "Select Directory");
if (!dir.isEmpty()) {
QDirIterator it(dir, QStringList() << "*.pdf", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
fileList->addItem(it.next());
}
}
}
void startProcessing() {
int totalFiles = fileList->count();
progressBar->setMaximum(totalFiles);
QString regionStr = regionEdit->text();
std::vector<int> region = parseRegion(regionStr);
for (int i = 0; i < totalFiles; ++i) {
QString pdfPath = fileList->item(i)->text();
processPDF(pdfPath.toStdString(), region);
progressBar->setValue(i + 1);
}
}
std::vector<int> parseRegion(const QString& regionStr) {
std::vector<int> region;
QStringList parts = regionStr.split(",");
for (const QString& part : parts) {
region.push_back(part.toInt());
}
return region;
}
void processPDF(const std::string& pdfPath, const std::vector<int>& region) {
std::vector<std::string> imagePaths = convertPDFToImages(pdfPath);
for (const std::string& imagePath : imagePaths) {
std::vector<std::vector<std::string>> tableData = extractTableFromImage(imagePath, region);
exportTable(tableData, pdfPath);
}
}
std::vector<std::string> convertPDFToImages(const std::string& pdfPath) {
std::vector<std::string> imagePaths;
poppler::document* doc = poppler::document::load_from_file(pdfPath);
if (doc) {
int numPages = doc->pages();
for (int i = 0; i < numPages; ++i) {
poppler::page* page = doc->create_page(i);
if (page) {
poppler::image image = page->render_to_image(300, 300);
std::string imagePath = pdfPath.substr(0, pdfPath.find_last_of('.')) + "_page_" + std::to_string(i) + ".png";
image.save(imagePath);
imagePaths.push_back(imagePath);
delete page;
}
}
delete doc;
}
return imagePaths;
}
std::vector<std::vector<std::string>> extractTableFromImage(const std::string& imagePath, const std::vector<int>& region) {
std::vector<std::vector<std::string>> tableData;
// 调用飞桨 OCR 模型进行指定区域的表格识别,后续详细说明
return tableData;
}
void exportTable(const std::vector<std::vector<std::string>>& tableData, const std::string& pdfPath) {
std::string csvPath = pdfPath.substr(0, pdfPath.find_last_of('.')) + ".csv";
std::ofstream csvFile(csvPath);
if (csvFile.is_open()) {
for (const auto& row : tableData) {
for (size_t i = 0; i < row.size(); ++i) {
csvFile << row[i];
if (i < row.size() - 1) {
csvFile << ",";
}
}
csvFile << "\n";
}
csvFile.close();
} else {
std::cerr << "Failed to open CSV file for writing: " << csvPath << std::endl;
}
}
private:
QPushButton *selectButton;
QPushButton *processButton;
QListWidget *fileList;
QProgressBar *progressBar;
QLabel *regionLabel;
QLineEdit *regionEdit;
QGraphicsView *graphicsView;
QGraphicsScene *scene;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
PDFTableExtractor extractor;
extractor.show();
return app.exec();
}
#include "main.moc"
2. 代码解释:
PDFTableExtractor类:selectDirectory槽函数:使用QFileDialog让用户选择包含 PDF 文件的目录,并将 PDF 文件添加到fileList中。startProcessing槽函数:从regionEdit获取用户输入的区域信息,将其转换为坐标向量,遍历文件列表进行处理。parseRegion函数:将用户输入的区域信息(以逗号分隔的字符串)解析为整数坐标向量。processPDF函数:将 PDF 转换为图像,并调用extractTableFromImage对指定区域进行表格识别,最后调用exportTable导出表格。convertPDFToImages函数:使用 Poppler 库将 PDF 页面转换为图像文件。exportTable函数:将表格数据存储为 CSV 文件。
四、飞桨 OCR 部分的实现
1. 实现指定区域的表格识别:
cpp
#include <paddleocr.h>
std::vector<std::vector<std::string>> extractTableFromImage(const std::string& imagePath, const std::vector<int>& region) {
std::vector<std::vector<std::string>> tableData;
PaddleOCR ocr;
// 假设飞桨有 API 可以指定区域进行 OCR 识别
// 以下是一种可能的实现方式,具体需根据飞桨 API 调整
Rect roi(region[0], region[1], region[2], region[3]); // 创建一个矩形区域
TablePredictResult tableResult = ocr.runTableWithRegion(imagePath, roi);
for (const auto& row : tableResult.rows) {
std::vector<std::string> rowData;
for (const auto& cell : row.cells) {
rowData.push_back(cell.text);
}
tableData.push_back(rowData);
}
return tableData;
}
2. 代码解释:
extractTableFromImage函数:- 初始化
PaddleOCR对象。 - 用
Rect对象表示用户指定的区域。 - 调用飞桨的
runTableWithRegion方法(假设存在)进行指定区域的表格识别,将结果存储在tableResult中。 - 将表格结果存储在
tableData向量中。
- 初始化
五、用户自定义区域绘制(可选)
为了让用户更方便地指定区域,可以添加一个功能,允许用户在图像上直接绘制区域:
cpp
#include <QMouseEvent>
class PDFTableExtractor : public QWidget {
Q_OBJECT
public:
PDFTableExtractor(QWidget *parent = nullptr) : QWidget(parent), isDrawing(false), startPoint(QPointF()), endPoint(QPointF()) {
// 其他界面元素创建和布局代码...
graphicsView->viewport()->installEventFilter(this);
}
protected:
bool eventFilter(QObject *obj, QEvent *event) {
if (obj == graphicsView->viewport()) {
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
isDrawing = true;
startPoint = mouseEvent->pos();
scene->addRect(QRectF(startPoint, startPoint), QPen(Qt::red));
}
} else if (event->type() == QEvent::MouseMove && isDrawing) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
endPoint = mouseEvent->pos();
QGraphicsRectItem *rectItem = scene->itemAt(startPoint, graphicsView->transform())->toGraphicsRectItem();
rectItem->setRect(QRectF(startPoint, endPoint));
} else if (event->type() == QEvent::MouseButtonRelease && isDrawing) {
isDrawing = false;
endPoint = static_cast<QMouseEvent*>(event)->pos();
QGraphicsRectItem *rectItem = scene->itemAt(startPoint, graphicsView->transform())->toGraphicsRectItem();
rectItem->setRect(QRectF(startPoint, endPoint));
regionEdit->setText(QString("%1,%2,%3,%4").arg(startPoint.x()).arg(startPoint.y()).arg(endPoint.x() - startPoint.x()).arg(endPoint.y() - startPoint.y()));
}
}
return QWidget::eventFilter(obj, event);
}
private:
bool isDrawing;
QPointF startPoint;
QPointF endPoint;
// 其他成员变量...
};
代码解释:
- 在
PDFTableExtractor类中添加eventFilter函数,用于处理鼠标事件。 - 当用户按下鼠标左键时开始绘制矩形,移动鼠标时更新矩形大小,释放鼠标时完成绘制,并将区域信息更新到
regionEdit中。
六、可能遇到的问题及解决方法
1. 飞桨 API 问题:
- 问题:飞桨可能没有提供
runTableWithRegion这样的 API 或其使用方式与假设不同。 - 解决方法:
- 深入研究飞桨的官方文档和 API 参考,可能需要结合图像裁剪和飞桨的通用 OCR 功能。先将图像的指定区域裁剪出来,再使用飞桨的 OCR 功能进行识别。例如:
cpp
// 假设使用 OpenCV 进行图像裁剪 #include <opencv2/opencv.hpp> cv::Mat cropImage(const std::string& imagePath, const std::vector<int>& region) { cv::Mat image = cv::imread(imagePath); cv::Rect roi(region[0], region[1], region[2], region[3]); cv::Mat cropped = image(roi); return cropped; } std::vector<std::vector<std::string>> extractTableFromImage(const std::string& imagePath, const std::vector<int>& region) { cv::Mat croppedImage = cropImage(imagePath, region); std::string tempImagePath = "temp_cropped_image.png"; cv::imwrite(tempImagePath, croppedImage); PaddleOCR ocr; TablePredictResult tableResult = ocr.runTable(tempImagePath); std::vector<std::vector<std::string>> tableData; for (const auto& row : tableResult.rows) { std::vector<std::string> rowData; for (const auto& cell : row.cells) { rowData.push_back(cell.text); } tableData.push_back(rowData); } // 删除临时文件 std::remove(tempImagePath.c_str()); return tableData; }
- 深入研究飞桨的官方文档和 API 参考,可能需要结合图像裁剪和飞桨的通用 OCR 功能。先将图像的指定区域裁剪出来,再使用飞桨的 OCR 功能进行识别。例如:
2. 区域输入问题:
- 问题:用户输入的区域坐标可能不合法。
- 解决方法:
- 在
parseRegion函数中添加输入验证,确保输入的坐标数量和范围合法,例如:收起
cpp
std::vector<int> parseRegion(const QString& regionStr) { std::vector<int> region; QStringList parts = regionStr.split(","); if (parts.size() == 4) { for (const QString& part : parts) { bool ok; int coord = part.toInt(&ok); if (ok) { region.push_back(coord); } else { // 输入不合法,处理错误 std::cerr << "Invalid coordinate: " << part.toStdString() << std::endl; return {}; } } } else { // 输入数量不对,处理错误 std::cerr << "Invalid region format. Please use x,y,width,height." << std::endl; return {}; } return region; }
- 在
3. 异常处理和优化:
- 问题:文件操作、OCR 识别和文件导出等操作可能出现错误。
- 解决方法:
- 在
convertPDFToImages中添加对文件加载和图像保存的异常处理。 - 在
extractTableFromImage中添加对飞桨 OCR 识别的异常处理。 - 在
exportTable中添加对文件打开失败的异常处理。 - 对于性能优化,可以使用多线程或异步处理,例如在 QT 中使用
QThread或QtConcurrent来避免界面卡顿。
- 在
七、运行项目
1. 编译并运行代码:
- 将上述代码保存为
main.cpp文件。 - 在 QT 开发环境中创建新的项目,添加
main.cpp文件,并配置.pro文件,确保包含所需的库(Poppler 库、飞桨库、OpenCV 库(如果使用))。 - 编译并运行项目,在界面中选择 PDF 文件目录,通过输入框或直接在图像上绘制指定区域,点击开始处理按钮开始批量处理。
通过上述步骤,你可以使用飞桨模型和 QT 实现对 PDF 扫描件批量识别用户自定义指定区域内容并导出表格。在实际开发中,需要根据飞桨的实际 API 进行调整,同时要注意处理各种异常情况,确保程序的稳定性和可靠性。
八、扩展和优化
1. 支持更多文件格式:
- 可扩展代码以支持除 PDF 外的其他文件格式,如 TIFF 等图像文件。
2. 高级表格处理:
- 对于复杂的表格(如合并单元格、嵌套表格),可采用更复杂的算法进行处理。
3. 用户界面优化:
- 可以添加更多的界面元素,如显示处理状态、处理结果、允许用户选择导出文件格式等。
请根据实际情况对代码进行调整和优化,以满足不同的需求。若在开发过程中遇到问题,可以查阅相关库的官方文档或继续向我咨询。

994

被折叠的 条评论
为什么被折叠?



