前期调研
需求:
银行中有许多的档案文件,人工录入成本过高,于是想开发一个将pdf类型的文件,识别成图片,再用ocr技术去识别图片中的文字信息这么一个功能,减少人工录入成本
调研:
-
初步主要是负责去查找怎么将pdf文件转换成图片格式,看来看去,最后使用的是Java开源代码,处理pdf文件的工具,PDFBox库 具体信息,看整理
-
使用ocr技术,将图片里的文字识别出来并传给后端
问题:既然识别出文字,那为什么不用Java直接把pdf里的文字提取出来。而是要用ocr
因为Java现有的库对pdf里的文本内容的识别,是建立在这个内容是文本的基础之上,而我们的pdf是图片类型的,不能直接识别,所以需要调用ocr
原因:
pdf转图片:
编写了一段程序,去实现pdf一页一页地转成图片:
代码:
public class getimage {
public static void main(String[] args) {
try {
long programStartTime = System.currentTimeMillis(); //获取开始时间
long startTime = System.currentTimeMillis(); //获取开始时间
String name = "11购房合同(协议)";
// 加载PDF文件
PDDocument document = PDDocument.load(new File("C:\\Users\\DELL\\Desktop\\" + name + ".pdf"));
PDPageTree pageTree = document.getPages();
long endTime = System.currentTimeMillis(); //获取结束时间
System.out.println("拆分时间: " + (endTime - startTime) + "ms");
ExecutorService executorService = Executors.newFixedThreadPool(8);
//新的:
PDFRenderer renderer=new PDFRenderer(document);
// 遍历每个页面
for (PDPage page : pageTree) {
int pageNum = pageTree.indexOf(page) + 1;
int count = 1;
System.out.println("Page " + pageNum + ":");
for (COSName xObjectName : page.getResources().getXObjectNames()) {
PDXObject pdxObject = page.getResources().getXObject(xObjectName);
if (pdxObject instanceof PDImageXObject) {
PDImageXObject image = (PDImageXObject) pdxObject;
System.out.println("Found image with width "
+ image.getWidth()
+ "px and height "
+ image.getHeight()
+ "px.");
String fileName = "./img/" + name + "-" + pageNum + "-" + ".jpg";
startTime = System.currentTimeMillis();
BufferedImage bufferedImage=image.getImage();
endTime = System.currentTimeMillis(); //获取保存结束时间
System.out.println("getImage时间: " + (endTime - startTime) + "ms");
//
startTime = System.currentTimeMillis();
ImageIO.write(bufferedImage, "jpg", new File(fileName));
//
endTime = System.currentTimeMillis(); //获取保存结束时间
System.out.println("保存时间: " + (endTime - startTime) + "ms");
}
}
}
document.close();
long programEndTime = System.currentTimeMillis(); //获取结束时间
System.out.println("总时间: " + (programEndTime - programStartTime) + "ms");
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
可以是可以,但是文件提取保存速度太慢。
可能原因:
看了文档和源码之后,发现他需要对数据进行rgb渲染,有可能是因为对源数据的编解码耗费了时间
解决方法:
1.简单暴力,使用多线程:
开了一个大小为四的线程池。
速度加快了。处理相同的文件,从55s--->25s左右,但平均一张图片还是在1s
/**
* @Author dinghuapeng
* @Date 2024/4/15 10:33
* @PackageName:PACKAGE_NAME
* @ClassName: executor
* @Description: TODO
* @Version 1.0
*/
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.cos.COSName;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.pdfbox.pdmodel.PDResources;
public class executor {
public static void main(String[] args) {
long programStartTime = System.currentTimeMillis(); //获取开始时间
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 加载PDF文件
PDDocument document = null;
try {
document = PDDocument.load(new File("C:\\Users\\DELL\\Desktop\\11购房合同(协议).pdf"));
PDPageTree pageTree = document.getPages();
// 遍历每个页面并提交任务到线程池
for (PDPage page : pageTree) {
int pageNum = pageTree.indexOf(page) + 1;
executorService.submit(() -> {
try {
processPage(pageNum, page);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
// 关闭线程池
executorService.shutdown();
// 等待所有任务完成
while (!executorService.isTerminated()) {
// 等待任务完成
}
// 关闭文档
document.close();
long programEndTime = System.currentTimeMillis(); //获取结束时间
System.out.println("总时间: " + (programEndTime - programStartTime) + "ms");
} catch (IOException e) {
e.printStackTrace();
}
}
private static void processPage(int pageNum, PDPage page) throws IOException {
PDResources resources = page.getResources();
for (COSName xObjectName : resources.getXObjectNames()) {
PDXObject pdxObject = resources.getXObject(xObjectName);
if (pdxObject instanceof PDImageXObject) {
PDImageXObject image = (PDImageXObject) pdxObject;
System.out.println("Found image on Page " + pageNum
+ " with width " + image.getWidth()
+ "px and height " + image.getHeight() + "px.");
String fileName = "./img/11购房合同(协议)-" + pageNum + "-" + image.hashCode() + ".jpg";
try {
ImageIO.write(image.getImage(), "jpg", new File(fileName));
System.out.println("Image saved as " + fileName);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2.跳过编解码步骤
具体就是跳过上面代码中的
if (pdxObject instanceof PDImageXObject) {
PDImageXObject image = (PDImageXObject) pdxObject;
System.out.println("Found image on Page " + pageNum
+ " with width " + image.getWidth()
+ "px and height " + image.getHeight() + "px.");
String fileName = "./img/11购房合同(协议)-" + pageNum + "-" + image.hashCode() + ".jpg";
try {
ImageIO.write(image.getImage(), "jpg", new File(fileName));
System.out.println("Image saved as " + fileName);
} catch (IOException e) {
e.printStackTrace();
}
}
直接获取,stream流,保存stream流,然后把这串stream流交给python去处理。
我的代码:
/**
* @Author dinghuapeng
* @Date 2024/4/15 10:33
* @PackageName:PACKAGE_NAME
* @ClassName: executor
* @Description: TODO
* @Version 1.0
*/
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.cos.COSName;
import java.io.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.pdfbox.pdmodel.PDResources;
public class executor2 {
public static void main(String[] args) {
long programStartTime = System.currentTimeMillis(); //获取开始时间
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 加载PDF文件
PDDocument document = null;
String name="2借款借据";
try {
document = PDDocument.load(new File("C:\\Users\\DELL\\Desktop\\"+name+".pdf"));
PDPageTree pageTree = document.getPages();
// 遍历每个页面并提交任务到线程池
for (PDPage page : pageTree) {
int pageNum = pageTree.indexOf(page) + 1;
executorService.submit(() -> {
try {
processPage(pageNum,page,name);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
// 关闭线程池
executorService.shutdown();
// 等待所有任务完成
while (!executorService.isTerminated()) {
// 等待任务完成
}
// 关闭文档
document.close();
long programEndTime = System.currentTimeMillis(); //获取结束时间
System.out.println("总时间: " + (programEndTime - programStartTime) + "ms");
} catch (IOException e) {
e.printStackTrace();
}
}
private static void processPage(int pageNum, PDPage page,String name) throws Exception {
PDResources resources = page.getResources();
for (COSName xObjectName : resources.getXObjectNames()) {
PDXObject pdxObject = resources.getXObject(xObjectName);
// 获取stream流
PDStream pdStream=pdxObject.getStream();
//new一个文件对象用来保存图片,默认保存当前工程根目录
File imageFile = new File("./img/"+name+pageNum+".txt");
//创建输出流
FileOutputStream fileOutputStream = new FileOutputStream(imageFile);
//写入数据
fileOutputStream.write(pdStream.toByteArray());
//关闭输出流
fileOutputStream.close();
}
}
}
问题:
使用这种方式直接保存的stream流信息太大,并且保存的文件格式错误。
可能原因
pdfbox库里的getstream流的方法,可能也对信息进行转码操作。
3.调用python接口
既然Java不适合处理,用Java调用python接口,让python处理。
最下策。
4.继续改,也是最后的确定
还是提取数据流,保存数据流
使用的是
createRawInputStream方法,然后把这串字节流直接存成图片格式。
代码如下
/**
* @Author dinghuapeng
* @Date 2024/4/15 10:33
* @PackageName:PACKAGE_NAME
* @ClassName: executor
* @Description: TODO
* @Version 1.0
*/
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.cos.COSName;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.pdfbox.pdmodel.PDResources;
public class executor3 {
public static void main(String[] args) {
long programStartTime = System.currentTimeMillis(); //获取开始时间
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 加载PDF文件
PDDocument document = null;
String name="11购房合同(协议)";
try {
document = PDDocument.load(new File("C:\\Users\\DELL\\Desktop\\"+name+".pdf"));
PDPageTree pageTree = document.getPages();
// 遍历每个页面并提交任务到线程池
for (PDPage page : pageTree) {
int pageNum = pageTree.indexOf(page) + 1;
executorService.submit(() -> {
try {
processPage(pageNum, page,name);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
//关闭线程池
executorService.shutdown();
//等待所有任务完成
while (!executorService.isTerminated()) {
//等待任务完成
}
//关闭文档
document.close();
long programEndTime = System.currentTimeMillis(); //获取结束时间
System.out.println("总时间: " + (programEndTime - programStartTime) + "ms");
} catch (IOException e) {
e.printStackTrace();
}
}
private static void processPage(int pageNum, PDPage page,String name) throws IOException {
PDResources resources = page.getResources();
for (COSName xObjectName : resources.getXObjectNames()) {
PDXObject pdxObject = resources.getXObject(xObjectName);
if (pdxObject instanceof PDImageXObject) {
COSStream cosStream=pdxObject.getCOSObject();
InputStream inputStream=cosStream.createRawInputStream();
//new一个文件对象用来保存图片,默认保存当前工程根目录
File imageFile = new File("./img/"+name+pageNum+".jpg");
//创建输出流
FileOutputStream fileOutputStream = new FileOutputStream(imageFile);
//写入数据
//使用缓冲来读取InputStream并写入到文件
byte[] buffer = new byte[1024]; //定义缓冲区大小
int bytesRead; //用于存储每次读取的字节数
while ((bytesRead = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, bytesRead); // 将读取的字节写入到文件
}
}
}
}
}
原因是,这个方法对字节流进行了编码操作,具体看 整理
合并pdf
两个步骤,先是拆分,然后是合并。
拆分方法:
pdfbox库里的Splitter方法文档
根据规则(使用String.match进行正则表达式的匹配)拆分,把pdf的inputstream流保存起来,然后根据这个流来合并
合并方法:
1.遍历pdf,把每页的流提取出来,合并
pdfbox库里的PDFMergerUtility方法。文档
使用 addSources(List<InputStream> sourcesList) ,可以把inputstream集合添加到文档中
然后使用mergeDocuments(MemoryUsageSetting memUsageSetting),可以合并源文档列表,将结果保存在目标文件中。
合并pdf,首先要获取每一页pdf的inputstream,然后
2.输入指定页数,将页数合并
代码:
package last;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PDFPageMerger {
public void mergeSelectedPages(String sourcePDFPath, String destinationPDFPath, List<Integer> pagesToMerge) throws IOException {
// 加载pdf文档
long programStartTime = System.currentTimeMillis(); //获取开始时间
PDDocument sourceDocument = PDDocument.load(new File(sourcePDFPath));
PDDocument destinationDocument = new PDDocument();
//将对应页数添加
for (Integer pageNumber : pagesToMerge) {
destinationDocument.addPage(sourceDocument.getPage(pageNumber - 1));
}
// 保存
destinationDocument.save(destinationPDFPath);
// 关闭文档
destinationDocument.close();
sourceDocument.close();
long endtime = System.currentTimeMillis(); //获取开始时间
System.out.println(endtime-programStartTime+"ms");
}
public static void main(String[] args) {
PDFPageMerger merger = new PDFPageMerger();
try {
List<Integer> list=new ArrayList<>(Arrays.asList(2,1,4,5,6,7,8,9,10,13,14,16,18,19,20,21,23,24,22,3));
String sourcePdf = "C:\\Users\\DELL\\Desktop\\11购房合同(协议).pdf";
// 拆分后pdf所放的文件夹
String splitPath = "./img/测试.pdf";
merger.mergeSelectedPages(sourcePdf, splitPath, list); // Example page numbers to merge
} catch (IOException e){
e.printStackTrace();
}
}
}
3.将多张图片合并为一份pdf
代码:
package last;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalTime;
import java.util.Arrays;
public class ImageToPdfConverter {
public static void main(String[] args) {
long programStartTime = System.currentTimeMillis(); //获取开始时间
try {
String[] imageFiles = {"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)8.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)2.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)5.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)6.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)9.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)10.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)11.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)12.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)13.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)14.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)15.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)16.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)17.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)18.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)19.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)20.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)21.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)22.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)23.jpg",
"D:\\Project-test\\PDF-Test\\img\\11购房合同(协议)7.jpg"};
String pdfFile = "./img/output.pdf";
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(pdfFile));
document.open();
// 遍历所有的图片文件
for (String imageFile : imageFiles) {
// 创建图片对象并添加到文档
Image image = Image.getInstance(imageFile);
// 为每张图片开始一个新的页面
document.newPage();
// 获取页面尺寸
Rectangle pagesize = document.getPageSize();
// 调整图片尺寸以适应页面,保持图片宽高比
float scale = pagesize.getWidth() / image.getWidth();
if (image.getHeight() * scale > pagesize.getHeight()) {
scale = pagesize.getHeight() / image.getHeight();
}
// 设置图片的缩放比例
image.scalePercent((int)((scale * 100) + 0.5));
image.setAbsolutePosition(0, document.getPageSize().getHeight() - image.getScaledHeight());
document.add(image);
}
// 关闭文档
document.close();
long endtme = System.currentTimeMillis(); //获取开始时间
System.out.println(endtme-programStartTime+"ms");
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
}
}