最近接了 一个需求,客户想上传pdf,系统识别pdf内容并保存,用于后续内容检索。其实在我看来不如招一个打字员,服务器接收到文件直接让打字员识别,这样准确率又高,至于效率嘛那得看给别人开多少的工资了。
奈何我不是老板,所以只能苦哈哈的去找能够代替打字员工作的方案了。
目前市面上常用的tess4j、百度OCR、Tesseract-OCR、百度paddle。前三个要么收费,要么就是准确率要低一些。用现在的话来说,不是买不起,而是paddle更具性价比。
PaddleOCR主流的两种部署方式,一种是基于cpu部署,但是识别速度大概有此处省略1万字那么长,另外一种是基于gpu部署,将gpu参与计算,速度还是很客观的。这里主要讲的也是gpu部署。
Serving 服务化部署(Python/C++)
基于GPU PaddleHub Serving的部署
1.环境准备 Ubuntu、python3、显卡、 CUDA、CUDNN、conda3
1.1安装Nvidia基本驱动
查看可以安装的显卡驱动型号,后缀recommend是系统推荐安装的版本
ubuntu-drivers device
自动安装显卡驱动
ubuntu-drivers autoinstall / (apt install nvidia-driver-xxx 选择具体版本)
安装成功后输入reboot 重启机器命令nvidia-smi 即可验证是否安装成功,同时可以看到cuda最高支持的版本
1.2安装cuda
apt-get update
官网下载相关的安装包 https://developer.nvidia.com/cuda-11-7-0-download-archive
我这里选择的本地安装下载runfile包上传服务器并执行文件(具体版本根据显卡以及paddle官网支持的版本,目前最高支持11.7)
wget https://developer.download.nvidia.com/compute/cuda/11.7.0/local_installers/cuda_11.7.0_515.43.04_linux.run
sh cuda_11.7.0_515.43.04_linux.run
设置环境变量
sudo nano ~/.bashrc
打开文件写入环境变量
# 文本最后添加以下内容:
export CUDA_HOME=/usr/local/cuda
export PATH=${CUDA_HOME}/bin:${PATH}
export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH
使生效
source ~/.bashrc
检测是否成功
nvcc -V
1.3安装cudnn
官网下载相关tar包 https://developer.nvidia.com/rdp/cudnn-download
解压文件,对应版本要进行替换
tar -xvf cudnn-linux-x86_64-8.x.x.x_cudaX.Y-archive.tar.xz
将加压后的文件拷贝到cuda并修改权限
sudo cp cudnn-*-archive/include/cudnn*.h /usr/local/cuda/include
sudo cp -P cudnn-*-archive/lib/libcudnn* /usr/local/cuda/lib64
sudo chmod a+r /usr/local/cuda/include/cudnn*.h /usr/local/cuda/lib64/libcudnn*
这里的路径要注意匹配上面的环境变量的路径
1.4安装python3
apt-get install zlib1g-dev libbz2-dev libssl-dev libncurses5-dev libsqlite3-dev libreadline-dev tk-dev libgdbm-dev libdb-dev libpcap-dev xz-utils libexpat1-dev liblzma-dev libffi-dev libc6-dev
下载python压缩包:
wget https://www.python.org/ftp/python/3.8.10/Python-3.8.10.tgz
解压缩:
tar -vxzf Python-3.8.10.tgz
cd Python-3.8.10
编译安装:
./configure --prefix=/usr/local/python3 --with-ssl #此处指定python安装目录
make
make install
建立软连接(相当于快捷方式),建立完后可以直接使用python3命令:
sudo ln -s /usr/local/python3/bin/python3 /usr/bin/python3
sudo ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3
没有软连接的话pip3和python3均要使用绝对路径, 如 /usr/local/python3/bin/python3
使用pip3安装模块时,若因网络原因安装失败 可尝试指定安装源 -i https://pypi.tuna.tsinghua.edu.cn/simple
1.5安装anconda
下载:
wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2021.11-Linux-x86_64.sh --no-check-certificate
注意conda相对应的系统内核版本,根据实际情况在https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive查找相应版本
sh Anaconda3-2021.11-Linux-x86_64.sh
之后一直输入yes
若最后一步直接回车,则默认输入了no
需要手动执行:
eval "$(/home/admin/anaconda3/bin/conda shell.bash hook)"
conda init
2.安装 Hub Serving
安装paddlehub模块
conda install paddlepaddle-gpu==2.5.2 cudatoolkit=11.7 -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/Paddle/ -c conda-forge
下载分类、检测、识别推理模型并解压
https://github.com/PaddlePaddle/PaddleOCR/blob/static/doc/doc_ch/quickstart.md
mkdir PaddleOCR
cd PaddleOCR
mkdir inference
cd inference
将下载的推理模型上传至服务器 inference文件下
下载PaddleOCR
https://codeload.github.com/PaddlePaddle/PaddleOCR/zip/refs/heads/release/2.7
上传至服务器并解压
unzip PaddleOCR-release-2.7.zip
移动至PaddleOCR-develop
mv PaddleOCR-release-2.7 PaddleOCR-develop
cd PaddleOCR-develop
# 安装检测服务模块,如果缺模块直接pip3 install 相关模块即可:
hub install deploy/hubserving/ocr_det/
# 或,安装识别服务模块:
hub install deploy/hubserving/ocr_rec/
# 或,安装检测+识别串联服务模块:
hub install deploy/hubserving/ocr_system/
修改config.json文件
vi deploy/hubserving/ocr_system/config.json
修改deploy/hubserving/ocr_system文件夹下params.py文件
修改det_model_dir、rec_model_dir、rec_char_dict_path、cls_model_dir路径
在PaddleOCR文件夹下
为保证运行 修改成绝对路径
3.启动服务
这里采用的是指定路径启动,需要切换到PaddleOCR-develop目录下通过命令
hub serving start -c deploy/hubserving/ocr_system/config.json
建议后台启动,采用screen新建窗口
screen -S OCR
export CUDA_VISIBLE_DEVICES=0
/usr/local/python3/bin/hub serving start -c deploy/hubserving/ocr_system/config.json
crtl+a+d 窗口离线
4.java接口调试
不能直接识别pdf,这里采用pdf转图片并识别
package com.dqzh.construction.utils;
import com.dqzh.construction.custom.ResultData;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 2 * @Author: zxc
* 3 * @Date: 2023-10-16 1:21
* 4
*/
@RestController
@RequestMapping("/test")
public class OCRController {
@Resource
private RestTemplate restTemplate;
@RequestMapping("/uploadV2")
public ResultData upload(@RequestParam(value = "file", required = false) MultipartFile file) throws IOException {
try {
//创建请求头
HttpHeaders headers = new HttpHeaders();
//设置请求头格式
headers.setContentType(MediaType.APPLICATION_JSON);
//构建请求参数
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
//读入静态资源文件
StringBuilder builder = new StringBuilder();
try (PDDocument document = PDDocument.load(file.getInputStream())
) {
PDFRenderer pdfRenderer = new PDFRenderer(document);
for (int page = 0; page < document.getNumberOfPages(); ++page) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300);
ImageIO.write(bim, "jpg", baos);
ByteArrayInputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
// 使用inputStream进行后续操作
map.put("images", Collections.singletonList(ImageToBase64(inputStream)));
//构建请求
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
//发送请求
//Send the request
//根据服务器地址修改ip
Map json = restTemplate.postForEntity("http://127.0.0.1:8868/predict/ocr_system", request, Map.class).getBody();
//解析Json返回值
//Parse the Json return value
List<List<Map>> json1 = (List<List<Map>>) json.get("results");
List<Map> maps = json1.get(0);
for (Map map1 : maps) {
builder.append(map1.get("text"));
}
// 关闭流
inputStream.close();
}
return ResultData.success(builder);
} catch (Exception e) {
e.printStackTrace();
return ResultData.success("读取失败");
}
} catch (Exception e) {
e.printStackTrace();
return ResultData.success("读取失败");
}
}
public static String ImageToBase64(InputStream imgPath) {
byte[] data = null;
// 读取图片字节数组
//Read the image byte array
try {
data = new byte[imgPath.available()];
imgPath.read(data);
imgPath.close();
} catch (IOException e) {
e.printStackTrace();
}
// 对字节数组Base64编码
//Base64 encoding of byte array
Base64.Encoder encoder = Base64.getEncoder();
// 返回Base64编码过的字节数组字符串
//Returns a Base64 encoded byte array string
// System.out.println(encoder.encodeToString(data));
return encoder.encodeToString(data);
}
}