PaddleOCRすげー!
最近OCRしたいと思っていて、いろいろグルグルと彷徨った結果、PaddleOCRが合ってるんじゃないか?と思って。
これをONNXにエクスポートして、C++で推論したい!
全然うまくいかないONNXエクスポート!
PaddleOCRはなんだか独自な感じで、paddle2onnxというパッケージを使うみたいなんですが、なんせ全然うまくいかない!
ONNXエクスポートできるまでかなり彷徨ったので、日本語の記録として残すものです。
私が欲しかったのはrecだけなのですが、detでもたぶん同じです。
環境!
Windows11
Python3.11
手順!
-
PaddleOCR を GitHub から clone!
git clone https://github.com/PaddlePaddle/PaddleOCR.git cd PaddleOCR
-
必要な依存をインストール!
pip install -r requirements.txt
-
エクスポートスクリプトを実行!
python tools/export_model.py \ -c .//configs//rec//PP-OCRv5//multi_language//en_PP-OCRv5_mobile_rec.yaml \ -o Global.save_inference_dir=./inference/rec
20260522追記:
よくみたら
下記に学習済みデータが置いてありました。これをダウンロードしてONNXにエクスポートするのが正解ぽいですhttps://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/OCR.html
-
Paddle2ONNXが正しく動くバージョンのパッケージをインストール!
pip install PaddlePaddle==3.1.1 PaddleX==3.3.13 Paddle2onnx==2.1.0
-
Paddle2ONNXでエクスポート!
paddle2onnx_export.pyimport paddle2onnx paddle2onnx.export( model_filename=".//inference//rec//inference.json", params_filename=".//inference//rec//inference.pdiparams", save_file=".//ppocr-v5-rec.onnx", opset_version=11, enable_onnx_checker=True )
-
実行時のログ!
途中、勝手にいろいろインストールされます。log(venv) PS D:\PaddleOCR> python test.py INFO: Could not find files for the given pattern(s). D:\PaddleOCR\venv\Lib\site-packages\paddle\utils\cpp_extension\extension_utils.py:717: UserWarning: No ccache found. Please be aware that recompiling all source files may be required. You can download and install ccache from: https://github.com/ccache/ccache/blob/master/doc/INSTALL.md warnings.warn(warning_message) [Paddle2ONNX] Start parsing the Paddle model file... [Paddle2ONNX] Use opset_version = 11 for ONNX export. [Paddle2ONNX] PaddlePaddle model is exported as ONNX format now. 2026-05-21 21:27:04 [INFO] Try to perform constant folding on the ONNX model with Polygraphy. [W] 'colored' module is not installed, will not use colors when logging. To enable colors, please install the 'colored' module: python3 -m pip install colored [I] Module: 'onnxruntime' is required, but not installed. Attempting to install now. [I] Running installation command: D:\PaddleOCR\venv\Scripts\python.exe -m pip install onnxruntime>=1.10.0 [I] Folding Constants | Pass 1 [I] Module: 'onnx_graphsurgeon' is required, but not installed. Attempting to install now. [I] Running installation command: D:\PaddleOCR\venv\Scripts\python.exe -m pip install onnx_graphsurgeon>=0.3.27 --extra-index-url=https://pypi.ngc.nvidia.com [I] Module: 'sympy' is required, but not installed. Attempting to install now. [I] Running installation command: D:\PaddleOCR\venv\Scripts\python.exe -m pip install sympy [I] Module: 'onnxruntime.tools.symbolic_shape_infer' is required, but not installed. Attempting to install now. [I] Running installation command: D:\PaddleOCR\venv\Scripts\python.exe -m pip install onnxruntime>=1.10.0 [I] Folding Constants | Pass 1 [I] Total Nodes | Original: 1051, After Folding: 577 | 474 Nodes Folded [I] Folding Constants | Pass 2 [I] Total Nodes | Original: 577, After Folding: 577 | 0 Nodes Folded 2026-05-21 21:27:45 [INFO] ONNX model saved in .//ppocr-v5-rec.onnx.
Python ONNXRUNTIMEで推論!
主にGeminiに作ってもらいました。
辞書ファイルはgit cloneで落としてきた公式リポジトリの辞書を指定しています。
import cv2
import numpy as np
import onnxruntime as ort
import os
# =====================================================================
# 1. 確定:GitHubリポジトリ内の公式英語辞書ファイルから文字マップを自動生成
# =====================================================================
# クローンした PaddleOCR のルートディレクトリにある公式辞書のパス
dict_path = "./ppocr/utils/dict/ppocrv5_en_dict.txt"
if not os.path.exists(dict_path):
raise FileNotFoundError(
f"公式辞書ファイルが見つかりません。現在の実行フォルダ(D:\\PaddleOCR)に "
f"'{dict_path}' が存在するか確認してください。"
)
# 0番目にCTCの「blank」を配置
character_list = ["blank"]
# 公式辞書ファイル(en_dict.txt)を読み込んでリストに追加
with open(dict_path, "r", encoding="utf-8") as f:
for line in f:
# 改行コードを取り除き、1文字ずつリストに格納
char = line.strip("\r\n")
character_list.append(char)
# 補足:PaddleOCR公式のPP-OCRv5/v3は、スペース文字( )をコード側で追加する仕様です
# リストにまだスペースが含まれていない場合は末尾に追加して全97クラスにします
if " " not in character_list:
character_list.append(" ")
print(f"公式辞書の読み込みに成功しました。総文字数: {len(character_list)}")
# =====================================================================
# 2. 公式と100%一致させた前処理 (リサイズ + ゼロパディング)
# =====================================================================
def preprocess_image(img_path, target_shape=(3, 48, 320)):
img = cv2.imread(img_path)
if img is None:
raise FileNotFoundError(f"画像が見つかりません: {img_path}")
rec_c, rec_h, rec_w = target_shape
img_h, img_w, _ = img.shape
# アスペクト比を維持したリサイズ幅の計算
ratio = img_w / float(img_h)
resized_w = int(np.ceil(rec_h * ratio))
resized_w = min(resized_w, rec_w) # 最大320
resized_img = cv2.resize(img, (resized_w, rec_h), interpolation=cv2.INTER_LINEAR)
# 浮動小数点変換と正規化 ((x / 255) - 0.5) / 0.5 -> x / 127.5 - 1.0
norm_img = resized_img.astype(np.float32) / 127.5 - 1.0
norm_img = norm_img.transpose((2, 0, 1)) # HWC から CHW
# 【最重要】右側を確実に0で埋めるパディング処理
padding_img = np.zeros((rec_c, rec_h, rec_w), dtype=np.float32)
padding_img[:, :, :resized_w] = norm_img
return np.expand_dims(padding_img, axis=0)
# =====================================================================
# 3. 修正:スペースを消さない CTC Greedy デコーダー
# =====================================================================
def ctc_decode(preds, character_list):
if len(preds.shape) == 3:
preds = preds[0] # バッチ次元を削除 -> (40, 97)
preds_idx = np.argmax(preds, axis=-1)
preds_prob = np.max(preds, axis=-1)
char_res = []
conf_res = []
# 連続重複チェック用の初期値(存在しないインデックスにするため -1)
last_idx = -1
for idx, prob in zip(preds_idx, preds_prob):
# 1. 0番目(blankトークン)はCTCのルールに従ってスキップ
if idx == 0:
last_idx = idx
continue
# 2. 連続する同じインデックスはスキップ(CTCのルール)
if idx == last_idx:
continue
# 3. 有効なインデックスであれば文字リストから復元
if idx < len(character_list):
char = character_list[idx]
char_res.append(char)
conf_res.append(prob)
last_idx = idx
text = "".join(char_res)
score = np.mean(conf_res) if conf_res else 0.0
return text, score
# =====================================================================
# 4. メイン処理 (CPU推論を強制)
# =====================================================================
if __name__ == "__main__":
model_path = "./c_ppocr-v5-rec_sim.onnx"
image_path = "./test_name_CalocenRieti.png"
# 【重要】providersを「CPUExecutionProvider」のみに絞り、DirectMLの競合を回避します
session = ort.InferenceSession(model_path, providers=['CPUExecutionProvider'])
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
# 実行
input_tensor = preprocess_image(image_path)
outputs = session.run([output_name], {input_name: input_tensor})
predictions = outputs[0]
# 既存の推論とデコード
recognized_text, confidence = ctc_decode(predictions, character_list)
print("\n" + "="*40)
print(f" 認識結果 : {recognized_text}")
print(f" 信頼度スコア: {confidence:.4f}")
print("="*40 + "\n")
(venv) PS D:\PaddleOCR> python ppocr_rec_infer.py
公式辞書の読み込みに成功しました。総文字数: 438
========================================
認識結果 : Calocen Rieti
信頼度スコア: 0.9947
========================================
参考にしたもの!
