pp-lcnet-doc-ori

9 downloads 2 likes on ModelScope updated

Model card

PP-LCNet DocOri —— 文档整图方向分类器 ONNX 版

模型简介

本模型源自 PaddleX / PaddleOCR 生态的 PP-LCNet_x1_0_doc_ori 文档方向分类器,已转换为 ONNX 格式,专为离线本地推理场景优化。

在 OCR 流水线中,扫描件、手机拍照、传真件等文档图像可能存在 90°、180° 甚至 270° 的旋转。如果直接将歪斜图像送入 OCR 引擎,文本识别准确率会大幅下降。本模型作为 OCR 前置步骤,自动判断文档图像的朝向并矫正为正向。

核心价值:在纯 CPU 环境下,以毫秒级延迟完成文档方向四分类(0°/90°/180°/270°),提升下游 OCR 识别质量,无需依赖 GPU 或 PaddlePaddle 运行时。


模型文件

文件 大小 说明
model.onnx ~6.5 MB ONNX 模型(包含图结构与权重)

单一文件,无外部数据依赖,直接加载即可推理。


模型架构

  • 基础模型:PP-LCNet_x1_0_doc_ori(百度 PaddleClas)
  • 网络结构:PP-LCNet(轻量级 CPU 友好 CNN)
  • 输入规格:
    • 尺寸:224 × 224 RGB 图像
    • 预处理:短边等比缩放至 256 → 中心裁剪 224 → ImageNet 归一化
    • 归一化参数:mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
  • 输出:float32[1, 4] 四类 softmax 分数
  • 类别映射:
class index 角度 含义
0 0° 正向(无需旋转)
1 90° 顺时针旋转 90°
2 180° 倒置
3 270° 逆时针旋转 90°(或顺时针 270°)

工作流程:

输入图像(任意朝向、任意分辨率)
    │
    ▼
┌─────────────────────────────────┐
│ 预处理                           │
│  resize(短边=256)                │
│  → centerCrop(224×224)          │
│  → normalize(ImageNet mean/std)  │
│  → NCHW float32[1,3,224,224]   │
└─────────────┬───────────────────┘
              ▼
┌─────────────────────────────────┐
│ ONNX 推理 (PP-LCNet)            │
│  输出: float32[1, 4]            │
└─────────────┬───────────────────┘
              ▼
┌─────────────────────────────────┐
│ argmax → 角度                    │
│  → 反向旋转矫正                  │
│  → 输出正向图像                  │
└─────────────────────────────────┘

快速开始

环境要求

  • onnxruntime >= 1.24.3
  • 图像预处理:numpy + PIL / opencv-python(Python),或 @napi-rs/canvas + sharp(Node.js)

Python 示例

import onnxruntime as ort
import numpy as np
from PIL import Image

# 1. 加载模型
model_path = "./pp-lcnet-doc-ori/model.onnx"
session = ort.InferenceSession(model_path)

# 2. 图像预处理
def preprocess(image_path: str) -> np.ndarray:
    """短边缩放 256 → 中心裁剪 224 → ImageNet 归一化 → NCHW"""
    img = Image.open(image_path).convert("RGB")
    w, h = img.size

    # 短边等比缩放
    scale = 256 / min(w, h)
    new_w, new_h = int(round(w * scale)), int(round(h * scale))
    img = img.resize((new_w, new_h), Image.BILINEAR)

    # 中心裁剪 224
    left = (new_w - 224) // 2
    top = (new_h - 224) // 2
    img = img.crop((left, top, left + 224, top + 224))

    # 归一化 (ImageNet)
    mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
    std  = np.array([0.229, 0.224, 0.225], dtype=np.float32)
    arr  = np.array(img, dtype=np.float32) / 255.0
    arr  = (arr - mean) / std

    # HWC → NCHW
    return np.expand_dims(arr.transpose(2, 0, 1), axis=0)

# 3. 推理
input_tensor = preprocess("document.jpg")
input_name  = session.get_inputs()[0].name
outputs     = session.run(None, {input_name: input_tensor})
scores      = outputs[0][0]  # [4]

# 4. argmax → 角度
ANGLE_MAP = {0: 0, 1: 90, 2: 180, 3: 270}
pred_class = int(np.argmax(scores))
angle = ANGLE_MAP[pred_class]

print(f"预测角度: {angle}°")
print(f"各类置信度: 0°={scores[0]:.3f}, 90°={scores[1]:.3f}, 180°={scores[2]:.3f}, 270°={scores[3]:.3f}")

TypeScript / Node.js 示例

import * as ort from "onnxruntime-node";
import { createCanvas, loadImage } from "@napi-rs/canvas";

// 归一化参数
const MEAN = [0.485, 0.456, 0.406];
const STD  = [0.229, 0.224, 0.225];
const INPUT_SIZE = 224;
const RESIZE_SHORT = 256;

async function preprocessImage(buffer: ArrayBuffer): Promise<Float32Array> {
  const img = await loadImage(Buffer.from(buffer));
  const w = img.width, h = img.height;

  // 短边缩放
  const scale = RESIZE_SHORT / Math.min(w, h);
  const newW = Math.round(w * scale), newH = Math.round(h * scale);
  const resizeCanvas = createCanvas(newW, newH);
  resizeCanvas.getContext("2d").drawImage(img, 0, 0, newW, newH);

  // 中心裁剪
  const sx = Math.max(0, Math.floor((newW - INPUT_SIZE) / 2));
  const sy = Math.max(0, Math.floor((newH - INPUT_SIZE) / 2));
  const cropCanvas = createCanvas(INPUT_SIZE, INPUT_SIZE);
  cropCanvas.getContext("2d").drawImage(resizeCanvas, sx, sy, INPUT_SIZE, INPUT_SIZE, 0, 0, INPUT_SIZE, INPUT_SIZE);

  const pixels = cropCanvas.getContext("2d").getImageData(0, 0, INPUT_SIZE, INPUT_SIZE).data;

  // NCHW + 归一化
  const plane = INPUT_SIZE * INPUT_SIZE;
  const out = new Float32Array(3 * plane);
  for (let i = 0; i < plane; i++) {
    out[i]            = (pixels[i * 4] / 255 - MEAN[0]) / STD[0];
    out[plane + i]    = (pixels[i * 4 + 1] / 255 - MEAN[1]) / STD[1];
    out[plane * 2 + i] = (pixels[i * 4 + 2] / 255 - MEAN[2]) / STD[2];
  }
  return out;
}

// 使用
const session = await ort.InferenceSession.create("./pp-lcnet-doc-ori/model.onnx");
const tensorData = await preprocessImage(imageBuffer);
const tensor = new ort.Tensor("float32", tensorData, [1, 3, 224, 224]);

const inputName  = session.inputNames[0];
const outputName = session.outputNames[0];
const outputs = await session.run({ [inputName]: tensor });
const scores = outputs[outputName].data as Float32Array;

const ANGLE_MAP = { 0: 0, 1: 90, 2: 180, 3: 270 };
const predClass = scores.indexOf(Math.max(...scores));
console.log(`预测角度: ${ANGLE_MAP[predClass]}°`);

应用场景

OCR 前置方向矫正

  • 扫描件处理:律所扫描的合同、判决书、证据材料可能存在各种朝向
  • 手机拍照:律师用手机拍摄的案卷材料常常歪斜
  • 传真件数字化:传真机输出的文档角度不可控
  • 批量文档自动化:大批量文档入库前自动统一朝向,无需人工逐页检查

降级策略

当模型文件未下载或推理失败时,系统自动跳过方向矫正,按 0°(正向)处理,不阻断下游 OCR 流程:

模型可用 → classify() → rotateToUpright() → 矫正图像 → OCR
模型不可用 → 跳过矫正 → 原图直接送 OCR

技术细节

网络结构

PP-LCNet(PP-Lightweight CPU Network)是百度提出的轻量级图像分类网络,设计目标为 CPU 友好:

  • 使用深度可分离卷积 + SE 注意力模块
  • 比 MobileNetV3 更快的推理速度
  • 专为 ARM / x86 CPU 优化
  • 在本项目中,单次推理耗时 < 50ms(桌面 CPU)

训练数据

原模型由 PaddleX 团队使用文档图像方向分类数据集训练,覆盖中英文文档、表格、票据、证件等多种文档类型。

与官方 Paddle 模型的关系

方面 官方 Paddle 模型 本模型 (ONNX)
运行时 PaddlePaddle ONNX Runtime
格式 inference.pdmodel model.onnx
依赖 paddlepaddle (~500MB) onnxruntime (~30MB)
GPU 支持 CPU only
推理速度 (CPU) ~80ms ~50ms
适合桌面应用 ❌ 运行时过大 ✅ 轻量集成

ONNX 转换说明

本模型从 PaddlePaddle 原生格式通过 paddle2onnx 工具转换而来:

  • paddle2onnx:2.1.0
  • paddlepaddle:2.6.2 / 3.0.0 nightly
  • 转换命令:paddle2onnx --model_dir ./ --model_filename inference.pdmodel --save_file model.onnx

已知问题:PaddlePaddle 3.0 部分模型包使用 inference.json 替代 inference.pdmodel,Windows 环境下 paddle2onnx 2.1.0 要求 paddlepaddle ≥ 3.0.0.dev,且可能因 MSVC 运行时缺失导致转换中断。


许可证

本模型遵循 Apache License 2.0,与 PaddleOCR / PaddleX 官方保持一致。


引用

如果你在研究中使用了本模型,请引用 PaddleOCR:

@misc{paddleocr,
  title={PaddleOCR: Awesome Multilingual OCR Toolkits Based on PaddlePaddle},
  author={PaddlePaddle Authors},
  howpublished={\url{https://github.com/PaddlePaddle/PaddleOCR}},
  year={2024}
}

@article{pp-lcnet,
  title={PP-LCNet: A Lightweight CPU Convolutional Neural Network},
  author={Cui, Cheng and Gao, Tingquan and Wei, Shengyu and Du, Yuning and Guo, Ruoyu and Dong, Shuilong and Lu, Bin and Zhou, Ying and Lv, Xueying and Liu, Qiwen and others},
  journal={arXiv preprint arXiv:2109.15099},
  year={2021}
}

致谢