图片型pdf转文本文档

665 阅读4分钟

基本思路

直接用工具将扫描型pdf转文本是不行的,因为扫描型的pdf是图片。先读取整个pdf文件按页生成图片,再调用OCR识别读取文字即可。

pdf第三方库pdfbox

依赖:

        <!--pdfbox pdf解析-->
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.1</version>
        </dependency>

使用该库可对pdf文件进行基本读写操作

File file = new File(PdfFilePath);
//加载pdf,可对其进行基本读写
PDDocument pdDocument = PDDocument.load(file);
int pages =pdDocument.getNumberOfPages();// 获取PDF页数
//pdf renderer 渲染器,可转成图片读取
PDFRenderer renderer = new PDFRenderer(pdDocument);
BufferedImage image = renderer.renderImageWithDPI(i, dpi);//i为页数,dpi为精度,默认96

从pdf中读取文本

    /**
     * 指定pdf与目标文件路径,从pdf文件读取文本到指定文件
     * @param pdfPath pdf文件路径,绝对路径
     * @param targetPath 目标文件,绝对路径
     */
    public static void readTextFromPdf(String pdfPath,String targetPath)  {
        //pdf文件校验
        File pdfFile=new File(pdfPath);
        if(!pdfFile.exists()){
            System.out.println("pdf文件未发现:"+pdfPath);
            return;
        }

        File targetFile=new File(targetPath);

        PDDocument pdDocument=null;

        try{
            //读取文档
            pdDocument=PDDocument.load(pdfFile);
            //获取文档页码
            int pages=pdDocument.getNumberOfPages();
            //读取文档内容并设置读取参数
            PDFTextStripper stripper=new PDFTextStripper();
            stripper.setSortByPosition(true);
            stripper.setStartPage(1);
            stripper.setEndPage(pages);
            String content=stripper.getText(pdDocument);

            //校验目标文件
            if(targetFile.exists()){
               System.out.println("文件已存在,将覆盖文件");
            }else {
                System.out.println("目标文件不存在,新建文件");
                targetFile.createNewFile();
            }

            //写入目标文件
            FileWriter fileWriter=new FileWriter(targetFile);
            fileWriter.write(content);

        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println("写入文件成功");
    }

将pdf转换为图片

    /**
     * PDF文件转PNG/JPEG图片
     * @param PdfFilePath 完整路径
     * @param dstImgFolder 图片存放的文件夹
     * @param dpi dpi越大转换后越清晰,相对转换速度越慢,一般电脑默认96dpi
     */
    public static void pdf2Image(String PdfFilePath,
                                     String dstImgFolder,
                                     int dpi) {
        File file = new File(PdfFilePath);
        PDDocument pdDocument;
        try {
            //获取pdf文件名称与上层路径
            String imgPDFPath = file.getParent();
            int dot = file.getName().lastIndexOf('.');
            // 获取图片文件名
            String imagePDFName = file.getName().substring(0, dot);

            String imgFolderPath = null;
            if (dstImgFolder.equals("")) {
                // 获取图片存放的文件夹路径
                imgFolderPath = imgPDFPath + File.separator + imagePDFName;
            } else {
                imgFolderPath = dstImgFolder + File.separator + imagePDFName;
            }

            if (createDirectory(imgFolderPath)) {
                pdDocument = PDDocument.load(file);
                PDFRenderer renderer = new PDFRenderer(pdDocument);
                int pages =pdDocument.getNumberOfPages();// 获取PDF页数
                System.out.println("PDF page number is:" + pages);
                StringBuffer imgFilePath = null;
                for (int i = 0; i < pages; i++) {
                    String imgFilePathPrefix = imgFolderPath
                            + File.separator + imagePDFName;
                    imgFilePath = new StringBuffer();
                    imgFilePath.append(imgFilePathPrefix);
                    imgFilePath.append("_");
                    imgFilePath.append(String.valueOf(i + 1));
                    imgFilePath.append(".png");// PNG
                    File dstFile = new File(imgFilePath.toString());
                    BufferedImage image = renderer.renderImageWithDPI(i, dpi);
                    ImageIO.write(image, "png", dstFile);// PNG
                }
                System.out.println("PDF文档转PNG图片成功!");
            } else {
                System.out.println("PDF文档转PNG图片失败:"
                        + "创建" + imgFolderPath + "失败");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static boolean createDirectory(String folder) {
        File dir = new File(folder);
        if (dir.exists()) {
            return true;
        } else {
            return dir.mkdirs();
        }
    }

使用百度API进行OCR识别

登录百度AI开放平台,选择文字识别→通用文字识别

image-20201215163712350.png

选择文字识别创建应用,三个用于权鉴的必要参数:AppID,AppKey,SecretKey

官方文档:百度AI文字识别文档

请注意不同的场景的免费额度限制

image-20201215164129692.png

权鉴获取token

/**
 * @Author lsl
 * @Date 2020/12/15 9:00
 *
 * 百度API OCR 文字识别客户端
 * 用于配置权鉴认证
 */
public abstract class ApiOcrClient {
    //设置APPID/AK/SK,注意替换为自己的
    public static final String APP_ID = "2**...";
    // 官网获取的 API Key 更新为你注册的
    public static final String API_KEY = "i**...";
    // 官网获取的 Secret Key 更新为你注册的
    public static final String SECRET_KEY = "**...";

    //标准版通用文字识别,50000次/天
    public static final String GENERAL_BASIC_URL="https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic";

    /**
     * 子类可使用的客户端,用于连接
     */
    protected static AipOcr client=new AipOcr(APP_ID, API_KEY, SECRET_KEY);

    /**
     * 获取权限token
     * @return 返回示例:
     * {
     * "access_token": "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567",
     * "expires_in": 2592000
     * }
     */
    public static String getAuth() {
        return getAuth(API_KEY, SECRET_KEY);
    }

    /**
     * 获取API访问token
     * 该token有一定的有效期,需要自行管理,当失效时需重新获取.
     * @param ak - 百度云官网获取的 API Key
     * @param sk - 百度云官网获取的 Securet Key
     * @return assess_token 示例:
     * "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567"
     */
    public static String getAuth(String ak, String sk) {
        // 获取token地址
        String authHost = "https://aip.baidubce.com/oauth/2.0/token?";
        String getAccessTokenUrl = authHost
                // 1. grant_type为固定参数
                + "grant_type=client_credentials"
                // 2. 官网获取的 API Key
                + "&client_id=" + ak
                // 3. 官网获取的 Secret Key
                + "&client_secret=" + sk;
        try {
            URL realUrl = new URL(getAccessTokenUrl);
            // 打开和URL之间的连接
            HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.err.println(key + "--->" + map.get(key));
            }
            // 定义 BufferedReader输入流来读取URL的响应
            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            /**
             * 返回结果示例
             */
            System.err.println("result:" + result);
            JSONObject jsonObject = new JSONObject(result.toString());
            return jsonObject.getString("access_token");
        } catch (Exception e) {
            System.err.print("获取token失败!");
            e.printStackTrace(System.err);
        }
        return null;
    }
}

文字识别返回结果

/**
 * @Author lsl
 * @Date 2020/11/9 13:49
 *
 * 百度API文字识别返回响应,返回数据以行为单位
 */
public class ApiOcrRes {
    private String logId;
    /** 返回结果集,wordResult代表一行 */
    private List<WordResult> wordsResult;
    /** 行数 */
    private int wordsResultNum;

    public String getLogId() {
        return logId;
    }
    public void setLogId(String logId) {
        this.logId=logId;
    }

    public List<WordResult> getWordsResult() {
        return wordsResult;
    }
    public void setWordsResult(List<WordResult> wordsResult) {
        this.wordsResult=wordsResult;
    }
    public int getWordsResultNum() {
        return wordsResultNum;
    }
    public void setWordsResultNum(int wordsResultNum) {
        this.wordsResultNum=wordsResultNum;
    }
}
/**
 * 每行识别结果
 */
class WordResult{
    private String words;

    public String getWords() {
        return words;
    }

    public void setWords(String words) {
        this.words = words;
    }
}

识别图片

注意要继承上面的抽象父类以获取token

/**
 * 百度AI OCR文字识别,注意每天访问次数与accessToken过期时间
 * @param imgPath 本地图片路径
 * @param url 百度API URL
 * @param accessToken 百度API 访问Token
 * @return 识别结果
 */
public static String baiduAiOcrGeneralBasic(String imgPath,String url,String accessToken) {

    try {
        /**
    * 重要提示代码中所需工具类
    * FileUtil,Base64Util,HttpUtil,GsonUtils请从
    * https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
    * https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
    * https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
    * https://ai.baidu.com/file/470B3ACCA3FE43788B5A963BF0B625F3
    * 下载
    */
        byte[] imgData = FileUtil.readFileByBytes(imgPath);
        String imgStr = Base64Util.encode(imgData);
        String imgParam = URLEncoder.encode(imgStr, "UTF-8");

        String param = "image=" + imgParam;
        //发起请求
        String result = HttpUtil.post(url, accessToken, param);

        //JSON转换
        ApiOcrRes apiOcrRes = JSON.parseObject(result, ApiOcrRes.class);

        StringBuilder res=new StringBuilder();
        System.out.println("识别结果行数:"+apiOcrRes.getWordsResultNum());
        if(apiOcrRes.getWordsResult()!=null){
            for (WordResult words : apiOcrRes.getWordsResult()) {
                res.append(words.getWords()).append("\n");
            }
        }
        return res.toString();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}



/**
 * 将指定图片文件转换成文本并返回字符串
 * 带位置高精度识别,访问次数500/天
 *
 * @param imgPath 图片文件
 * @return 识别结果
 */
public static String ImgToText(String imgPath) {
    if (imgPath == null || imgPath.length() == 0 || !new File(imgPath).exists() || !new File(imgPath).isFile()) {
        System.out.println("文件不存在");
        return "";
    }

    //返回结果
    StringBuilder res = new StringBuilder();

    // 传入可选参数调用接口
    HashMap<String, String> options = new HashMap<>();
    options.put("detect_direction", "true");

    //ocr识别 参数为本地图片路径 获取OCR识别结果
    JSONObject resOcr = client.basicAccurateGeneral(imgPath, options);
    //JSON转换
    ApiOcrRes apiOcrRes = JSON.parseObject(resOcr.toString(), ApiOcrRes.class);

    System.out.println("识别结果:"+apiOcrRes.getWordsResultNum());
    if(apiOcrRes.getWordsResult()!=null){
        for (WordResult words : apiOcrRes.getWordsResult()) {
            res.append(words.getWords()).append("\n");
        }
    }
    return res.toString();
}

封装-识别图片目录

/**
 * 将指定图片目录的所有图片文件转换成文本到指定文本文件
 *
 * @param imgDirPath 图片目录,按顺序识别所有图片
 * @param textPath   目标文本文件路径
 * @param comparator 比较器,目录下图片的排序规则
 */
public static void ImgDirToText(String imgDirPath, String textPath, Comparator<File> comparator) {
    //图片目录校验
    File imgDir = new File(imgDirPath);
    if (!imgDir.exists() || !imgDir.isDirectory()) {
        System.out.println("错误:图片目录不存在或是文件");
        return;
    }

    //目标文本文件
    File textFile = new File(textPath);

    try {
        if (!textFile.exists() && !textFile.createNewFile()) {
            System.out.println("创建目标文件失败");
            return;
        }
        FileWriter fileWriter = new FileWriter(textFile);
        //遍历图片目录,按照顺序规则严格指定
        File[] imgFiles = imgDir.listFiles();
        //指定排序规则
        Arrays.sort(imgFiles, comparator);

        //获取Token
        String accessToken=getAuth();

        for (File file : imgFiles) {
            System.out.println("开始将图片转换为文本:" + file.getAbsolutePath());
            //获取OCR识别结果
            String res = baiduAiOcrGeneralBasic(file.getAbsolutePath(),GENERAL_BASIC_URL,accessToken);
            //写入文件
            fileWriter.write(res);
        }

        //关闭文件
        fileWriter.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

封装-使用测试

注意按照生成图片命名规则来构造比较器

    @Test
    public void imgDirToText() {
        Comparator comparator=new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                String a1=o1.getAbsolutePath();
                String a2=o2.getAbsolutePath();
                a1=a1.substring(a1.indexOf('_')+1,a1.indexOf('.'));
                a2=a2.substring(a2.indexOf('_')+1,a2.indexOf('.'));
                return Integer.parseInt(a1)-Integer.parseInt(a2);
            }
        };
        Image2Text.ImgDirToText("E:\\新建文件夹","E:\\ctf特训营1.txt",comparator);
    }