我正在开发一种算法,以便按阅读顺序从PDF文件中提取文本和图像。我为此目的使用iText java,基本上我的算法如下工作。
locationTextExtractionStrategy
文本块。对于具有中到复杂布局的PDF文件,此方法可以获得大约80%或更多的结果。我知道几乎不可能获得100%的准确率,因为PDF文件不会按阅读顺序存储信息。
我想要做的是提高我的准确度,但问题是iText阻止我这样做。我在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中的一个错误。
正如您所看到的,如果该文本块的内容以空格结尾,那么我正在缩小文本块。但这是一个临时修复,我不想这样做,因为它也缩小了正确的文本块。
这个有解决方法吗?或者,如果我的代码中存在问题,请帮我修复..
答案 0 :(得分:1)
我在这里假设,如果你知道列的位置,你可以将每个矩形分配给正确的列。在我看来,如果你在右手栏的左边缘画一条线,你可以根据它们的中心是否位于该边缘的右边或左边来正确分配几乎所有的矩形。所以问题是在存在异常值的情况下找到最佳描述数据的参数(特别是最右列的左边缘)。
绝对正确的方法可能是适合某种统计模型,但我认为有一些更容易的黑客可能有效。
1)图像中的所有重叠矩形似乎都非常小。也许您可以简单地删除给定大小以下的矩形,找出列应该在哪里,然后根据它的中心是否位于右侧列的左侧边缘的左侧或右侧来分配每个小矩形。
2)有一种一般策略可以拟合被https://en.wikipedia.org/wiki/RANSAC衍生的异常值污染的数据。
2a)首先将模型拟合到少量数据。您将多次重复2a和2b,并选择最佳结果。您希望为其中一个案例选择的初始点完全没有异常值。请注意,如果有N个异常值并且您将数据划分为N + 1个块,则这些块中的至少一个必须完全没有异常值。
2b)初步拟合后,查看所有数据并确定哪些点是异常值并暂时忽略它们(即将k个最差拟合点放在一边)。然后使用剩余的点再次适合模型。在许多情况下,你可以证明,如果你无限期地重复这个步骤,它最终会收敛到某个东西,因为改变被识别为k最差拟合的点可以改善拟合,重新拟合模型也是如此,所以每次迭代都会改善拟合直到你没有变化,此时您声明该过程已经收敛。