【问题标题】:How to extract table data from scanned PDF?如何从扫描的 PDF 中提取表格数据?
【发布时间】:2018-10-25 12:51:27
【问题描述】:

我创建了一个 Java 项目,它在解析具有特定结构的可搜索 PDF 方面相当成功。其中的表格很复杂,具有合并的行或列,但在每个这样的 PDF 中,表格的结构保持不变,只有里面的文本发生了变化。借助 PDFBox、PDF2Dom 和 Tabula,我能够克服所有这些挑战。

但是,昨天当我收到一组经过扫描的新 PDF 时,问题就出现了。被扫描后,整个内容只是图像,无法搜索。感觉到对 OCR 的需求,我开始研究 Tesseract。但是,我发现仅使用它只会在没有任何上下文的情况下咳出 PDF 的整个文本内容,并且复选框会丢失。因此,我尝试使用 Ghostscript 和 Tesseract 的组合将 PDF 转换为可搜索的 PDF。我使用 Ghostscript 将扫描的 PDF 转换为 jpg 图像,方法如下:

File pdfFile = new File("D://Tess//inputFile.pdf");
List<Image> images = new ArrayList<Image>();
PDFDocument document = new PDFDocument();
document.load(pdfFile);

SimpleRenderer renderer = new SimpleRenderer();
renderer.setResolution(300);

images = renderer.render(document);

for (int i = 0; i < images.size(); i++) {
    Image img = images.get(i);
    ImageIO.write((RenderedImage) img, "jpg", new File(i + ".jpg"));
}

之后,我使用 Tesseract 将生成的图像转换回 PDF。

Tesseract tessInst = new Tesseract();
tessInst.setDatapath("D://Tess//tessdata");
List<RenderedFormat> list = new ArrayList<RenderedFormat>();
list.add(RenderedFormat.PDF);

for (int i = 0; i < images.size(); i++)
    tess.createDocuments(i + ".jpg", "D://Tess//output" + i, list);

PDF 生成得很好,甚至可以搜索,但是当我选择一个单词时,选择突出显示与实际单词有点偏差。此外,无法选择复选框。我尝试使用 PDF2Dom 生成 DOM 结构,就像我一直在使用其他 PDF 一样,这些 PDF 无需 OCR 处理即可搜索并获得了很好的结果:

Document document = parser.createDOM(pdf);

这会引发以下异常:

java.io.IOException: java.io.IOException: Multi byte glyph name not supported.

at org.mabb.fontverter.pdf.PdfFontExtractor.convertType0FontToOpenType(PdfFontExtractor.java:217)

at org.fit.pdfdom.FontTable$Entry.loadType0TtfDescendantFont(FontTable.java:193)

at org.fit.pdfdom.FontTable$Entry.getData(FontTable.java:146)

at org.fit.pdfdom.FontTable$Entry.isEntryValid(FontTable.java:162)

at org.fit.pdfdom.FontTable.addEntry(FontTable.java:49)

at org.fit.pdfdom.PDFBoxTree.processFontResources(PDFBoxTree.java:381)

at org.fit.pdfdom.PDFBoxTree.updateFontTable(PDFBoxTree.java:358)

at org.fit.pdfdom.PDFDomTree.updateFontTable(PDFDomTree.java:544)

at org.fit.pdfdom.PDFBoxTree.processPage(PDFBoxTree.java:204)

at org.apache.pdfbox.text.PDFTextStripper.processPages(PDFTextStripper.java:319)

at org.apache.pdfbox.text.PDFTextStripper.writeText(PDFTextStripper.java:266)

at org.fit.pdfdom.PDFDomTree.createDOM(PDFDomTree.java:218)

at com.pv.pdf.PdfExtractor.extractCheckboxValues(PdfExtractor.java:403)

at com.pv.pdf.PdfExtractor.getMedicalRecordDetails(PdfExtractor.java:372)

at com.pv.servlet.OnServletLogin.doPost(OnServletLogin.java:32)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)

at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)

at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)

at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:67)

at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)

at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)

at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)

at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)

at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)

at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)

at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)

at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)

at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)

at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)

at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)

at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)

at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)

at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)

at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)

at org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)

at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)

at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)

at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)

at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)

at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)

at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)

at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)

at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)

at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1526)

at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1526)

at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1526)

at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1526)

at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)

at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)

at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)

at io.undertow.server.Connectors.executeRootHandler(Connectors.java:360)

at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)

at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)

at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1985)

at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1487)

at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1378)

at java.lang.Thread.run(Unknown Source)

Caused by: java.io.IOException: Multi byte glyph name not supported.

at org.mabb.fontverter.converter.PsType0ToOpenTypeConverter.convertCmap(PsType0ToOpenTypeConverter.java:89)

at org.mabb.fontverter.converter.PsType0ToOpenTypeConverter.convert(PsType0ToOpenTypeConverter.java:50)

at org.mabb.fontverter.pdf.PdfFontExtractor.convertType0FontToOpenType(PdfFontExtractor.java:215)

... 57 more

我发现了 Ghostscript 中关于字形宽度的问题:

https://github.com/tesseract-ocr/tesseract/issues/712

但是,我不确定它在当前的用例中是否可以帮助我。但它也告诉我选择的文本突出显示是倾斜的,就像我的情况一样。我用的是Ghost4j 1.0.1版,相当于Ghostscript 9.25版,所以这里描述的问题应该已经解决了。

请帮我解决这个问题。提前谢谢你。

编辑

我并没有将错误归咎于 Ghostscript。但是我在搜索的时候发现了一个和我类似的问题,所以我在这里提供了,如果它确实指向了根本问题,那么更多有学问的人会比较容易回答我的问题。

编辑

我认为我的问题可以归结为 Tesseract 正在为输出 PDF 创建“无字形”字体,并且由于它是无字形的,因此无法生成 DOM 结构,因为它没有字形查找表字体。我尝试搜索如何更改输出字体,但没有运气。我得到的最接近的是:

https://unix.stackexchange.com/questions/306051/tesseract-is-it-possible-to-change-font-output-in-ocred-pdf/353191#353191

但我不知道需要进行哪些更改才能使其正常工作。这应该由 Tesseract 作为可配置参数提供。

【问题讨论】:

  • 我看不出 Ghostscript 在您的问题中的位置,因为您只是使用它来生成 JPEG 图像。因此,它不会影响 Tesseract 生成的 PDF 文件中的突出显示。我真的不认为这应该用 Ghostscritp 标记。
  • 这里的问题是我不确定问题的根源是什么!我已经用我使用过的所有组件标记了这个问题,以便人们可以理解它的各个方面。 Ghostscript 在很大程度上是这个过程的一部分。
  • 但如果 Ghostscript 只生成图像,那么 - 如果复选框在图像中看起来合理 - OCR 的任何“失败”都必须归结为 Tesseract。
  • 那么,你能帮我解决这个 Tesseract 的问题吗?
  • “我认为我的问题可以归结为 Tesseract 正在为输出 PDF 创建“无字形”字体这一事实” - 不要对此解释太多“无字形”术语。字体有字形,它们只是都是空的。异常文本更清楚地表明“不支持多字节字形名称”,即您只是面临 PDF2DOM 的缺点或其依赖项。您可能想要求他们对此实施支持;但请做好准备,并准备一份说明手头问题的示例 PDF。

标签: java pdf ocr tesseract ghostscript


【解决方案1】:

使用 PDF 编写器创建的 PDF 的文本提取已经是一项不平凡的工作。添加需要理解以表格形式布局的复杂性会增加另一层复杂性。必须对扫描的图像进行 OCR 以将其转换为 PDF 中的不可见文本,这又增加了一层复杂性。

也许 OCR 软件在将文本放置在 PDF 页面上的位置与图像叠加时字符在图像数据中的位置有关方面存在精度问题。这会导致 higgling 文本看起来不对劲。这可能是软件的一个缺陷,或者可能只是您需要调整一些可调整的参数以在这种情况下微调您的 OCR 结果。

一个好的测试可能是使用商业产品,例如Adobe Acrobat 对特定的纯图像 PDF 执行 OCR,然后尝试查看它们是否确实获得了您所期望的定位或遇到类似问题。

就您的确切例外而言,我很幸运能够在 FontVert java 库中找到here(不确定您是否直接使用该库),这似乎是一个本土产品。

也许您可以向该公司/个人询问这是否只是他们软件中的设计限制(我认为这是因为我不清楚为什么在这种情况下您需要转换字体格式)。

阅读复选框超出了 OCR 支持的范围,并进入了 OMR 支持。这会给您今天的工作增加另一层复杂性。

【讨论】:

  • 感谢 Joseph 回答我的问题。由于我受合同约束,无需过多透露,我可以告诉您,我已经能够从可搜索的 PDF 文档中读取复选框,但我需要 DOM 结构才能使其工作。我认为我的问题可以归结为 Tesseract 正在为输出 PDF 创建“无字形”字体,并且由于它是无字形的,因此无法生成 DOM 结构,因为它没有字体的字形查找表。我尝试搜索如何更改输出字体,但没有运气。
  • @abhishek “我需要 DOM 结构才能工作” - 您可能需要重新考虑这一点;你真的需要 DOM 结构吗?或者它只是一个舒适的细节,如果需要的话,你可以不做。只需尝试其他文本提取器的输出,例如根据您的堆栈跟踪,PDFBox 是 PDF2DOM 下的实际文本提取器。并且可能不是基本文本提取的输出,而是返回样式和位置的改进变体。
  • @mkl DOM 结构绝对不是一个舒适的细节。如果是这样,我可以使用 Tesseract 提取整个文本,它从一开始就给了我一些东西,并且过着幸福的生活!但是 OCR 为我提供了 pdf 的整个文本内容,而没有任何上下文信息,无论它是在表格中还是在其他地方。文本输出中的复选框也会被忽略。
  • 如果没有人可以帮助改进 pdf2dom 以达到您的目的,您可以尝试分析 pdf2dom 从 pdfbox 的文本和位置信息中获取这些 上下文信息 并使用相同的启发式方法,但使用不同的字体库。
猜你喜欢
  • 2017-04-02
  • 1970-01-01
  • 2018-04-18
  • 1970-01-01
  • 2015-07-10
  • 2020-05-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-09
相关资源
最近更新 更多