数据录入----前期调研

117 阅读8分钟

前期调研

需求:

银行中有许多的档案文件,人工录入成本过高,于是想开发一个将pdf类型的文件,识别成图片,再用ocr技术去识别图片中的文字信息这么一个功能,减少人工录入成本

调研:

  1. 初步主要是负责去查找怎么将pdf文件转换成图片格式,看来看去,最后使用的是Java开源代码,处理pdf文件的工具,PDFBox库 具体信息,看整理

  2. 使用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();
        }
    }
}

结果:

2024-04-15_171615.png

2024-04-15_171709.png

可以是可以,但是文件提取保存速度太慢。

可能原因:

看了文档和源码之后,发现他需要对数据进行rgb渲染,有可能是因为对源数据的编解码耗费了时间

2024-04-15_172329.png

解决方法:

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.继续改,也是最后的确定

还是提取数据流,保存数据流

使用的是

createrow.png 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();
        }
    }
}