可以使用 iText 和 Tesseract(谷歌 OCR 实现)的组合来完成。
首先,我会在 OCR 引擎周围放置一个界面。这让我以后可以换掉它。
public interface IOpticalCharacterRecognitionEngine {
class OCRChunk {
private Rectangle location;
private String text;
public OCRChunk(Rectangle rectangle, String text){
this.location = rectangle;
this.text = text;
}
public String getText(){ return text; }
public Rectangle getLocation(){return location;}
}
List<OCRChunk> doOCR(BufferedImage bufferedImage);
}
这个界面本质上说“OCR 引擎返回的对象是位置(矩形)和文本的组合”
然后我们需要创建一个 ITextExtractionStrategy 将ImageRenderInfo
事件转换为TextRenderInfo
使用 OCREngine
public class OCRTextExtractionStrategy implements ITextExtractionStrategy {
private final ITextExtractionStrategy innerStrategy;
private final IOpticalCharacterRecognitionEngine opticalCharacterRecognitionEngine;
private final Logger logger = Logger.getLogger(OCRTextExtractionStrategy.class.getSimpleName());
public OCRTextExtractionStrategy(ITextExtractionStrategy innerStrategy, IOpticalCharacterRecognitionEngine opticalCharacterRecognitionEngine){
this.innerStrategy = innerStrategy;
this.opticalCharacterRecognitionEngine = opticalCharacterRecognitionEngine;
}
public String getResultantText() {
return innerStrategy.getResultantText();
}
public void eventOccurred(IEventData iEventData, EventType eventType) {
// handle images
if(eventType == EventType.RENDER_IMAGE){
// extract coordinates
ImageRenderInfo imageRenderInfo = (ImageRenderInfo) iEventData;
float x = imageRenderInfo.getImageCtm().get(Matrix.I11);
float y = imageRenderInfo.getImageCtm().get(Matrix.I22);
// attempt to parse image
try {
BufferedImage bufferedImage = imageRenderInfo.getImage().getBufferedImage();
for(IOpticalCharacterRecognitionEngine.OCRChunk chunk : opticalCharacterRecognitionEngine.doOCR(bufferedImage)){
if(chunk.getText() != null && !chunk.getText().isEmpty()) {
chunk.getLocation().translate((int) x, (int) y);
TextRenderInfo textRenderInfo = pseudoTextRenderInfo(chunk);
if(textRenderInfo != null)
innerStrategy.eventOccurred( textRenderInfo, EventType.RENDER_TEXT);
}
}
} catch (IOException e) { logger.severe(e.getLocalizedMessage()); }
}
// handle anything else
else {
innerStrategy.eventOccurred(iEventData, eventType);
}
}
private TextRenderInfo pseudoTextRenderInfo(IOpticalCharacterRecognitionEngine.OCRChunk chunk){
// dummy graphics state
ModifiableGraphicsState mgs = new ModifiableGraphicsState();
try {
mgs.setFont(PdfFontFactory.createFont());
mgs.setCtm(new Matrix( 1,0,0,
0,1,0,
0,0,1));
} catch (IOException e) { }
// dummy text matrix
float x = chunk.getLocation().x;
float y = chunk.getLocation().y;
Matrix textMatrix = new Matrix( x, 0,0,
0, y, 0,
0,0,0);
// return TextRenderInfo object
return new TextRenderInfo(
new PdfString(chunk.getText(), ""),
mgs,
textMatrix,
new Stack<CanvasTag>()
);
}
public Set<EventType> getSupportedEvents() { return null; }
}
此类执行该翻译。坐标转换有一些魔力(我可能还没有完全正确)。
工作的繁重是在pseudoTextRenderInfo
将 给定的结果转换IOpticalCharacterRecognitionEngine
为TextRenderInfo
对象的方法中执行的。
为了让它工作,我们需要一个CanvasGraphicsState
可修改的。默认实现不是。所以让我们扩展默认值。
class ModifiableGraphicsState extends CanvasGraphicsState{
private Matrix ctm;
public ModifiableGraphicsState(){ super(); }
public Matrix getCtm() { return ctm; }
public ModifiableGraphicsState setCtm(Matrix ctm){this.ctm = ctm; return this;};
public void updateCtm(float a, float b, float c, float d, float e, float f) { updateCtm(new Matrix(a, b, c, d, e, f)); }
public void updateCtm(Matrix newCtm) {
ctm = newCtm.multiply(ctm);
}
}
最后,我们需要一个IOpticalCharacterRecognitionEngine
. 这个具体的实现是使用 Tesseract 完成的(如果您使用的是 Java,则为 tess4j)。
public class TesseractOpticalCharacterRecognitionEngine implements IOpticalCharacterRecognitionEngine {
private Tesseract tesseract;
public TesseractOpticalCharacterRecognitionEngine(File tesseractDataDirectory, String languageCode){
tesseract = new Tesseract();
// set data path
if(!tesseractDataDirectory.exists())
throw new IllegalArgumentException();
tesseract.setDatapath(tesseractDataDirectory.getAbsolutePath());
// set language code
if(!new File(tesseractDataDirectory, languageCode + ".traineddata").exists())
throw new IllegalArgumentException();
tesseract.setLanguage(languageCode);
}
public List<OCRChunk> doOCR(BufferedImage bufferedImage) {
List<OCRChunk> textChunkLocationList = new ArrayList<>();
try {
for(Rectangle rectangle : tesseract.getSegmentedRegions(bufferedImage, ITessAPI.TessPageIteratorLevel.RIL_WORD)){
String text = tesseract.doOCR(bufferedImage, rectangle);
textChunkLocationList.add(new OCRChunk(rectangle, text));
}
} catch (Exception e) { }
return textChunkLocationList;
}
}
然后,您可以按如下方式调用代码:
// initialize tesseract
TesseractOpticalCharacterRecognitionEngine ocrEngine = new TesseractOpticalCharacterRecognitionEngine(new File("tessdata_fast"), "eng");
// create document
PdfDocument pdfDocument = new PdfDocument(new PdfReader(new File("scanned_document.pdf")));
// extract text
SimpleTextExtractionStrategy simpleTextExtractionStrategy = new SimpleTextExtractionStrategy();
OCRTextExtractionStrategy ocrTextExtractionStrategy = new OCRTextExtractionStrategy(simpleTextExtractionStrategy, ocrEngine);
new PdfCanvasProcessor(ocrTextExtractionStrategy).processPageContent(pdfDocument.getPage(1));
// display
System.out.println(simpleTextExtractionStrategy.getResultantText());