简介:一套基于Flask框架封装PaddleOCR的服务部署项目,面向需要快速搭建OCR识别接口的开发者与计算机相关专业学生,可应用于健康宝识别、文档自动化处理等场景。项目以轻量Web服务形式对外提供文字识别能力,通过简单POST请求即可提交图像数据并获得识别结果。压缩包共19个文件,以Python脚本、Markdown/HTML说明文档和JPG效果示例图为主,整体约1.25MB,目录结构简洁,便于本地调试与云端部署。内容不仅包含Flask服务端实现和测试调用脚本,还提供本地使用、云端部署及请求参数的详细说明,附图展示实际识别效果,帮助使用者理解接口调用流程与返回格式。已有288人学习下载,适合作为AI/CS相关课程设计、毕业设计或入门OCR服务部署的参考资料。
1. 一条 POST 请求拿到图像文字:Flask 壳与 PaddleOCR 推理链路的真实落点
把一张带文字的图片 POST 到一个 HTTP 接口,两三秒后拿回 JSON 格式的识别结果,这是 OCR 能力接入业务系统时最常用的交付形态。很多人早就在 notebook 里跑通过 PaddleOCR,但真正做项目部署时会被卡住:没有 Web 接口、模型初始化慢、文档只写了训练不写服务封装、部署到服务器上不知道从哪个文件开始。这份基于 Flask 的 PaddleOCR 服务部署资源,把交付过程打包成了可直接复现的 Flask 工程:server.py 统一加载推理模型,HTTP 层接收图像,templates/index.html 提供浏览器调试入口,test-post.py 演示客户端上传图片的完整写法。适合课程设计、毕业设计,也适合内部工具快速接入 OCR 能力,尤其是健康宝识别、票据录入这类需要提交图片换文字的自动化场景。
2. 环境准备与模型资源检查:先看懂目录,再谈启动
2.1 依赖安装顺序与版本坑
PaddleOCR 的安装顺序很容易被搞反。常见做法是先安装 PaddlePaddle 基础运行库,再安装 paddleocr 工具包,最后补 Flask 和 requests。如果直接pip install paddleocr,它未必会帮你装好对应版本的 paddlepaddle 运行库,启动时大概率报ModuleNotFoundError: No module named 'paddle',或者 protobuf、shapely 等依赖版本冲突。这个项目对应的模型调用方式属于 PaddleOCR 2.x 分支,安装时建议把三个核心包绑在相近的版本上,避免 3.x 返回结构变化导致解析代码失效。
# 先装 PaddlePaddle 基础库,CPU 环境用 2.6.x 足够稳定 python -m pip install paddlepaddle==2.6.2 -i https://mirror.baidu.com/pypi/simple # 再装 OCR 工具链与 Web 框架 python -m pip install paddleocr==2.7.3 flask requests第一行命令把 PaddlePaddle 固定在 2.6.2,是因为 2.7 以上对部分旧版 CUDA 环境不友好,而 2.6.x 是 CPU 推理和常见 GPU 环境兼容性最稳的一个版本。第二行里的 flash 和 requests 分别对应服务端与测试端,requests 在 test-post.py 中承担客户端传图职责。如果服务器在内网环境,记得预先下载好 paddlepaddle 和 paddleocr 的 wheel 包,离线安装时用pip install --no-index --find-links=$PACKAGE_DIR指定本地目录,否则会卡在下载阶段。安装后可以执行一次python -c "from paddleocr import PaddleOCR; PaddleOCR(use_angle_cls=True, lang='ch', show_log=False)"来验证依赖是否完整,首次执行会自动加载检测、方向分类、识别三个模型文件。
2.2 项目目录里哪些文件是服务必需的
解压后可以看到 PaddleOCR-Flask-main 目录下除了 server.py 和 README.md,还有 templates、images、test-post.py 和 caches 目录。我一般会先把 caches 单独复制出来留作模型备份,因为它存放的是模型下载后的缓存数据,里面的哈希命名文件就是推理过程中保存的中间图像和特征图,这些不是运行必需文件,但能反映请求在哪个步骤出了异常,排查图片旋转、裁切问题时非常有用。
| 文件/目录 | 作用 | 部署时是否需要 |
|---|---|---|
| server.py | Flask 应用主入口,模型初始化和 /ocr 接口 | 必需 |
| templates/index.html | 浏览器可视化调试页面,可上传图片预览结果 | 可选,保留便于演示 |
| test-post.py | 用 requests 模拟客户端上传图片的测试脚本 | 必需,用于自测 |
| images/ | 内置测试图片,如 1.jpg、2.jpg | 建议保留 |
| caches/ | PaddleOCR 模型缓存与中间产物 | 备份即可 |
| README.md | 本地启动、云端部署、接口参数说明 | 必需,部署前通读 |
templates/index.html 对生产环境不是必需品,不过课程答辩或给非技术同事演示时非常方便,浏览器打开 Flask 根路径就能看到上传按钮,不用每次敲命令行。images 目录里的 demo.jpg 和 5407_corrected.jpg 适合做请求级联测试:先用小图验证链路通,再用文字密集的图验证识别效果。
2.3 PaddleOCR 推理最小闭环:一条图片输入路径的完整链路
PaddleOCR 的标准推理链路包含三个子模块:文本检测 DB、方向分类器、文本识别 CRNN。server.py 初始化时会把这三个模型一起加载进内存,后续每个请求只做前向推理,不再重复读盘。推理代码的逻辑并不复杂,先传入图片路径,然后逐层取出文本框坐标、识别文本和置信度。
from paddleocr import PaddleOCR # 初始化时一次性加载三件套:检测 + 方向分类 + 识别 ocr = PaddleOCR( use_angle_cls=True, # 启用方向分类器,适合手机拍摄的倾斜图片 lang='ch', # 中文识别,也能兼容英文和数字 show_log=False # 关闭推理日志,避免污染服务打印 ) # 对单张图片执行识别 result = ocr.ocr("images/1.jpg", cls=True) # 解析返回结果:result 的每个元素对应一张图 for block in result: for line in block: box = line[0] # 四个角点坐标,用于定位文字在画面中的位置 text = line[1][0] # 识别出的文本内容 confidence = line[1][1] # 置信度,小于 0.6 的通常需要人工确认 print(f"坐标: {box}, 文本: {text}, 置信度: {confidence}")初始化里的use_angle_cls=True看似只多了一个参数,但它会让方向分类器先判断图片是否旋转了 90 度或 180 度,再送入识别模型。像手机拍的证件、翻拍的纸质文档,这个开关能明显提升准确率,代价是单张图片多出十几毫秒的推理时间。lang='ch'指定中英文混合模型,如果业务场景全是英文,换成lang='en'后模型体积更小,首包传输更快。代码里对返回结果的解析按“图块 → 文本框 → 文本与置信度”三层展开,项目里 server.py 就是把这段逻辑包进 Flask 路由,再把数据转成 JSON 返回给调用方。
3. Flask 接口设计:路由、参数表与 test-post.py 的第一次调用
3.1 server.py 的路由结构与模型加载时机
Flask 服务的路由通常是两个:根路径/返回 templates/index.html,用于浏览器访问;/ocr接收 POST 请求并执行推理。模型对象要声明在路由函数外部、模块加载时完成初始化,这是接口响应速度的关键。如果把PaddleOCR()写进视图函数,每次请求都要重建三个模型,第一次调用会被拖到几十秒,线上直接触发超时。
import io import json from flask import Flask, request, jsonify, render_template from paddleocr import PaddleOCR from PIL import Image app = Flask(__name__) # 全局只初始化一次,进程生命周期内复用同一份模型 ocr_engine = PaddleOCR( use_angle_cls=True, lang='ch', show_log=False ) @app.route('/') def index(): # 返回调试页面,浏览器可以直接上传图片 return render_template('index.html') @app.route('/ocr', methods=['POST']) def ocr_service(): # 1. 从表单文件字段读取图片 if 'image' in request.files: file_storage = request.files['image'] image_data = file_storage.read() else: image_data = request.get_data() # 2. 转成 PIL Image 后再存临时文件,兼容内存中直接传入的字节流 image = Image.open(io.BytesIO(image_data)).convert('RGB') temp_path = '/tmp/ocr_input.jpg' image.save(temp_path, quality=95) # 3. 执行推理并整理结果 result = ocr_engine.ocr(temp_path, cls=True) lines = [] for block in result: for line in block: box = line[0] text = line[1][0] confidence = float(line[1][1]) lines.append({ "text": text, "confidence": confidence, "box": box }) return jsonify({"code": 0, "data": lines})request.files.get('image')是前端表单上传文件时的标准取法,用file_storage.read()直接拿到二进制内容。Image.open(io.BytesIO(image_data))让接口既支持 multipart 文件,也支持二进制流直接提交,convert('RGB')的目的是把带透明度通道的 PNG 图统一转成三通道,固定成 JPEG 后写盘,避免 PaddleOCR 在处理 RGBA 输入时出现通道数不匹配的报错。返回结构里 box 字段是四角坐标列表,前端可以据此画红框标出文字位置。
3.2 接口参数表与返回结构说明
这个项目的 README 中已经把接口参数写得很清楚,部署时可以直接作为接口文档交付。核心参数集中在识别语言、方向分类、是否返回坐标三项,文件本身通过表单字段传递。
| 参数名 | 类型 | 必须 | 说明 |
|---|---|---|---|
| image | file | 是 | 表单文件字段,支持 jpg、png,大小建议不超过 10MB |
| lang | string | 否 | 识别语言,ch 为中英文,en 为英文,默认 ch |
| use_angle_cls | bool | 否 | 是否启用方向分类器,true/false,默认 true |
| detail | bool | 否 | 是否返回每个文本框坐标,默认 true |
返回内容统一封装成 JSON,调用方只需要判断code是否为 0,再遍历data数组取text字段即可。置信度字段是浮点数,取值范围 0 到 1,识别模糊图片时建议在业务侧过滤掉置信度低于 0.6 的结果,宁可漏识也不要把错误文本直接入库。
{ "code": 0, "data": [ { "text": "姓名:张XX", "confidence": 0.987, "box": [[25, 34], [125, 34], [125, 64], [25, 64]] }, { "text": "证件号码:110101199001011234", "confidence": 0.964, "box": [[25, 72], [285, 72], [285, 104], [25, 104]] } ] }返回结构里的 box 坐标顺序是“左上、右上、右下、左下”,不是随手写的任意四边形顺序。如果前端要画 Polygon,必须按这个顺序连线,否则会出现文字框交叉。置信度低于 0.6 的结果建议后端主动标记"need_review": true,让下游人工审核,这个逻辑可以在 server.py 的返回组装里加一行判断。
3.3 用 test-post.py 发起第一次识别请求
项目里的 test-post.py 解决了“服务起来了但不敢确认能不能用”的尴尬。脚本逻辑非常简单,构造一个表单文件字段,用 requests.post 发给本地服务,再把响应打印出来。正式接入时,把这套写进你的业务客户端即可。
import requests # 服务地址,本地测试用 127.0.0.1,云端部署换成 ECS 公网 IP url = "http://127.0.0.1:5000/ocr" # 以表单文件字段方式上传图片,字段名必须和 server.py 里一致 files = {"image": open("images/1.jpg", "rb")} # 超时时间设 30 秒,OCR 推理在 CPU 环境对复杂图片可能超过 10 秒 resp = requests.post(url, files=files, timeout=30) # 服务端返回的是 JSON,直接按字典读取 result = resp.json() print("状态码:", resp.status_code) print("业务码:", result["code"]) for item in result["data"]: print(f"识别文本: {item['text']}, 置信度: {item['confidence']:.4f}")files字典的键名"image"必须与 server.py 里request.files['image']完全一致,拼错任何一个字母都会导致服务端读不到文件,返回 400。timeout=30是客户端层面的保护,防止服务端长时间不响应导致业务线程被拖死。第一次运行如果报连接拒绝,先看 Flask 服务是否还在前台运行;如果是部署到云服务器,还要确认安全组规则里放通了 5000 端口,这个坑在阿里云 ECS 上尤其常见。
4. 从开发机到服务器:gunicorn、Nginx 反向代理与 systemd 守护
4.1 为什么 flask run 不能直接用于生产
python server.py启动的是 Flask 自带开发服务器,底层是 Werkzeug 单进程模型,能扛住开发调试的小流量,但一旦并发请求上来,会出现请求排队、CPU 占用异常、图片上传大文件时连接被重置等问题。常见做法是引入 gunicorn 作为 WSGI 服务器,把 Flask 应用对象加载到多个 worker 进程中,利用多核 CPU 并行处理请求。gunicorn 本身只负责进程管理和请求分发,真正的应用逻辑仍然跑在 server.py 里。
4.2 gunicorn 启动命令与 worker 数选择
启动命令的写法决定了服务能不能稳定扛住并发。PaddleOCR 推理每个 worker 进程会持有完整模型,内存占用大致在 1GB 到 2GB 之间,worker 数开多了容易把服务器内存打满。我一般先按 CPU 核心数的一半起步,同时预留内存余量。
# 2 个 worker,绑定 5000 端口,超时 120 秒 gunicorn -w 2 -b 0.0.0.0:5000 -t 120 server:app # 如果内存紧张,改为单 worker 配合多线程 gunicorn -w 1 --threads 4 -b 0.0.0.0:5000 -t 120 server:app-w 2是 worker 进程数,简单理解成同时能并行处理的请求数上限。-t 120是超时时间,默认值 30 秒对 PaddleOCR 不够,一张高分辨率图片在 CPU 上推理就可能超过 30 秒,超时后 gunicorn 会直接 kill 掉 worker,导致服务反复重启。第二种写法里--threads 4让单进程内部用线程处理请求,线程共享模型内存,适合并发低但单请求耗时长、内存又紧张的场景。启动后访问http://服务器IP:5000/如果能看到调试页面,说明 gunicorn 已经正常接管 Flask 应用。
4.3 Nginx 反向代理、上传限制与静态资源
把 Nginx 放在 gunicorn 前面,主要是为了处理三件事:客户端图片上传体积限制、静态资源访问、外部请求到内部进程的转发。PaddleOCR 服务本身不擅长处理大文件,Nginx 可以直接在入口处限制请求体大小,超过 10MB 的图片直接拒绝,省掉模型推理上的无效消耗。
server { listen 80; server_name your_domain_or_ip; # 上传大小限制,OCR 图片一般 10MB 以内足够 client_max_body_size 10m; location / { # 转发到 gunicorn 监听端口 proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 推理可能耗时较长,关闭 Nginx 层快速超时 proxy_read_timeout 300s; proxy_send_timeout 300s; } }client_max_body_size 10m设的是请求体上限,超过 10MB 的业务图片会在 Nginx 层直接返回 413,而不是把压力传给后端模型。proxy_read_timeout 300s很关键,因为 PaddleOCR 推理是计算密集型任务,拿 4MB 图片在纯 CPU 环境跑一次可能耗时几十秒,Nginx 默认 60 秒超时对于大图不够,调大后能避免误杀慢请求。配置完成后执行nginx -t检查语法,再systemctl reload nginx生效,这时候服务对外端口从 5000 变成 80,访问路径也变成http://IP/ocr,test-post.py 里的地址要同步修改。
4.4 systemd 让服务开机自启与崩溃自动拉起
在服务器上维护进程,最怕登录终端一关服务就挂。用 systemd 写一个服务单元文件,能实现开机自动启动、进程崩溃自动拉起、日志集中管理,比手动 nohup 可靠得多。文件路径/etc/systemd/system/ocr.service,内容可以直接套用下面的模板。
[Unit] Description=PaddleOCR Flask Service After=network.target [Service] User=www-data WorkingDirectory=/opt/PaddleOCR-Flask-main # 用 gunicorn 启动,加载 server 模块里的 app 对象 ExecStart=/usr/bin/gunicorn -w 2 -b 127.0.0.1:5000 -t 120 server:app Restart=always RestartSec=5 [Install] WantedBy=multi-user.targetWorkingDirectory必须指向 server.py 所在目录,否则 gunicorn 找不到应用对象。ExecStart指定 gunicorn 全路径,这是 systemd 环境里 PATH 精简导致的最常见故障点。Restart=always表示进程无论因为什么原因退出都自动重启,RestartSec=5控制重启间隔 5 秒,避免模型加载失败时陷入快速重启风暴。配置好后执行systemctl daemon-reload、systemctl enable --now ocr,再用systemctl status ocr查看运行状态。如果端口被占用,先ss -lntp | grep 5000找到旧进程,杀掉后重启服务。
提示:每次更新 server.py 代码后,只需要
systemctl restart ocr即可,不需要重新加载系统服务配置。如果模型或依赖发生变化,重启间隔建议拉长到 10 秒以上。
5. 线上验证与耗时调优:从 curl 自测到模型切换
5.1 用 curl 做连通性自测
部署完成后先别急着写业务调用,用 curl 从命令行直接模拟一次请求,快速验证链路是否打通。curl 能同时暴露网络层、HTTP 层和应用层的异常,比打开浏览器调试更精准。
# 上传 images/1.jpg 到 /ocr 接口,打印响应头和响应体 curl -X POST http://127.0.0.1:5000/ocr \ -F "image=@images/1.jpg" \ -w "\nHTTP状态码: %{http_code}\n" # 只请求根路径,确认 Flask 是否正常返回调试页面 curl -I http://127.0.0.1:5000/参数-F "image=@images/1.jpg"表示以表单字段形式上传文件,@前缀告诉 curl 后面的字符串是本地文件路径而不是字面量。-I只发送 HEAD 请求,用来确认 Web 服务进程本身没有宕机。如果返回 500,多半是 server.py 在解析图片或组装 JSON 时抛了异常,查看 gunicorn 日志定位即可;如果返回 502,则是 Nginx 后端连接超时或 gunicorn worker 全部崩溃。
5.2 耗时瓶颈定位与定位策略
单张图片接口耗时超过预期时,先明确瓶颈在传输还是推理。用 test-post.py 本地调用对比输出时间,如果本地小于 100ms 而云端超过 3 秒,问题在网络带宽或图片体积;如果本地和服务端都超过 2 秒,瓶颈在模型推理。PaddleOCR 单个请求的耗时主要由三部分构成:检测模型推理、方向分类、识别模型推理。日志里会输出每张图片各阶段的耗时分布,观察是检测花了 1.5 秒还是识别花了 1.8 秒,再针对性优化。
| 瓶颈阶段 | 现象 | 处理方式 |
|---|---|---|
| 文本检测慢 | 图片中文字区域多,检测耗时占比高 | 降低输入图片分辨率,或换 mobile 检测模型 |
| 方向分类慢 | 每张图片都启用方向分类 | 业务图片都是正向时,在接口里加参数关闭分类器 |
| 识别模型慢 | 文字行多,识别耗时占比高 | 换 ch_PP-OCRv4_mobile_rec 模型,识别速度提升明显 |
5.3 轻量模型替换与方向分类器开关技巧
项目默认加载的是标准版模型,在内存和速度上都不是最优解。如果服务器只有 2 核 4GB 配置,建议显式指定轻量模型组合,把检测模型换成 mobile 版,识别模型换成 mobile 版,初始化参数写在 server.py 的 PaddleOCR 调用里即可。
from paddleocr import PaddleOCR ocr = PaddleOCR( use_angle_cls=True, lang='ch', show_log=False, det_model_dir='./models/ch_PP-OCRv4_mobile_det_infer', rec_model_dir='./models/ch_PP-OCRv4_mobile_rec_infer', cls_model_dir='./models/ch_ppocr_mobile_v2.0_cls_infer' )det_model_dir和rec_model_dir分别指定检测模型与识别模型的本地路径,指定后不会再触发在线下载,离线服务器也能直接运行。mobile 系列模型体积比 server 版小大约一半,单次推理耗时能减少 30% 到 50%,识别精度差异在正常拍摄的文档上几乎感知不到。修改完模型路径后,要把原来的模型缓存清理掉,否则 PaddleOCR 仍会从缓存目录读取旧权重。这里还留一个可以继续深挖的扩展点:用--preload配合 gunicorn 启动,在 worker fork 前加载模型,让多个 worker 共享同一份模型权重,减少 2GB 级的内存重复占用,这对 4GB 内存的入门云服务器非常有用。
本文还有配套的精品资源,点击获取