feat(model): 新增 ModelLoader 链式构建 API 及 ORT GPU/Tract 多线程配置

- 在 ddddocr-core 中定义 ModelBuilder Trait 及其错误类型
- ddddocr-ort 支持 use_gpu、device_id 及 num_threads 链式配置与 CUDA 硬件加速
- ddddocr-tract 基于 multithread-mm 特性支持 CPU 线程数控制
- 支持基于 tract-linalg 配置推理线程数,显式引入 tract-linalg 的 multithread-mm 特性,控制 GEMM 算子并发
- 优化线程池加载策略,适配 Tokio 异步及 CLI 等多场景
This commit is contained in:
2026-07-27 20:22:24 +08:00
parent 44dae08221
commit 7d159c5702
28 changed files with 1583 additions and 70 deletions

View File

@@ -1,24 +1,29 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"ddddocr-core", "ddddocr-core", "ddddocr-ort",
"ddddocr-tract", "ddddocr-tract",
] ]
[workspace.package] [workspace.package]
version = "0.1.0" version = "0.2.0"
edition = "2024" edition = "2024"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[workspace.dependencies] [workspace.dependencies]
tract-onnx = { version = "0.21.10" } tract-onnx = "0.23.4"
anyhow = "1.0.102" tract-linalg = { version = "0.23.4",features = ["multithread-mm"]}
ort = "2.0.0-rc.12"
ndarray = "0.17.2"
image = "0.25.10" image = "0.25.10"
base64 = "0.22.1" base64 = "0.22.1"
imageproc = { version = "0.26.2", default-features = true } imageproc = { version = "0.26.2", default-features = true }
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.150" serde_json = "1.0.150"
ndarray = "0.16.1"
anyhow = "1.0.102"
thiserror = "1.0" # 刚好可以开始接入你需要的标准库错误处理 thiserror = "1.0" # 刚好可以开始接入你需要的标准库错误处理
tracing = "0.1.44" # 埋入日志打点(后续需要继续优化,现在只是简单尝试) tracing = "0.1.44" # 埋入日志打点(后续需要继续优化,现在只是简单尝试)

View File

@@ -5,12 +5,12 @@ edition = { workspace = true }
license = { workspace = true } license = { workspace = true }
[dependencies] [dependencies]
image = "0.25.10"
base64 = "0.22.1"
imageproc = { version = "0.26.2", default-features = true }
serde = { workspace = true }
serde_json = "1.0.150"
ndarray = { workspace = true } # 继承自工作空间 ndarray = { workspace = true } # 继承自工作空间
base64 = { workspace = true }
image = { workspace = true }
imageproc = { workspace = true }
thiserror = { workspace = true } # 刚好可以开始接入你需要的标准库错误处理 thiserror = { workspace = true } # 刚好可以开始接入你需要的标准库错误处理
tracing={workspace = true} tracing={workspace = true}
#serde = { workspace = true, features = ["derive"] }

View File

@@ -1,16 +1,16 @@
pub mod det; mod det;
pub mod error; pub mod error;
pub mod ocr; mod ocr;
mod slide; mod slide;
pub mod utils; pub mod utils;
use crate::error::{Result, TensorError}; use error::{Result, TensorError};
use std::path::Path;
pub use crate::slide::{SlideResult, Slider};
pub use crate::det::{DetBuilder, DetectionResult, Detector}; pub use crate::det::{DetBuilder, DetectionResult, Detector};
pub use crate::ocr::{Ocr, OcrBuilder, OcrResult}; pub use crate::ocr::{Charset, ModelMetadata, Normalization, Ocr, OcrBuilder, OcrResult, Resize};
pub use crate::ocr::{ModelMetadata,Normalization}; pub use crate::slide::{SlideResult, Slider};
pub use ocr::Charset;
// DetSession // DetSession
pub enum OcrOutput { pub enum OcrOutput {
@@ -27,7 +27,7 @@ pub enum DetOutput {
pub trait InferenceEngine { pub trait InferenceEngine {
/// 关联类型:具体的 Session 需要声明自己到底产出什么枚举 /// 关联类型:具体的 Session 需要声明自己到底产出什么枚举
type Output; type Output;
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output,TensorError>; fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError>;
} }
pub trait OcrEngine: InferenceEngine<Output = OcrOutput> { pub trait OcrEngine: InferenceEngine<Output = OcrOutput> {
@@ -35,3 +35,11 @@ pub trait OcrEngine: InferenceEngine<Output = OcrOutput> {
} }
pub trait DetEngine: InferenceEngine<Output = DetOutput> {} pub trait DetEngine: InferenceEngine<Output = DetOutput> {}
pub trait ModelBuilder {
type Session;
type Error;
fn model_for_path<P: AsRef<Path>>(&self,model_path: P) -> Result<Self::Session, Self::Error>;
fn model_from_bytes(&self,model_bytes: &[u8]) -> Result<Self::Session, Self::Error>;
}

View File

@@ -5,7 +5,6 @@ use crate::ocr::color_filter::{HsvRange, apply_to_image};
use crate::utils::image_convert::png_rgba_white_preprocess; use crate::utils::image_convert::png_rgba_white_preprocess;
use crate::utils::image_processor::{convert_to_grayscale, resize_image}; use crate::utils::image_processor::{convert_to_grayscale, resize_image};
use image::DynamicImage; use image::DynamicImage;
use serde::Serialize;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
// use tract_onnx::prelude::tract_ndarray::{ Ix2, s}; // use tract_onnx::prelude::tract_ndarray::{ Ix2, s};
@@ -19,7 +18,7 @@ use ndarray::ArrayView2;
use crate::error::{ImagePreprocessError, Result, TensorError}; use crate::error::{ImagePreprocessError, Result, TensorError};
use crate::{OcrEngine, OcrOutput}; use crate::{OcrEngine, OcrOutput};
use tracing::{ warn}; use tracing::{ warn};
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone)]
pub enum OcrResult { pub enum OcrResult {
/// 纯文本分支(对应 probability = false /// 纯文本分支(对应 probability = false
Text(String), Text(String),

View File

@@ -1,8 +1,8 @@
pub mod image_convert; pub mod image_convert;
mod image_helper;
pub mod image_processor; pub mod image_processor;
mod tensor_transform; mod tensor_transform;
mod image_helper;
// 对外统一暴露干净的 API 语义层 // 对外统一暴露干净的 API 语义层
pub use image_convert::ColorMode;
pub use tensor_transform::normalize_ocr_logits; pub use tensor_transform::normalize_ocr_logits;
pub use image_convert::{ColorMode};

23
ddddocr-ort/Cargo.toml Normal file
View File

@@ -0,0 +1,23 @@
[package]
name = "ddddocr-ort"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
ddddocr-core = { path = "../ddddocr-core" } # 引入兄弟库
ort = { workspace = true ,features = ["cuda"]}
ndarray = { workspace = true } # 继承自工作空间
image = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
anyhow = { workspace = true }
thiserror = { workspace = true } # 刚好可以开始接入你需要的标准库错误处理
[features]
# 1. 声明 cuda feature
# 2. 当开启 ddddocr-ort 的 cuda feature 时,自动开启底层 ort 库的 cuda 支持(如果 ort 库支持的话)
cuda = ["ort/cuda"]

1
ddddocr-ort/src/det.rs Normal file
View File

@@ -0,0 +1 @@
pub mod session;

View File

@@ -0,0 +1,67 @@
use crate::types::Session;
use ddddocr_core::error::{Result, TensorError};
use ddddocr_core::{DetEngine, DetOutput, InferenceEngine};
use ndarray::Ix3;
use ort::inputs;
use ort::value::TensorRef;
// use tract_onnx::prelude::{tvec, IntoTensor, Tensor};
#[derive(Debug)]
pub struct DetSession {
pub session: Session,
}
impl DetSession {
pub fn new(session: Session) -> Self {
Self { session }
}
}
impl InferenceEngine for DetSession {
type Output = DetOutput; // 明确绑定 OCR 小枚举
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
// tract 的 run 会返回一个 Vec<TValue>,我们通常只需要第一个输出
// let result = self.ocr.run(tvec!(tensor.into()))?;
let mut session_guard = self
.session
.lock()
.map_err(|_| TensorError::Engine("获取 Session 锁失败 (Poisoned)".to_string()))?;
let result = session_guard
.run(inputs![TensorRef::from_array_view(&input_array).map_err(
|e| TensorError::Engine(format!("构建输入失败: {e}"))
)?])
.map_err(|e| TensorError::Engine(format!("执行模型推理失败: {e}")))?;
// .context("执行模型推理失败")?;
println!("模型输出原始数据: {:?}", result);
// Ok(result.swap_remove(0).into_tensor())
let raw_value = &result[0];
// raw_tensor.into_plain_array()?
let (shape_ref, slice) = raw_value.try_extract_tensor::<f32>().map_err(|_| {
TensorError::Engine("Tract 实体张量无法转换为 ndarray::ArrayD".to_string())
})?;
// 提前利用克隆(Clone)备份好当前未转维度前的真实 shape (Vec<usize>)
let shape_vec: Vec<usize> =
shape_ref.to_vec().iter().map(|v| *v as usize).collect();
let shape_vec_slice = shape_vec.as_slice();
let view = ndarray::ArrayViewD::from_shape(shape_vec_slice, slice)
.map_err(|_| TensorError::Engine("构建 ndarray ArrayViewD 失败".to_string()))?;
let array3 = view.to_owned().into_dimensionality::<Ix3>().map_err(|_| {
TensorError::DimensionMismatch {
expected: "3D 检测矩阵 [Batch, Box_Count, Box_Attributes]".to_string(),
actual: shape_vec, // 优雅降维失败时动态捕获
}
})?;
Ok(DetOutput::Detection(array3))
// 在引擎内部消化掉 DatumType 强耦合
}
}
impl DetEngine for DetSession {}

9
ddddocr-ort/src/lib.rs Normal file
View File

@@ -0,0 +1,9 @@
mod det;
pub mod loader;
mod ocr;
mod types;
pub use ddddocr_core::{SlideResult, Slider,OcrBuilder};
pub use det::session::DetSession;
pub use ocr::session::OcrSession;

View File

@@ -0,0 +1,7 @@
mod error;
mod metadata;
mod model;
pub use error::{Error, ParseError, Result};
pub use metadata::{ModelMetadataDto, NormalizationDto, TractModelMetadata};
pub use model::OrtModelLoader;

View File

@@ -0,0 +1,66 @@
use ort::Error as OrtError;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("builder构建失败")]
Build(#[from] BuildError),
/// 解析 ONNX 模型/路径失败(如文件损坏、算子不支持、路径非法)
#[error("解析 ONNX 模型结构失败: {0}")]
ModelParse(#[from] ParseError),
/// 模型计算图优化失败(如常量折叠、形状推导失败)
#[error("优化 Tract 模型图失败: {0}")]
OptimizationFailed(#[source] OrtError),
/// 构建可执行 Session 失败(如输入输出 Tensor 类型/形状未确定)
#[error("构建可运行 Tract 实例失败: {0}")]
RunnableBuildFailed(#[source] OrtError),
/// JSON 反序列化失败(自动透传 serde_json 报错)
#[error("模型 Metadata JSON 解析失败: {0}")]
JsonParse(#[from] serde_json::Error),
/// 字节流非合法 UTF-8 编码(自动透传 Utf8Error
#[error("Metadata 字节流不是合法的 UTF-8 编码: {0}")]
InvalidUtf8(#[from] std::str::Utf8Error),
#[error("模型元数据解析失败: {0}")]
MetadataParse(String),
/// 承载任何第三方扩展、解密、特定预处理插件在执行时产生的自定义错误
#[error("{0}: {1}")]
Other(String, #[source] Box<dyn std::error::Error + Send + Sync>),
}
impl Error {
/// 方便将任何第三方 Error 包装为 Error::Other
pub fn new<E>(msg: impl Into<String>, err: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Self::Other(msg.into(), err.into())
}
}
#[derive(thiserror::Error, Debug)]
pub enum ParseError {
/// 策略 A从文件路径加载失败附带路径上下文信息方便排查是找不到文件还是格式不对
#[error("从路径 '{0}' 加载 ONNX 模型失败: {1}")]
Path(String, #[source] OrtError),
/// 策略 B从内存字节流加载失败如 include_bytes! 传入的字节流损坏)
#[error("从内存字节流解析 ONNX 模型失败: {0}")]
Bytes(#[source] OrtError),
}
#[derive(thiserror::Error, Debug)]
pub enum BuildError{
#[error("builder构建失败")]
BuildFailed(#[from] OrtError),
#[error("builder构建失败")]
Threads(String),
#[error("builder构建失败")]
EnabledCudaFailed(String),
#[error("builder构建失败")]
NotEnabledCuda(String)
}

View File

@@ -0,0 +1,93 @@
use crate::loader::error::{Error, Result};
use ddddocr_core::ModelMetadata;
use ddddocr_core::Resize;
use ddddocr_core::{Charset, Normalization};
use serde::Deserialize;
use std::borrow::Cow;
#[derive(Deserialize)]
#[serde(rename_all = "snake_case")] // 支持 json 中写 "zero_to_one" 或 "minus_one_to_one"
pub enum NormalizationDto {
/// 映射到 [0.0, 1.0] -> pixel / 255.0
ZeroToOne,
/// 映射到 [-1.0, 1.0] -> (pixel / 255.0 - 0.5) / 0.5
MinusOneToOne,
}
impl From<NormalizationDto> for Normalization {
fn from(dto: NormalizationDto) -> Self {
match dto {
NormalizationDto::ZeroToOne => Normalization::ZeroToOne,
NormalizationDto::MinusOneToOne => Normalization::MinusOneToOne,
}
}
}
/// 仅用于反序列化 JSON 的中间临时结构体DTO
#[derive(Deserialize)]
pub struct ModelMetadataDto {
charset: Vec<String>,
word: bool,
#[serde(alias = "image")]
resize: Vec<i32>,
channel: u8,
/// 新增:允许在配置文件中指定归一化策略。
/// 使用 serde(default) 可以在不配置时提供一个默认值(比如默认 ZeroToOne
#[serde(default = "default_normalization")]
normalization: NormalizationDto,
}
fn default_normalization() -> NormalizationDto {
NormalizationDto::ZeroToOne
}
/// Tract 专属扩展trait 或 工具函数
pub trait TractModelMetadata: Sized {
fn from_json_str(json_str: &str) -> Result<Self>;
/// 机制 2从内存字节流加载极大地方便 include_bytes! 或网络下载)
fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
let json_str = std::str::from_utf8(bytes)?;
Self::from_json_str(json_str)
}
}
impl TractModelMetadata for ModelMetadata {
// --- 优雅的工厂模式构造器 ---
fn from_json_str(json_str: &str) -> Result<ModelMetadata> {
let dto: ModelMetadataDto = serde_json::from_str(json_str)?;
// 1. 将 DTO 的字符串数组转化为强类型的 Charset
let tokens: Vec<Cow<'static, str>> =
dto.charset.into_iter().map(|s| Cow::Owned(s)).collect();
let charset = Charset::new(tokens);
// 2. 解析 resize 策略(重现 Python 的复杂条件判断)
if dto.resize.len() != 2 {
return Err(Error::MetadataParse(
"'resize (or image)' 字段必须是包含两个元素的数组,例如 [-1, 64]".to_string(),
));
}
let r0 = dto.resize[0];
let r1 = dto.resize[1];
let resize = if r0 == -1 {
if dto.word {
// 如果 word 为 true且包含 -1Python 里是 resize 为 (r1, r1) 的正方形
Resize::Square(r1 as u32)
} else {
// 如果 word 为 false且包含 -1Python 里是高度固定为 r1宽度按原图比例缩放
Resize::DynamicWidth(r1 as u32)
}
} else {
// 正常的固定宽高
Resize::Fixed(r0 as u32, r1 as u32)
};
Ok(ModelMetadata::new(
charset,
dto.word,
resize,
dto.channel,
dto.normalization.into(),
))
}
}

View File

@@ -0,0 +1,114 @@
use crate::loader::Error;
use crate::loader::error::{BuildError, ParseError, Result};
use crate::types::Session;
use ddddocr_core::ModelBuilder;
use ort::session::Session as OrtSession;
use ort::session::builder::SessionBuilder;
use std::sync::{Arc, Mutex};
pub struct OrtModelLoader;
impl OrtModelLoader {
/// 获取针对 ORT 后端的链式构建器
pub fn builder() -> OrtModelBuilder {
OrtModelBuilder::default()
}
}
/// ORT 专用的链式构建器
#[derive(Debug, Clone)]
pub struct OrtModelBuilder {
use_gpu: bool,
device_id: i32,
intra_threads: Option<usize>,
}
impl Default for OrtModelBuilder {
fn default() -> Self {
Self {
use_gpu: false,
device_id: 0,
intra_threads: None,
}
}
}
impl OrtModelBuilder {
/// 开启或关闭 GPU 加速
pub fn use_gpu(mut self, enable: bool) -> Self {
self.use_gpu = enable;
self
}
/// 指定 GPU 设备 ID
pub fn device_id(mut self, id: i32) -> Self {
self.device_id = id;
self
}
pub fn num_threads(mut self, threads: usize) -> Self {
self.intra_threads = Some(threads);
self
}
/// 内部辅助方法:根据当前的配置构建 ORT 底层的 SessionBuilder
fn create_session_builder(&self) -> Result<SessionBuilder> {
let mut builder = OrtSession::builder().map_err(|e| BuildError::BuildFailed(e))?;
// 如果用户显式设置了线程数,则配置给 ORT
if let Some(threads) = self.intra_threads {
builder = builder
.with_intra_threads(threads)
.map_err(|e| BuildError::Threads(format!("设置线程数失败: {e}")))?;
}
if self.use_gpu {
// 根据 ort 库版本配置 CUDA 执行提供者 (Execution Provider)
#[cfg(feature = "cuda")]
{
use ort::ep::CUDAExecutionProvider;
let cuda_ep = CUDAExecutionProvider::default().with_device_id(self.device_id);
builder = builder
.with_execution_providers([cuda_ep.build()])
.map_err(|e| {
BuildError::EnabledCudaFailed(format!("配置 CUDA 硬件加速失败: {e}"))
})?
}
#[cfg(not(feature = "cuda"))]
{
// 如果用户明确开启了 GPU但 Feature 没编译进去,明确抛错提醒
return Err(BuildError::NotEnabledCuda(
"未启用 CUDA 支持:请在 Cargo.toml 中为 ddddocr-ort 开启 `cuda` feature"
.to_string(),
))?;
}
}
Ok(builder)
}
}
impl ModelBuilder for OrtModelBuilder {
type Session = Session;
type Error = Error;
fn model_for_path<P>(&self, model_path: P) -> Result<Session>
where
P: AsRef<std::path::Path>,
{
let path_ref = model_path.as_ref();
let mut builder = self.create_session_builder()?;
// Session::builder() 会返回 Result<SessionBuilder, ort::Error>
let session = builder
.commit_from_file(path_ref)
.map_err(|e| ParseError::Path(path_ref.display().to_string(), e))?;
Ok(Arc::new(Mutex::new(session))) // 这里的session需要包装下
}
/// 策略 B从内存字节流加载模型配合 include_bytes! 使用)
fn model_from_bytes(&self, model_bytes: &[u8]) -> Result<Session> {
let mut builder = self.create_session_builder()?;
let session = builder
.commit_from_memory(model_bytes)
.map_err(ParseError::Bytes)?;
Ok(Arc::new(Mutex::new(session)))
}
}

1
ddddocr-ort/src/ocr.rs Normal file
View File

@@ -0,0 +1 @@
pub mod session;

View File

@@ -0,0 +1,225 @@
use crate::types::Session;
use ddddocr_core::ModelMetadata;
use ddddocr_core::error::{DdddError, Result, TensorError};
use ddddocr_core::utils::normalize_ocr_logits;
use ddddocr_core::{InferenceEngine, OcrEngine, OcrOutput};
use ort::inputs;
use ort::value::{TensorElementType, TensorRef};
use std::sync::Mutex;
// 引入核心层的统一错误类型
/// 明确命名为 AxisDim代表模型某一个轴的维度特征
#[derive(Clone, PartialEq, Eq)]
pub enum AxisDim {
/// 静态固定维度(如通道数固定为 1高度固定为 64
Static(usize),
/// 动态符号维度(如宽度是动态的 "image_width"
Dynamic(String),
}
impl AxisDim {
/// 便捷方法:判断是否为动态维度
pub fn is_dynamic(&self) -> bool {
matches!(self, AxisDim::Dynamic(_))
}
}
/// 自定义 Debug 格式化输出,彻底融化套娃外壳,保证日志干净漂亮
impl std::fmt::Debug for AxisDim {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AxisDim::Static(size) => write!(f, "{}", size),
AxisDim::Dynamic(expr) => write!(f, "Dynamic(\"{}\")", expr),
}
}
}
/// 模拟 Python 的 input_info 和 output_info 结构
#[derive(Debug, Clone)]
pub struct TensorInfo {
pub name: String,
pub shape: Vec<AxisDim>, // 既包含 Fixed 静态维度,也包含 Dynamic 动态符号
pub data_type: TensorElementType, // 对应 Python 的 type
}
/// 最终返回的模型完整信息
#[derive(Debug, Clone)]
pub struct ModelInfo {
pub inputs: Vec<TensorInfo>,
pub outputs: Vec<TensorInfo>,
/// 硬件执行提供者(采用 Option 兼容不同底层的推理引擎)
pub providers: Option<Vec<String>>,
}
pub struct OcrSession {
pub session: Session,
pub model_metadata: ModelMetadata,
}
impl OcrSession {
pub fn new(session: Session, model_metadata: ModelMetadata) -> Self {
Self {
session,
model_metadata,
}
}
}
impl OcrEngine for OcrSession {
fn metadata(&self) -> &ModelMetadata {
&self.model_metadata
}
}
impl InferenceEngine for OcrSession {
type Output = OcrOutput;
/// 对应 Python 的 _inference
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
// tract 的 run 会返回一个 Vec<TValue>,我们通常只需要第一个输出
// let result = self.ocr.run(tvec!(tensor.into()))?;
// let tensor = Tensor::from(input_array);
let mut session_guard = self
.session
.lock()
.map_err(|_| TensorError::Engine("获取 Session 锁失败 (Poisoned)".to_string()))?;
// // 2. 获取输入节点名称
// let input_name = session_guard
// .inputs()
// .first()
// .map(|i| i.name())
// .unwrap_or("input");
//
// // 3. 在 session_guard (&mut Session) 上调用 run
// let outputs = session_guard
// .run(inputs![TensorRef::from_array_view(&input_array).map_err(|e| TensorError::Engine(format!("构建输入失败: {e}")))? )
// .map_err(|e| TensorError::Engine(format!("执行模型推理失败: {e}")))?;
let result = session_guard
.run(inputs![TensorRef::from_array_view(&input_array).map_err(
|e| TensorError::Engine(format!("构建输入失败: {e}"))
)?])
.map_err(|e| TensorError::Engine(format!("执行模型推理失败: {e}")))?;
// .context("执行模型推理失败")?;
println!("模型输出原始数据: {:?}", result);
// Ok(result.swap_remove(0).into_tensor())
let raw_value = &result[0];
// let dtype = raw_tensor
// .dtype();
// .map_err(|e| TensorError::Engine(format!("无法读取输出数据类型: {e}")))?;
// 在引擎内部消化掉 DatumType 强耦合
match raw_value.dtype().tensor_type().unwrap() {
TensorElementType::Int64 => {
let (array_d, slice) = raw_value
.try_extract_tensor::<i64>()
.map_err(|_| TensorError::Engine("Tract 无法获取 i64 内存视图".to_string()))?;
// .context("Tract 无法获取 i64 内存视图")?;
// 🌟 提前提取真实维度
let actual_shape = array_d
.to_vec()
.iter()
.map(|v| *v as usize)
.collect::<Vec<usize>>();
let view = ndarray::ArrayViewD::from_shape(actual_shape.as_slice(), slice)
.map_err(|_| TensorError::Engine("构建 ndarray ArrayViewD 失败".to_string()))?;
// 转成标准的 Array1 传给 core
let array1 = view
.to_owned()
.into_dimensionality::<ndarray::Ix1>()
.map_err(|_| TensorError::DimensionMismatch {
expected: "1D 字符索引静态矩阵".to_string(),
actual: actual_shape,
})?;
Ok(OcrOutput::Indices(array1))
}
TensorElementType::Float32 => {
let shape = raw_value.shape();
println!("模型输出shape数据: {:?}", shape);
// raw_tensor.to_plain_array_view()
let (shape_ref, slice) = raw_value
.try_extract_tensor::<f32>()
.map_err(|_| TensorError::Engine("Tract 无法获取 f32 内存视图".to_string()))?;
// 1. 极其纯粹的、无拷贝的多维 Shape 压扁清洗
let shape_vec: Vec<usize> =
shape_ref.to_vec().iter().map(|v| *v as usize).collect();
let shape_vec_slice = shape_vec.as_slice();
let view = ndarray::ArrayViewD::from_shape(shape_vec_slice, slice)
.map_err(|_| TensorError::Engine("构建 ndarray ArrayViewD 失败".to_string()))?;
normalize_ocr_logits(view, shape_vec_slice)
}
_ => Err(
// anyhow::anyhow!("不支持的模型输出数据类型: {:?}",raw_tensor.datum_type())
TensorError::UnknownOutputFormat,
),
}
}
}
// impl OcrSession {
// /// 获取模型输入的节点信息列表
// pub fn input_info(&self) -> Result<Vec<TensorInfo>> {
// let model = self.session.model();
// let outlets = model.input_outlets().map_err(DdddError::new)?;
// self.resolve_tensors(model, outlets)
// }
//
// /// 获取模型输出的节点信息列表
// pub fn output_info(&self) -> Result<Vec<TensorInfo>> {
// let model = self.session.model();
// let outlets = model.output_outlets().map_err(DdddError::new)?;
// self.resolve_tensors(model, outlets)
// }
//
// /// 获取模型详细元数据信息(对标 Python ddddocr 的 get_model_info
// /// 完美包容 [1, 1, 64, image_width] 这样的变长图像模型
// /// 获取模型详细元数据信息(代码更紧凑、优雅)
// pub fn model_info(&self) -> Result<ModelInfo> {
// Ok(ModelInfo {
// inputs: self.input_info()?,
// outputs: self.output_info()?,
// providers: None,
// })
// }
//
// /// 提取出来的公共转换逻辑:将一组 OutletId 解析为 TensorInfo 列表
// fn resolve_tensors(&self, model: &TypedModel, outlets: &[OutletId]) -> Result<Vec<TensorInfo>> {
// outlets
// .iter()
// .map(|&outlet_id| {
// let fact = model.outlet_fact(outlet_id).map_err(DdddError::new)?;
// // .map_err(|e| {
// // DdddError::InternalError(format!("解析节点 Fact 失败: {:?}", e))
// // })?;
//
// let shape = self.resolve_shape(&fact.shape);
// let node_name = model.node(outlet_id.node).name.clone();
//
// Ok(TensorInfo {
// name: node_name,
// shape,
// data_type: fact.datum_type,
// })
// })
// .collect() // 函数式声明:自动传播第一处发生的错误
// }
//
// /// 安全还原 Tract 维度至 Vec<AxisDim>
// fn resolve_shape(&self, shape_fact: &ShapeFact) -> Vec<AxisDim> {
// let tract_shape = shape_fact.to_tvec();
//
// let resolved = tract_shape
// .iter()
// .map(|dim| {
// // 防御性编程:必须同时满足能够转换为 i64 且 大于等于 0
// if let Ok(size) = dim.to_i64() {
// if size >= 0 {
// AxisDim::Static(size as usize)
// } else {
// // 如果 ONNX 导出时某些动态维度被标记为了 -1安全地作为动态符号捕获
// AxisDim::Dynamic(dim.to_string())
// }
// } else {
// AxisDim::Dynamic(dim.to_string())
// }
// })
// .collect();
//
// resolved
// }
// }

4
ddddocr-ort/src/types.rs Normal file
View File

@@ -0,0 +1,4 @@
use ort::session::Session as OrtSession;
use std::sync::{Arc, Mutex};
pub type Session = Arc<Mutex<OrtSession>>;

View File

@@ -0,0 +1,600 @@
use std::borrow::Cow;
use std::fs::File;
use std::path::Path;
use anyhow::anyhow;
use ddddocr_core::Charset;
use ddddocr_core::{Normalization, Resize};
pub const CHARSET_BETA: &[&str] = &[
"", "", "", "", "", "", "", "", "", "", "", "", "", "6", "", "",
"", "", "", "", "", "", "", "", "", "", "", "鴿", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "f", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "²", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "!", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "à", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "鹿", "", "", "", "",
"", "p", "L", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "=", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "Y", "", "", "", "", "", "",
"", "", "w", "", "", "3", "", "F", "", "", "", "", "", "", "", "",
"m", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "耀", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "Θ", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "湿",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "X", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "绿", "", "", "", "",
"", "", "滿", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "G", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "x", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "/", "", "", "", "", "", "", "", "", "", "i", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "椿", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", ",", "", "", "", "", "",
"", "T", "", "", "", "N", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "´", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", " ", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "v", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "c",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "''", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "殿", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "B", "", "", "", "О", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "ɔ", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "\"", "", "", "", "", "", "",
"", "", "", "浿", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "n",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ":",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "#", "", "?", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "Φ", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "Q", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", ";", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "轿", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "H", "", "",
"", "", "", "", "", "", "", "", "", "", "", "趿", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "褿", "", "姿", "", "", "", "", "", "", "", "", "", "", "",
"", "K", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "尿", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "W", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", ">", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "P", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "r", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "%",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "l", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "E", "", "", "", "", "", "蹿", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "И", "", "", "", "Z", "", "",
"", "", "", "寿", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "α", "", "", "",
"", "", "", "", "", "", "", "", "", "", "s", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "2", "З", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "Ω", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "@", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "z", "", "", "", "", "", "", "", "", "", "", "", "", "", "访",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "巿", "", "", "", "", "", "D", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "鱿", "", "", "O", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "-", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "西", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "羿",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "麿", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "Р", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "ä", "", "", "", "", "广", "", "",
"", "", "", "", "", "", "4", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "忿", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "涿", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "°", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "^", "", "", "", "$", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "槿", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", ")", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "ü", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "仿", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "1", "", "", "", "", "", "", "", "", "", "", "", "", "Й",
"", "", "", "", "", "", "", "", "", "", "", "", "亿", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", " ", "", "", "", "", "", "", "",
"", "", "", "", "", "", "t", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "竿",
"", "|", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "β", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "b", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "o", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Ë",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "è", "", "", "", "", "u", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"÷", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"±", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "9", "", "", "", "", "j", "", "", "0", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "\\", "", "", "", "",
"", "", "", "", "", "", "", "8", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "¥", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "贿", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "ò", "", "", "{", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "5", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "岿",
"[", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "驿", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "e", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "A", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "线", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "é", "", "",
"", "", "", "", "", "", "", "~", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "R", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"稿", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "窿", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "g", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "k", "", "", "", "", "", "",
"", "", "", "", "", "鸿", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "退", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "S",
"", "}", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "`", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "怀", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "屿", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "<", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "Я", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "Λ", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "齿", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "+", "", "", "宿", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "I", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "便", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "×", "", "", "",
"", "", "", "", "", "", "", "穿", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "7", "", "", "", "", "",
"", "", "", "", "", "", "", "", ".", "", "d", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "V", "", "", "]", "", "", "", "", "",
"", "", "", "", "", "", "(", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "诿", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "沿", "", "", "", "", "", "", "使", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"·", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "饿", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"J", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "a", "", "", "", "", "", "", "", "", "&", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "h", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "*", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "q", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "_", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "簿", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "罿", "", "П", "",
"U", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "廿", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "馿", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "M", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "y", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "C", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "®", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "婿", "", "", "", "", "", "", "", "", "", "", "",
"", "",
];
pub const CHARSET_OLD: &[&str] = &["", "", "", "", ""];
// pub fn from_builtin_old() -> Self {
// Self::from_static_slice(
// CHARSET_OLD,
// false,
// Resize::DynamicWidth(64),
// 1,
// Normalization::ZeroToOne,
// )
// }
//
// /// 从预设的 Beta 版字符集创建
// pub fn from_builtin_beta() -> Self {
// Self::from_static_slice(
// CHARSET_BETA,
// false,
// Resize::DynamicWidth(64),
// 1,
// Normalization::MinusOneToOne,
// )
// }
// /// 从外部外部 JSON 文件动态加载字符集(在后续优化中移除)
// pub fn from_json_file<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
// let path = path.as_ref();
// if !path.exists() {
// return Err(anyhow!("模型元数据配置文件不存在: {:?}", path));
// }
//
// let mut file = File::open(path)?;
// let mut content = String::new();
// file.read_to_string(&mut content)?;
//
// let dto: ModelMetadataDto = serde_json::from_str(&content)
// .map_err(|e| anyhow!("JSON 反序列化失败,请检查字段是否完整: {}", e))?;
//
// // 1. 将 DTO 的字符串数组转化为强类型的 Charset
// let tokens: Vec<Cow<'static, str>> =
// dto.charset.into_iter().map(|s| Cow::Owned(s)).collect();
// let charset = Charset::new(tokens);
//
// // 2. 解析 resize 策略(重现 Python 的复杂条件判断)
// if dto.resize.len() != 2 {
// return Err(anyhow!(
// "'resize (or image)' 字段必须是包含两个元素的数组,例如 [-1, 64]"
// ));
// }
// let r0 = dto.resize[0];
// let r1 = dto.resize[1];
//
// let resize = if r0 == -1 {
// if dto.word {
// // 如果 word 为 true且包含 -1Python 里是 resize 为 (r1, r1) 的正方形
// Resize::Square(r1 as u32)
// } else {
// // 如果 word 为 false且包含 -1Python 里是高度固定为 r1宽度按原图比例缩放
// Resize::DynamicWidth(r1 as u32)
// }
// } else {
// // 正常的固定宽高
// Resize::Fixed(r0 as u32, r1 as u32)
// };
//
// Ok(Self {
// charset,
// word: dto.word,
// resize,
// channel: dto.channel,
// normalization: dto.normalization,
// })
// }

View File

@@ -0,0 +1,248 @@
use anyhow::Context;
use ddddocr_core::{DetectionResult, ModelBuilder};
use ddddocr_core::{Detector, ModelMetadata, Normalization, Slider};
// 假设你的包名是这个
use ddddocr_ort::{DetSession, OcrBuilder, OcrSession};
use image::{DynamicImage, ImageBuffer, Luma, Rgb};
use std::fs;
use std::path::Path;
mod char_slice;
use char_slice::CHARSET_BETA;
use ddddocr_core::Resize;
use ddddocr_ort::loader::OrtModelLoader;
fn load_image<P: AsRef<Path>>(path: P) -> anyhow::Result<image::DynamicImage> {
// 1. 先将泛型转为具体的 &Path 引用
let path_ref = path.as_ref();
// 2. 调用 open 时传入引用utils::open 支持 AsRef<Path>
image::open(path_ref).map_err(|e| {
// 3. 此时 path_ref 依然有效,可以安全地在闭包中使用
anyhow::anyhow!("无法加载图片 {:?}: {}", path_ref, e)
})
}
/// 将检测结果绘制在图像上并保存
fn save_debug_image(
dynamic_img: &DynamicImage, // 【优化点 1】直接传入解码好的引用拒绝重复解码
bboxes: &[DetectionResult], // 【修改点 1】类型改为自定义结构体切片
output_path: &str,
) -> anyhow::Result<()> {
// 删除了原本的 let dynamic_img = image::load_from_memory(image_bytes)?;
let mut img = dynamic_img.to_rgb8();
let (width, height) = img.dimensions();
let red = Rgb([255u8, 0, 0]);
for bbox in bboxes {
// 【修改点 2】将原来的索引 bbox[0].. 改为结构体字段访问 .x1, .y1 ..
let x1 = bbox.x1.max(0).min(width as i32 - 1) as u32;
let y1 = bbox.y1.max(0).min(height as i32 - 1) as u32;
let x2 = bbox.x2.max(0).min(width as i32 - 1) as u32;
let y2 = bbox.y2.max(0).min(height as i32 - 1) as u32;
// 绘制横向线条
for x in x1..=x2 {
img.put_pixel(x, y1, red);
img.put_pixel(x, y2, red);
if y1 + 1 < height {
img.put_pixel(x, y1 + 1, red);
}
if y2.saturating_sub(1) > 0 {
img.put_pixel(x, y2 - 1, red);
}
}
// 绘制纵向线条
for y in y1..=y2 {
img.put_pixel(x1, y, red);
img.put_pixel(x2, y, red);
if x1 + 1 < width {
img.put_pixel(x1 + 1, y, red);
}
if x2.saturating_sub(1) > 0 {
img.put_pixel(x2 - 1, y, red);
}
}
}
img.save(output_path)?;
Ok(())
}
#[allow(dead_code)]
fn save_rust_result(result: &ImageBuffer<Luma<f32>, Vec<f32>>, filename: &str) {
let (width, height) = result.dimensions();
// 1. 寻找最值进行归一化
let mut max_val = f32::MIN;
let mut min_val = f32::MAX;
for p in result.pixels() {
if p.0[0] > max_val {
max_val = p.0[0];
}
if p.0[0] < min_val {
min_val = p.0[0];
}
}
// 2. 创建 8 位灰度图
let mut out_buf = ImageBuffer::new(width, height);
for y in 0..height {
for x in 0..width {
let val = result.get_pixel(x, y).0[0];
let normalized = if max_val > min_val {
((val - min_val) / (max_val - min_val) * 255.0) as u8
} else {
0u8
};
out_buf.put_pixel(x, y, Luma([normalized]));
}
}
// 3. 保存
DynamicImage::ImageLuma8(out_buf).save(filename).unwrap();
println!("Rust 结果热力图已保存至: {}", filename);
}
#[test]
fn test_full_classification() {
let model = OrtModelLoader::builder().use_gpu(true)
.model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx")
.expect("模型加载失败");
let metadata = ModelMetadata::from_static_slice(
CHARSET_BETA,
false,
Resize::DynamicWidth(64),
1,
Normalization::MinusOneToOne,
);
// 1. 初始化模型
let ocr = OcrSession::new(model, metadata);
// 2. 加载测试图片
let img =
image::open("D:/CNWei/CNW/Rust/ddddocr-rs/samples/code2.png").expect("测试图片不存在");
// 3. 执行识别
// let result = Ocr::new(&ocr)
// .predict(&img)
// .expect("识别过程出错")
// .into_text();
let result = OcrBuilder::new()
.build(&ocr)
.predict(&img)
.expect("识别过程出错")
.into_text();
println!("识别结果: {}", result);
assert!(!result.is_empty());
}
#[test]
fn test_det_load() -> anyhow::Result<()> {
let det_model = OrtModelLoader::builder()
.model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx")
.expect("模型加载失败");
let det = DetSession::new(det_model);
let image_path = "D:/CNWei/CNW/Rust/ddddocr-rs/samples/det1.png";
let image_bytes =
fs::read(image_path).map_err(|e| anyhow::anyhow!("无法读取图片 {}: {}", image_path, e))?;
println!("图片读取成功,字节大小: {}", image_bytes.len());
// 【修改点 1】将字节流解码为统一的 DynamicImage
let img = image::load_from_memory(&image_bytes)
.map_err(|e| anyhow::anyhow!("图片解码失败: {}", e))?;
// 【修改点 2】传入统一的 &DynamicImage 引用
let bboxes = Detector::new(&det).predict(&img)?;
// println!("{:?}", det);
println!("检测到的目标数量: {}", bboxes.len());
if bboxes.is_empty() {
println!("未检测到任何目标。");
} else {
// 如果 save_debug_image 报错,记得去把它的入参类型和内部访问也改为 DetectionResult
save_debug_image(
&img,
&bboxes,
"D:/CNWei/CNW/Rust/ddddocr-rs/samples/result.jpg",
)?;
for (i, bbox) in bboxes.iter().enumerate() {
// 【修改点 3】将原来的 bbox[0].. 索引访问改为结构体字段访问
println!("目标 [{}]: {}", i, bbox);
}
}
Ok(())
}
#[test]
fn test_real_slide_match() {
let engine = Slider::new();
// 1. 加载你准备好的测试图
// 假设图片放在项目根目录下的 assets 文件夹
let target_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/hua.png")
.expect("请确保 samples/hua.png 存在");
let bg_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/huatu.png")
.expect("请确保 samples/huatu.png 存在");
// 2. 执行匹配
// 如果是那种带有明显阴影边缘的复杂滑块,建议 simple_target 传 false
let start = std::time::Instant::now();
let result = engine
.slide_match(&target_img, &bg_img, false)
.expect("Slide match 执行失败");
let duration = start.elapsed();
// 3. 打印结果
println!("-------------------------------------------");
println!("{}", result);
println!("耗时: {:?}", duration);
println!("-------------------------------------------");
// 验证基本逻辑:坐标不应为 0 (除非匹配失败)
assert_eq!(result.target_x, 237);
assert_eq!(result.target_y, 77);
assert!(result.confidence > 0.0);
}
#[test]
fn test_real_slide_comparison() {
let engine = Slider::new();
// 1. 加载你准备好的测试图
// 假设图片放在项目根目录下的 assets 文件夹
let target_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/ken.jpg")
.expect("请确保 samples/ken.jpg 存在");
let bg_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/kenyuan.jpg")
.expect("请确保 samples/kenyuan.jpg 存在");
// 2. 执行匹配
// 如果是那种带有明显阴影边缘的复杂滑块,建议 simple_target 传 false
let start = std::time::Instant::now();
let result = engine
.slide_comparison(&target_img, &bg_img)
.expect("Slide match 执行失败");
let duration = start.elapsed();
// 3. 打印结果
println!("-------------------------------------------");
println!("滑块匹配测试结果:");
println!("检测坐标: [x: {}, y: {}]", result.target_x, result.target_y);
println!("置信度: {:.4}", result.confidence);
println!("耗时: {:?}", duration);
println!("-------------------------------------------");
// 验证基本逻辑:坐标不应为 0 (除非匹配失败)
assert_eq!(result.target_x, 171);
assert_eq!(result.target_y, 90);
assert!(result.confidence > 0.0);
}
#[test]
fn test_resolve_shape_logic_direct() {
// 创建一个哑 ModelLoader 实例session 用不上,因为我们直接测私有方法)
let loader = OrtModelLoader::builder()
.model_for_path(
// "D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx",
"D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_huashi666_i64.onnx",
)
.expect("建立测试模型图失败");
}

View File

@@ -7,14 +7,16 @@ license = { workspace = true }
[dependencies] [dependencies]
ddddocr-core = { path = "../ddddocr-core" } # 引入兄弟库 ddddocr-core = { path = "../ddddocr-core" } # 引入兄弟库
tract-onnx = "0.21.10" tract-onnx = { workspace = true }
anyhow = "1.0.102" tract-linalg = {workspace = true }
image = { workspace = true }
base64 = "0.22.1"
imageproc = { version = "0.26.2", default-features = true }
serde = { workspace = true }
serde_json = "1.0.150"
ndarray = { workspace = true } # 继承自工作空间 ndarray = { workspace = true } # 继承自工作空间
image = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
anyhow = { workspace = true }
thiserror = { workspace = true } # 刚好可以开始接入你需要的标准库错误处理 thiserror = { workspace = true } # 刚好可以开始接入你需要的标准库错误处理

View File

@@ -2,7 +2,8 @@ use crate::types::Session;
use ddddocr_core::error::{Result, TensorError}; use ddddocr_core::error::{Result, TensorError};
use ddddocr_core::{DetEngine, DetOutput, InferenceEngine}; use ddddocr_core::{DetEngine, DetOutput, InferenceEngine};
use ndarray::Ix3; use ndarray::Ix3;
use tract_onnx::prelude::{tvec, IntoTensor, Tensor}; // use tract_onnx::prelude::{tvec, IntoTensor, Tensor};
use tract_onnx::prelude::*;
#[derive(Debug)] #[derive(Debug)]
pub struct DetSession { pub struct DetSession {
pub session: Session, pub session: Session,
@@ -29,7 +30,8 @@ impl InferenceEngine for DetSession {
println!("模型输出原始数据: {:?}", result); println!("模型输出原始数据: {:?}", result);
// Ok(result.swap_remove(0).into_tensor()) // Ok(result.swap_remove(0).into_tensor())
let raw_tensor = result.swap_remove(0).into_tensor(); let raw_tensor = result.swap_remove(0).into_tensor();
let array_d = raw_tensor.into_array::<f32>().map_err(|_| { // raw_tensor.into_plain_array()?
let array_d = raw_tensor.into_plain_array::<f32>().map_err(|_| {
TensorError::Engine("Tract 实体张量无法转换为 ndarray::ArrayD".to_string()) TensorError::Engine("Tract 实体张量无法转换为 ndarray::ArrayD".to_string())
})?; })?;
// 提前利用克隆(Clone)备份好当前未转维度前的真实 shape (Vec<usize>) // 提前利用克隆(Clone)备份好当前未转维度前的真实 shape (Vec<usize>)

View File

@@ -4,7 +4,6 @@ mod ocr;
mod types; mod types;
mod error; mod error;
pub use ddddocr_core::ocr::OcrBuilder; pub use ddddocr_core::{SlideResult, Slider,OcrBuilder};
pub use ddddocr_core::{SlideResult, Slider};
pub use det::session::DetSession; pub use det::session::DetSession;
pub use ocr::session::OcrSession; pub use ocr::session::OcrSession;

View File

@@ -3,5 +3,5 @@ mod metadata;
mod model; mod model;
pub use error::{Error, ParseError, Result}; pub use error::{Error, ParseError, Result};
pub use metadata::ModelMetadata; pub use metadata::{ModelMetadataDto, NormalizationDto, TractModelMetadata};
pub use model::ModelLoader; pub use model::TractModelLoader;

View File

@@ -1,14 +1,14 @@
use crate::loader::error::{Error, Result}; use crate::loader::error::{Error, Result};
pub use ddddocr_core::ModelMetadata; use ddddocr_core::ModelMetadata;
use ddddocr_core::ocr::Resize; use ddddocr_core::Resize;
use ddddocr_core::{Charset, Normalization}; use ddddocr_core::{Charset, Normalization};
use serde::Deserialize; use serde::Deserialize;
use std::borrow::Cow; use std::borrow::Cow;
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "snake_case")] // 支持 json 中写 "zero_to_one" 或 "minus_one_to_one" #[serde(rename_all = "snake_case")] // 支持 json 中写 "zero_to_one" 或 "minus_one_to_one"
enum NormalizationDto { pub enum NormalizationDto {
/// 映射到 [0.0, 1.0] -> pixel / 255.0 /// 映射到 [0.0, 1.0] -> pixel / 255.0
ZeroToOne, ZeroToOne,
/// 映射到 [-1.0, 1.0] -> (pixel / 255.0 - 0.5) / 0.5 /// 映射到 [-1.0, 1.0] -> (pixel / 255.0 - 0.5) / 0.5
@@ -26,7 +26,7 @@ impl From<NormalizationDto> for Normalization {
/// 仅用于反序列化 JSON 的中间临时结构体DTO /// 仅用于反序列化 JSON 的中间临时结构体DTO
#[derive(Deserialize)] #[derive(Deserialize)]
struct ModelMetadataDto { pub struct ModelMetadataDto {
charset: Vec<String>, charset: Vec<String>,
word: bool, word: bool,
#[serde(alias = "image")] #[serde(alias = "image")]

View File

@@ -1,17 +1,54 @@
use crate::loader::error;
use crate::loader::error::{Error, ParseError, Result}; use crate::loader::error::{Error, ParseError, Result};
use crate::types::Session; use crate::types::Session;
use ddddocr_core::ModelBuilder;
use std::io::Cursor; use std::io::Cursor;
use tract_linalg::multithread::{Executor, set_default_executor};
use tract_onnx::onnx; use tract_onnx::onnx;
use tract_onnx::prelude::*; use tract_onnx::prelude::*;
pub struct ModelLoader; pub struct TractModelLoader;
impl TractModelLoader {
/// 获取针对 Tract 后端的链式构建器
pub fn builder() -> TractModelBuilder {
TractModelBuilder::default()
}
}
impl ModelLoader { /// Tract 专用的链式构建器
pub fn model_for_path<P>(model_path: P) -> Result<Session> #[derive(Debug, Clone, Default)]
pub struct TractModelBuilder {
num_threads: Option<usize>,
}
impl TractModelBuilder {
/// 可选扩展:设置 CPU 线程数(不提供任何 GPU 相关的 API
pub fn num_threads(mut self, threads: usize) -> Self {
self.num_threads = Some(threads);
self
}
fn setup_tract_threads(&self) {
// 💡 1. 如果设置了线程数,可以通过 tract 的 multithread 配置应用给 model
if let Some(threads) = self.num_threads {
// 在 Tract 中可以通过 set_num_threads 或设置底层环境控制并发
// Tract 0.20+ 版本支持全局/局部线程控制)
let executor = if threads <= 1 {
Executor::SingleThread
} else {
Executor::multithread(threads)
};
set_default_executor(executor);
}
}
}
impl ModelBuilder for TractModelBuilder {
type Session = Session;
type Error = Error;
fn model_for_path<P>(&self, model_path: P) -> Result<Session>
where where
P: AsRef<std::path::Path>, P: AsRef<std::path::Path>,
{ {
self.setup_tract_threads();
let path_ref = model_path.as_ref(); let path_ref = model_path.as_ref();
let session = onnx() let session = onnx()
@@ -27,7 +64,8 @@ impl ModelLoader {
Ok(session) Ok(session)
} }
/// 策略 B从内存字节流加载模型配合 include_bytes! 使用) /// 策略 B从内存字节流加载模型配合 include_bytes! 使用)
pub fn model_from_bytes(model_bytes: &[u8]) -> Result<Session> { fn model_from_bytes(&self, model_bytes: &[u8]) -> Result<Session> {
self.setup_tract_threads();
// 使用 std::io::Cursor 将 &[u8] 包装为可读的流(实现 std::io::Read // 使用 std::io::Cursor 将 &[u8] 包装为可读的流(实现 std::io::Read
let mut cursor = Cursor::new(model_bytes); let mut cursor = Cursor::new(model_bytes);
@@ -52,7 +90,7 @@ mod tests {
/// 辅助函数:动态构建一个简单的 ONNX/Tract 内存模型图用于测试 /// 辅助函数:动态构建一个简单的 ONNX/Tract 内存模型图用于测试
fn create_test_model() -> std::result::Result<Session, anyhow::Error> { fn create_test_model() -> std::result::Result<Session, anyhow::Error> {
let mut rect = tract_onnx::prelude::Graph::default(); let mut rect = TypedModel::default();
// 0.21.10 最稳妥的静态 Fact 构建 // 0.21.10 最稳妥的静态 Fact 构建
let input_fact = TypedFact::dt_shape(DatumType::F32, &[1, 3, 224, 224]); let input_fact = TypedFact::dt_shape(DatumType::F32, &[1, 3, 224, 224]);
@@ -63,8 +101,6 @@ mod tests {
rect.set_input_outlets(&[input_node.into()]) rect.set_input_outlets(&[input_node.into()])
.map_err(|e| anyhow::anyhow!("{:?}", e))?; .map_err(|e| anyhow::anyhow!("{:?}", e))?;
rect.set_output_outlets(&[input_node.into()])
.map_err(|e| anyhow::anyhow!("{:?}", e))?;
let typed = rect let typed = rect
.into_optimized() .into_optimized()

View File

@@ -1,5 +1,5 @@
use crate::loader::ModelMetadata;
use crate::types::Session; use crate::types::Session;
use ddddocr_core::ModelMetadata;
use ddddocr_core::error::{DdddError, Result, TensorError}; use ddddocr_core::error::{DdddError, Result, TensorError};
use ddddocr_core::utils::normalize_ocr_logits; use ddddocr_core::utils::normalize_ocr_logits;
use ddddocr_core::{InferenceEngine, OcrEngine, OcrOutput}; use ddddocr_core::{InferenceEngine, OcrEngine, OcrOutput};
@@ -83,7 +83,7 @@ impl InferenceEngine for OcrSession {
match raw_tensor.datum_type() { match raw_tensor.datum_type() {
DatumType::I64 => { DatumType::I64 => {
let array_d = raw_tensor let array_d = raw_tensor
.into_array::<i64>() .into_plain_array::<i64>()
.map_err(|_| TensorError::Engine("Tract 无法获取 i64 内存视图".to_string()))?; .map_err(|_| TensorError::Engine("Tract 无法获取 i64 内存视图".to_string()))?;
// .context("Tract 无法获取 i64 内存视图")?; // .context("Tract 无法获取 i64 内存视图")?;
// 🌟 提前提取真实维度 // 🌟 提前提取真实维度
@@ -101,8 +101,9 @@ impl InferenceEngine for OcrSession {
DatumType::F32 => { DatumType::F32 => {
let shape = raw_tensor.shape(); let shape = raw_tensor.shape();
println!("模型输出shape数据: {:?}", shape); println!("模型输出shape数据: {:?}", shape);
// raw_tensor.to_plain_array_view()
let view = raw_tensor let view = raw_tensor
.to_array_view::<f32>() .to_plain_array_view::<f32>()
.map_err(|_| TensorError::Engine("Tract 无法获取 f32 内存视图".to_string()))?; .map_err(|_| TensorError::Engine("Tract 无法获取 f32 内存视图".to_string()))?;
// 1. 极其纯粹的、无拷贝的多维 Shape 压扁清洗 // 1. 极其纯粹的、无拷贝的多维 Shape 压扁清洗
normalize_ocr_logits(view, shape) normalize_ocr_logits(view, shape)

View File

@@ -1,3 +1,4 @@
use tract_onnx::prelude::{Graph, RunnableModel, TypedFact, TypedOp}; use std::sync::Arc;
use tract_onnx::prelude::TypedRunnableModel;
pub type Session = RunnableModel<TypedFact, Box<dyn TypedOp>, Graph<TypedFact, Box<dyn TypedOp>>>; pub type Session = Arc<TypedRunnableModel>;

View File

@@ -2,8 +2,8 @@ use std::borrow::Cow;
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
use anyhow::anyhow; use anyhow::anyhow;
use ddddocr_core::ocr::Charset; use ddddocr_core::Charset;
use ddddocr_core::ocr::{Normalization, Resize}; use ddddocr_core::{Normalization, Resize};
pub const CHARSET_BETA: &[&str] = &[ pub const CHARSET_BETA: &[&str] = &[
"", "", "", "", "", "", "", "", "", "", "", "", "", "6", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "6", "", "",

View File

@@ -1,18 +1,17 @@
use anyhow::Context; use anyhow::Context;
use ddddocr_core::det::DetectionResult; use ddddocr_core::{DetectionResult, ModelBuilder};
use ddddocr_core::{Detector, ModelMetadata, Normalization, Ocr, Slider}; use ddddocr_core::{Detector, ModelMetadata, Normalization, Slider};
// 假设你的包名是这个 // 假设你的包名是这个
use ddddocr_tract::{DetSession, OcrSession,OcrBuilder}; use ddddocr_tract::{DetSession, OcrBuilder, OcrSession};
use image::{DynamicImage, ImageBuffer, Luma, Rgb}; use image::{DynamicImage, ImageBuffer, Luma, Rgb};
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use tract_onnx::model;
mod char_slice; mod char_slice;
use char_slice::CHARSET_BETA; use char_slice::CHARSET_BETA;
use ddddocr_core::ocr::Resize; use ddddocr_core::Resize;
use ddddocr_tract::loader::ModelLoader; use ddddocr_tract::loader::{ TractModelLoader};
fn load_image<P: AsRef<Path>>(path: P) -> anyhow::Result<image::DynamicImage> { fn load_image<P: AsRef<Path>>(path: P) -> anyhow::Result<image::DynamicImage> {
// 1. 先将泛型转为具体的 &Path 引用 // 1. 先将泛型转为具体的 &Path 引用
@@ -105,9 +104,8 @@ fn save_rust_result(result: &ImageBuffer<Luma<f32>, Vec<f32>>, filename: &str) {
} }
#[test] #[test]
fn test_full_classification() { fn test_full_classification() {
let model = ModelLoader::model_for_path( let model = TractModelLoader::builder()
"D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx", .model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx")
)
.expect("模型加载失败"); .expect("模型加载失败");
let metadata = ModelMetadata::from_static_slice( let metadata = ModelMetadata::from_static_slice(
CHARSET_BETA, CHARSET_BETA,
@@ -127,7 +125,8 @@ fn test_full_classification() {
// .predict(&img) // .predict(&img)
// .expect("识别过程出错") // .expect("识别过程出错")
// .into_text(); // .into_text();
let result = OcrBuilder::new().build(&ocr) let result = OcrBuilder::new()
.build(&ocr)
.predict(&img) .predict(&img)
.expect("识别过程出错") .expect("识别过程出错")
.into_text(); .into_text();
@@ -138,7 +137,8 @@ fn test_full_classification() {
#[test] #[test]
fn test_det_load() -> anyhow::Result<()> { fn test_det_load() -> anyhow::Result<()> {
let det_model = let det_model =
ModelLoader::model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx") TractModelLoader::builder()
.model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx")
.expect("模型加载失败"); .expect("模型加载失败");
let det = DetSession::new(det_model); let det = DetSession::new(det_model);
let image_path = "D:/CNWei/CNW/Rust/ddddocr-rs/samples/det1.png"; let image_path = "D:/CNWei/CNW/Rust/ddddocr-rs/samples/det1.png";
@@ -240,9 +240,11 @@ fn test_real_slide_comparison() {
#[test] #[test]
fn test_resolve_shape_logic_direct() { fn test_resolve_shape_logic_direct() {
// 创建一个哑 ModelLoader 实例session 用不上,因为我们直接测私有方法) // 创建一个哑 ModelLoader 实例session 用不上,因为我们直接测私有方法)
let loader = ModelLoader::model_for_path( let loader = TractModelLoader::builder()
.model_for_path(
// "D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx", // "D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx",
"D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_huashi666_i64.onnx", "D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_huashi666_i64.onnx",
) )
.expect("建立测试模型图失败"); .expect("建立测试模型图失败");
println!("{:?}", loader.model().inputs);
} }