如何从扫描的PDF中提取表格数据?

时间:2018-10-25 12:51:28

标签: java pdf ocr tesseract ghostscript

我创建了一个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结构,就像我处理其他无需经过OCR处理并获得良好结果的PDF一样:

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作为可配置参数提供。

1 个答案:

答案 0 :(得分:0)

使用PDF编写器创建的PDF的文本提取已经不是一件容易的事。增加了需要理解的复杂性,以表格形式进行了布局,这又增加了另一层复杂性。必须进行OCR扫描的图像才能将它们转换为PDF中的不可见文本,这又增加了另一层复杂性。

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

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

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

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

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