基本思路
直接用工具将扫描型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开放平台,选择文字识别→通用文字识别
选择文字识别创建应用,三个用于权鉴的必要参数:AppID,AppKey,SecretKey
官方文档:百度AI文字识别文档
请注意不同的场景的免费额度限制
权鉴获取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);
}