【区域识别PDF】如何使用飞桨模型和QT实现对PDF扫描件批量识别指定区域内容导出表格?

一、整体思路

  1. 使用 QT 创建一个用户界面,允许用户选择 PDF 扫描件所在的目录,定义需要识别的区域(可以通过鼠标绘制区域),并开始处理流程。
  2. 利用 Poppler 库将 PDF 文件转换为图像文件。
  3. 使用飞桨的 OCR 模型对用户指定的区域进行内容识别。
  4. 从识别结果中提取表格数据,并将其导出为表格文件格式(如 CSV 或 Excel)。

二、环境搭建

1. 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;
      }
      

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. 用户界面优化

  • 可以添加更多的界面元素,如显示处理状态、处理结果、允许用户选择导出文件格式等。

请根据实际情况对代码进行调整和优化,以满足不同的需求。若在开发过程中遇到问题,可以查阅相关库的官方文档或继续向我咨询。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值