news 2026/6/10 7:43:53

Jetson Orin Nano 部署 PaddleOCR 终极方案:ONNX + OpenCV DNN,6倍提速!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Jetson Orin Nano 部署 PaddleOCR 终极方案:ONNX + OpenCV DNN,6倍提速!

Jetson Orin Nano 部署 PaddleOCR 终极方案:ONNX + OpenCV DNN,6倍提速!

设备: NVIDIA Jetson Orin Nano Super (8GB)
效果: 比 C++ Paddle Inference 快6 倍,单图 ~1 秒
核心: PP-OCRv5 Mobile → ONNX → OpenCV DNN CUDA


一、前言

在 Jetson Orin Nano 上部署 PaddleOCR 做文字识别,网上能找到的教程基本都指向 C++ Paddle Inference 方案。但实际跑下来,速度只有5.5 秒/图,完全没法用。

PaddlePaddle 官方至今没有发布 JetPack 6 (L4T 36.x) 的 GPU 预编译包。没有 GPU 加速,OCR 在嵌入设备上就是慢动作。

经过一整天的折腾,我找到了一条可行的路:将 PP-OCRv5 模型导出为 ONNX,用 OpenCV DNN + CUDA 做推理。最终速度提升了6 倍,从 5.5s 降到 1s 以内。

这篇文章记录了完整过程,包括 8 个大坑和解决方案。


二、环境说明

项目参数
设备NVIDIA Jetson Orin Nano Super (P3767-0005)
CPU6核 ARM Cortex-A78AE
GPUAmpere GA10B, 1024 CUDA cores, SM 8.7
内存8GB LPDDR5 (CPU/GPU 统一)
系统Ubuntu 22.04, L4T R36.5.0, JetPack 6.0
CUDA12.6.68
cuDNN9.3.0
OpenCV4.10.0 (自编译, CUDA 加速)
Python3.10.12

三、为什么不用 PaddlePaddle GPU?

一句话:PaddlePaddle 没有 JetPack 6 的 GPU 预编译包

平台PaddlePaddle GPU 预编译
x86_64 Linux CUDA 12
Jetson JetPack 5.x (CUDA 11.4)
Jetson JetPack 6.x (CUDA 12.6)

有人用 JetPack 5.x 的预编译包在 JetPack 6 上跑(利用 CUDA 向后兼容),但这种方式:

  • TensorRT 不兼容(预编译 TRT 8.5 vs 系统 TRT 10.3)
  • FP16 反而更慢(Orin Nano 无专用 FP16 Tensor Core)
  • CPU 模式 Segfault(ARM 没有 Intel MKL)
  • 速度 5.5s/图,太慢

从源码编译 PaddlePaddle 理论上可行,但耗时 4+ 小时且需要 8GB+ 内存,失败率高。


四、核心思路

PP-OCRv5 Mobile 模型 (PIR格式) │ ▼ paddle.onnx.export() ← 导出 ONNX │ ▼ onnxsim simplify() ← 简化模型 │ ▼ OpenCV DNN + CUDA ← GPU 推理 │ ▼ PaddleOCR 原生后处理 ← 复用 DTPostProcess + CTCLabelDecode │ ▼ 输出: JSON 文本 + 可视化图片

为什么选 OpenCV DNN?

  • Jetson 上 OpenCV 已编译 CUDA 加速版(含 DNN CUDA 后端)
  • 无需额外依赖,无需 PaddlePaddle 运行时
  • 推理速度实测 67ms/检测 + 25ms/识别框

为什么选 onnxsim?

OpenCV DNN 的 ONNX 解析器比较挑剔,部分算子不支持。原始 ONNX 加载直接报Reshape错误,onnxsim简化后可以正常加载。


五、Step by Step 实战

Step 1: 创建虚拟环境

cd/home/ysdhanji/ocr python3-mvenv venv_paddleocrsourcevenv_paddleocr/bin/activate pipinstall--upgradepip

Step 2: 安装依赖

# PaddlePaddle CPU 版 (GPU 不可用)pipinstallpaddlepaddle-fhttps://www.paddlepaddle.org.cn/whl/linux/aarch64/cpu/stable.html# PaddleOCRpipinstallpaddleocr# paddle2onnx (--no-deps 跳过 onnxoptimizer 编译)pipinstallpaddle2onnx --no-deps# ONNX 工具链pipinstallonnx onnxsim onnxruntime

Step 3: 准备 PIR 模型

PP-OCRv5 模型可以从 PaddleOCR 官方下载:

wgethttps://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_mobile_det_infer.tarwgethttps://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_mobile_rec_infer.tartarxf PP-OCRv5_mobile_det_infer.tartarxf PP-OCRv5_mobile_rec_infer.tar

模型是 PIR 格式(PaddlePaddle 3.x 新格式),包含inference.json+inference.pdiparams

Step 4: PIR → ONNX 导出

importpaddleimportos os.makedirs('models',exist_ok=True)# === 导出检测模型 ===det_model=paddle.jit.load('PP-OCRv5_mobile_det_infer/inference')# 注意: 文件前缀, 不是目录!det_model.eval()paddle.onnx.export(det_model,'models/PP-OCRv5_mobile_det',# 不带.onnx后缀!input_spec=[paddle.static.InputSpec(shape=[1,3,-1,-1],dtype='float32',name='x')],opset_version=14,)# === 导出识别模型 ===rec_model=paddle.jit.load('PP-OCRv5_mobile_rec_infer/inference')rec_model.eval()paddle.onnx.export(rec_model,'models/PP-OCRv5_mobile_rec',input_spec=[paddle.static.InputSpec(shape=[1,3,-1,-1],dtype='float32',name='x')],opset_version=14,)

⚠️坑 1:paddle.jit.load()参数是文件前缀inference,不是目录!传目录会报KeyError: 'forward'
⚠️坑 2:export()的第二个参数不要带.onnx后缀,否则文件会变成.onnx.onnx

Step 5: ONNX 模型简化

importonnxfromonnxsimimportsimplifyfornamein['PP-OCRv5_mobile_det','PP-OCRv5_mobile_rec']:model=onnx.load(f'models/{name}.onnx')model_simp,check=simplify(model)onnx.save(model_simp,f'models/{name}_sim.onnx')print(f'{name}: simplified, check={check}')

简化后 OpenCV DNN 才能加载。

Step 6: 准备字符字典

从识别模型的inference.yml提取:

importyamlwithopen('PP-OCRv5_mobile_rec_infer/inference.yml')asf:config=yaml.safe_load(f)# 只保存 18383 个字符, blank 和 space 由 CTCLabelDecode 自动添加chars=config['PostProcess']['character_dict']withopen('models/ppocr_keys_v1.txt','w',encoding='utf-8')asf:forcinchars:f.write(c+'\n')

⚠️坑 3: 字典文件只需 18383 字符。CTCLabelDecode(use_space_char=True)会自动添加 blank + space → 18385 维,匹配模型输出。

Step 7: 编写推理脚本

完整的推理脚本包含以下关键部分:

7.1 系统 OpenCV 导入

pip 版opencv-python没有 CUDA!必须用系统自编译版:

importsys sys.path.insert(0,'/usr/local/lib/python3.10/dist-packages')importcv2

⚠️坑 4: 如果之前 pip install 过 opencv-python,会导致 NumPy 版本冲突。需要卸载 pip 版,降级 NumPy 到 1.x。

7.2 检测预处理
defdet_preprocess(img,target_size=960):"""PP-OCRv5 检测预处理: resize_long=960, NormalizeImage, padding到32倍数"""h,w=img.shape[:2]ratio=target_size/max(h,w)new_h,new_w=int(h*ratio),int(w*ratio)# Padding 到 32 的倍数 (DBNet 下采样 1/32)pad_h=(32-new_h%32)%32pad_w=(32-new_w%32)%32img=cv2.resize(img,(new_w,new_h))img=cv2.copyMakeBorder(img,0,pad_h,0,pad_w,cv2.BORDER_CONSTANT,value=(114,114,114))# 归一化: mean=[0.485,0.456,0.406] std=[0.229,0.224,0.225]img=img.astype(np.float32)/255.0mean=np.array([0.485,0.456,0.406],dtype=np.float32)std=np.array([0.229,0.224,0.225],dtype=np.float32)img=(img-mean)/std# HWC → CHW → BCHWimg=img.transpose(2,0,1)img=np.expand_dims(img,axis=0).astype(np.float32)returnimg,(new_h,new_w),(h,w),(pad_h,pad_w)
7.3 识别预处理
defrec_preprocess(img,target_shape=(48,320)):"""PP-OCRv5 识别预处理: RecResizeImg 内置归一化"""h,w=img.shape[:2]target_h,target_w=target_shape# 保持宽高比, 高度缩放到 48ratio=target_h/h new_w=int(w*ratio)ifnew_w>3200:# ⚠️ 不是 320! C++ 默认 max_imgW=3200new_w=3200img=cv2.resize(img,(new_w,target_h))# CHW + RecResizeImg 内置归一化: (x/255 - 0.5) / 0.5 → [-1, 1]img=img.astype(np.float32).transpose(2,0,1)img=img/255.0img=(img-0.5)/0.5# 宽度填充 (仅当 < target_w)ifnew_w<target_w:pad=np.zeros((3,target_h,target_w-new_w),dtype=np.float32)img=np.concatenate([img,pad],axis=2)img=np.expand_dims(img,axis=0).astype(np.float32)returnimg

⚠️坑 5: 识别模型max_imgW默认3200,不是 320!设置为 320 会导致长文本(如登机牌底部小字)被严重压缩,识别为空。
⚠️坑 6: 识别预处理中没有独立的NormalizeImage,但RecResizeImg内置(x/255 - 0.5) / 0.5的归一化操作。不要漏掉,也不要多做。

7.4 模型加载与 CUDA 后端
det_net=cv2.dnn.readNetFromONNX('models/PP-OCRv5_mobile_det_sim.onnx')rec_net=cv2.dnn.readNetFromONNX('models/PP-OCRv5_mobile_rec_sim.onnx')# 设置 CUDA 后端det_net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)det_net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)rec_net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)rec_net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
7.5 后处理(复用 PaddleOCR 原生类)

不用自己写!直接引用 PaddleOCR 源码中的类:

importimportlib.util# 绕过 __init__.py 避免 skimage 等可选依赖spec=importlib.util.spec_from_file_location('db_pp','PaddleOCR-main/ppocr/postprocess/db_postprocess.py')mod=importlib.util.module_from_spec(spec)spec.loader.exec_module(mod)DBPostProcess=mod.DBPostProcess# 同样加载 CTCLabelDecodespec2=importlib.util.spec_from_file_location('rec_pp','PaddleOCR-main/ppocr/postprocess/rec_postprocess.py')mod2=importlib.util.module_from_spec(spec2)spec2.loader.exec_module(mod2)CTCLabelDecode=mod2.CTCLabelDecode
7.6 中文可视化

⚠️坑 7:cv2.putText()的 Hershey 字体不支持中文,中文字会空白!

fromPILimportImage,ImageDraw,ImageFont# 系统自带 Noto Serif CJK 中文字体font=ImageFont.truetype('/usr/share/fonts/opentype/noto/NotoSerifCJK-Bold.ttc',16)# OpenCV BGR → PIL RGB → 绘制中文 → 转回 OpenCV BGRvis_rgb=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)vis_pil=Image.fromarray(vis_rgb)draw=ImageDraw.Draw(vis_pil)draw.text((x,y),text,font=font,fill=(0,255,0))vis=cv2.cvtColor(np.array(vis_pil),cv2.COLOR_RGB2BGR)

Step 8: 解决 ONNX 检测框碎片化

⚠️坑 8: ONNX 输出的概率图比 PaddlePaddle 原生稍碎片化,导致多出 ~8 个检测框。

添加相邻框合并算法:

def_merge_adjacent(boxes,scores):"""并查集合并水平紧邻框"""n=len(boxes)parent=list(range(n))deffind(x):whileparent[x]!=x:parent[x]=parent[parent[x]]x=parent[x]returnxdefunion(a,b):pa,pb=find(a),find(b)ifpa!=pb:parent[pb]=paforiinrange(n):bi=np.array(boxes[i])yi_min,yi_max=bi[:,1].min(),bi[:,1].max()xi_max=bi[:,0].max()hi=yi_max-yi_minforjinrange(i+1,n):bj=np.array(boxes[j])yj_min,yj_max=bj[:,1].min(),bj[:,1].max()xj_min=bj[:,0].min()hj=yj_max-yj_min# 垂直重叠 > 50%y_overlap=min(yi_max,yj_max)-max(yi_min,yj_min)ify_overlap<min(hi,hj)*0.5:continue# 水平间距 < 平均高度 × 2x_gap=xj_min-xi_max avg_h=(hi+hj)/2if0<x_gap<avg_h*2:union(i,j)# ... 按组合并 ...

效果:38 框 → 30 框(对齐 C++ 基准)。


六、性能测试

测试环境

  • 图片: 896×528, 30 个文本区域
  • 预热 2 次后取 20 次平均

结果

方案检测识别总耗时提速
C++ Paddle Inference~2s~3s~5.5s基准
OpenCV DNN CUDA (本项目)100ms900ms~1.0s

每框耗时分解

裁剪 (numpy): 0.1ms ( 0%) 预处理 (resize): 0.8ms ( 1%) setInput (拷贝): 0.1ms ( 0%) 模型推理 (GPU): 79.0ms (97%) ← 瓶颈 CTC解码 (numpy): 1.8ms ( 2%) ───────────────────────── 每框合计: 81.8ms

97% 的时间花在 GPU 推理上,CPU 处理几乎不占时间。

为什么不用 TensorRT?

试过了,在 Jetson Orin Nano 8GB 上:

  • optimization_level=2: 构建 2 分钟,但输出全零(计算错误)
  • optimization_level=3: 构建 3+ 分钟未完成
  • 识别模型(SVTR Transformer)预计 10+ 分钟

结论: TensorRT 在此设备上不实用。编译太慢,调试周期不可接受。


七、效果展示

识别结果(登机牌测试图)

[0.996] 登机牌 [0.981] BOARDING PASS [0.997] 票价FARE [0.997] 张祺伟 [0.989] ZHANGQIWEI [0.989] 姓名NAME [0.999] 福州 [0.995] FUZHOU [0.996] 航班FLIGHT [0.998] 登机口 [0.999] 日期DATE [1.000] 座位号 [0.959] 登机口于起飞前10分钟关闭 GATESCLOSE10MINUTESBEFORE DEPARTURE TIME

性能

$ python ocr_opencv_dnn.py-itest.png--benchmark性能测试(896x528,20): 检测(det_size=960): 100ms(±8ms)识别(单框):25.5ms 识别(30): 765ms 总耗时: ~900ms

八、完整代码

完整推理脚本(200+ 行)可在项目中找到:

gitclone<repo_url>cdocrsourcevenv_paddleocr/bin/activate python ocr_opencv_dnn.py-iyour_image.jpg

关键文件:

  • ocr_opencv_dnn.py— 主推理脚本
  • export_to_onnx.py— ONNX 导出脚本
  • models/— ONNX 模型 + 字典
  • PP-OCRv5_部署完整记录.md— 完整技术文档

九、踩坑速查表

#现象原因解决
1paddle.jit.load(dir)KeyError: 'forward'PIR 格式需文件前缀load(f'{dir}/inference')
2ONNX 文件名变xxx.onnx.onnxexport自动加后缀传入不带后缀的路径
3cv2.dnn.readNetFromONNXReshape错误OpenCV DNN 不支持某些算子onnxsim 简化
4系统import cv2_ARRAY_APINumPy 2.x vs 1.x 冲突pip install 'numpy<2'
5pip opencv-python 覆盖 CUDA 版pip 依赖自动安装pip uninstall opencv-python -y
6识别全部乱码字典维度不匹配CTCLabelDecode(use_space_char=True)
7图片上中文空白cv2.putText 不支持中文PIL + 中文字体
8长文本识别为空max_imgW 设为 320改为 3200
9检测框比基准多 8 个ONNX 概率图碎片化相邻框合并算法
10TensorRT 引擎输出全零optimization_level=2bug放弃 TRT, 用 OpenCV DNN

十、总结

在 Jetson Orin Nano (JetPack 6) 上部署 PaddleOCR,ONNX + OpenCV DNN是目前最实用的方案:

速度快: 比 C++ Paddle Inference 快 6 倍
精度好: 对齐原生 PaddlePaddle 输出
依赖少: 不需要 PaddlePaddle 运行时
纯 Python: 不需要编译 C++ 代码
可维护: 代码清晰, 后处理复用 PaddleOCR 原生类

❌ TensorRT 在此设备上不实用(编译太慢)
❌ 批处理不支持(模型架构限制)

完整文档和代码已开源,欢迎 Star ⭐


作者: CedarQ
日期: 2026-06-09
设备: NVIDIA Jetson Orin Nano Super
项目地址: [GitHub]

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 7:40:31

cursor初学实战项目——待办清单

项目简介 我们要开发的是一个有AI能力的待办事项列表&#xff0c;AI在其中的作用是帮助我们拆解任务。很多时候&#xff0c;一件事迟迟不做、非得拖延到最后一刻才开始&#xff0c;是因为我们不知道如何下手&#xff0c;行动力被不确定性阻断&#xff0c;通过AI把要做的事情拆…

作者头像 李华
网站建设 2026/6/10 7:40:28

在Apple Silicon Mac上运行Windows应用的终极指南:Whisky完全解析

在Apple Silicon Mac上运行Windows应用的终极指南&#xff1a;Whisky完全解析 【免费下载链接】Whisky A modern Wine wrapper for macOS built with SwiftUI 项目地址: https://gitcode.com/gh_mirrors/wh/Whisky 想在Apple Silicon Mac上无缝运行Windows软件和游戏吗&…

作者头像 李华
网站建设 2026/6/10 7:37:11

5分钟学会专业照片水印:semi-utils批量添加相机参数工具完全指南

5分钟学会专业照片水印&#xff1a;semi-utils批量添加相机参数工具完全指南 【免费下载链接】semi-utils 一个批量添加相机机型和拍摄参数的工具&#xff0c;后续「可能」添加其他功能。 项目地址: https://gitcode.com/gh_mirrors/se/semi-utils 还在为给照片添加水印…

作者头像 李华
网站建设 2026/6/10 7:36:14

Linux安装cluade

Nodecurl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt install -y nodejs检查版本&#xff1a;node -v npm -v安装gitsudo apt install -y git安装 Claude Code CLInpm install -g anthropic-ai/claude-code检查是否安装成功&#xff1a;claude --…

作者头像 李华
网站建设 2026/6/10 7:36:02

Windows系统文件dbgeng.dll文件丢失找不到问题解决

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华