iText提取文本块的错误位置

时间:2016-07-30 01:37:56

标签: java algorithm pdf itext

我正在开发一种算法,以便按阅读顺序从PDF文件中提取文本和图像。我为此目的使用iText java,基本上我的算法如下工作。

  1. 使用iText提取页面中每个文本块的坐标。
  2. 使用提取的坐标创建Rectangle对象。在这一步之后,我们有一大堆矩形对象代表页面中的实际文本块。
  3. 将矩形分组为更大的文本块,这些文本块将对应于pdf页面中的实际列。
  4. 按Y排序文本块,然后按X
  5. 排序
  6. 逐个应用locationTextExtractionStrategy文本块。
  7. 对于具有中到复杂布局的PDF文件,此方法可以获得大约80%或更多的结果。我知道几乎不可能获得100%的准确率,因为PDF文件不会按阅读顺序存储信息。

    我想要做的是提高我的准确度,但问题是iText阻止我这样做。我在iText中发现了一个问题。它有时会提取文本块的错误位置,这使得我的算法不正确。以下图片就是一个很好的例子。

    Actual Pdf Page Resulting Rectangles after extraction using iText

    您可以看到,在实际的PDF页面中,列之间存在明显的差距。但是得到的矩形在该间隙之间包含一些有缺陷的矩形,这使我无法识别正确的列。

    以下是我用来提取文本块位置的代码。

    package com.InteliText.Extract;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    
    import com.itextpdf.text.Rectangle;
    import com.itextpdf.text.pdf.parser.ImageRenderInfo;
    import com.itextpdf.text.pdf.parser.LineSegment;
    import com.itextpdf.text.pdf.parser.SimpleTextExtractionStrategy;
    import com.itextpdf.text.pdf.parser.TextExtractionStrategy;
    import com.itextpdf.text.pdf.parser.TextRenderInfo;
    import com.itextpdf.text.pdf.parser.Vector;
    
    /*
     * THIS CLASS ACT AS THE TEXT EXTRACTOR FOR THE PREPROCESSOR 
     */
    public class PreProcessorStrategy extends SimpleTextExtractionStrategy{
    
        private StringBuilder result = new StringBuilder();
    
        private ArrayList<Double> fontSizes = new ArrayList<Double>();
        private ArrayList<Double> lineSpaces = new ArrayList<Double>();
        private ArrayList<TextSegment> textSegments = new ArrayList<TextSegment>();
    
        Vector previousBaseLine = null;
    
        @Override
        public void beginTextBlock() {
            // TODO Auto-generated method stub
    
        }
    
        @Override
        public void endTextBlock() {
            // TODO Auto-generated method stub
    
        }
    
        @Override
        public void renderImage(ImageRenderInfo arg0) {
            // TODO Auto-generated method stub
    
        }
    
        @Override
        public void renderText(TextRenderInfo renderInfo) {
    
            //This code assumes that if the baseline changes then we're on a newline
            Vector curBaseline = renderInfo.getBaseline().getStartPoint();
            Vector topRight = renderInfo.getAscentLine().getEndPoint();
            //System.out.println(renderInfo.getText()+"\t"+curBaseline.get(0)+"\t"+topRight.get(0));
    
            if(curBaseline.get(1) < 800 && curBaseline.get(1) > 50 ) {
                // Chunk of text as a rectangle
                Rectangle rect = new Rectangle(curBaseline.get(0), curBaseline.get(1), topRight.get(0), topRight.get(1));
    
                double curFontSize = rect.getHeight();
                fontSizes.add(curFontSize);
                String text = renderInfo.getText();
                boolean isBullet = text.contains("•");
                if(!(text.equals(" ") || text.equals("  ") || text.equals("   ")) && !isBullet) {
                    double endX = topRight.get(0);
                    if(text.endsWith(" "))
                        endX -= 8;
    
                    textSegments.add(new TextSegment(curBaseline.get(0),endX,curBaseline.get(1),topRight.get(1),renderInfo.getText(),curFontSize));
                }
    
                result.append(renderInfo.getText());
            }
            previousBaseLine = topRight;
        }
    
        @Override
        public String getResultantText() {
            // TODO Auto-generated method stub
            return result.toString();
        }
    
        public ArrayList<TextSegment> getResultantTextSegments() {
            return this.textSegments;
        }
    

    我使用生成的textSegments ArrayList通过查看存储在那些textSegments中的坐标来创建矩形对象。我怀疑这可能是iText中的一个错误。

    正如您所看到的,如果该文本块的内容以空格结尾,那么我正在缩小文本块。但这是一个临时修复,我不想这样做,因为它也缩小了正确的文本块。

    这个有解决方法吗?或者,如果我的代码中存在问题,请帮我修复..

1 个答案:

答案 0 :(得分:1)

我在这里假设,如果你知道列的位置,你可以将每个矩形分配给正确的列。在我看来,如果你在右手栏的左边缘画一条线,你可以根据它们的中心是否位于该边缘的右边或左边来正确分配几乎所有的矩形。所以问题是在存在异常值的情况下找到最佳描述数据的参数(特别是最右列的左边缘)。

绝对正确的方法可能是适合某种统计模型,但我认为有一些更容易的黑客可能有效。

1)图像中的所有重叠矩形似乎都非常小。也许您可以简单地删除给定大小以下的矩形,找出列应该在哪里,然后根据它的中心是否位于右侧列的左侧边缘的左侧或右侧来分配每个小矩形。

2)有一种一般策略可以拟合被https://en.wikipedia.org/wiki/RANSAC衍生的异常值污染的数据。

2a)首先将模型拟合到少量数据。您将多次重复2a和2b,并选择最佳结果。您希望为其中一个案例选择的初始点完全没有异常值。请注意,如果有N个异常值并且您将数据划分为N + 1个块,则这些块中的至少一个必须完全没有异常值。

2b)初步拟合后,查看所有数据并确定哪些点是异常值并暂时忽略它们(即将k个最差拟合点放在一边)。然后使用剩余的点再次适合模型。在许多情况下,你可以证明,如果你无限期地重复这个步骤,它最终会收敛到某个东西,因为改变被识别为k最差拟合的点可以改善拟合,重新拟合模型也是如此,所以每次迭代都会改善拟合直到你没有变化,此时您声明该过程已经收敛。