refactor(core): 提炼公共类型
- 将 AxisDim、TensorInfo 等公共类型下沉至 ddddocr_core::types - 项目结构优化
This commit is contained in:
@@ -6,7 +6,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,11 @@
|
|||||||
use crate::det::executor::Detector;
|
use crate::det::executor::Detector;
|
||||||
// use ddddocr_tract::det::session::DetSession;
|
// use ddddocr_tract::det::session::DetSession;
|
||||||
use crate::DetEngine;
|
use crate::traits::DetEngine;
|
||||||
|
#[derive(Default)]
|
||||||
pub struct DetBuilder {
|
pub struct DetBuilder;
|
||||||
use_gpu: bool,
|
|
||||||
device_id: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DetBuilder {
|
impl DetBuilder {
|
||||||
fn use_gpu(mut self) -> Self {
|
|
||||||
self.use_gpu = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn device_id(mut self, device_id: u8) -> Self {
|
|
||||||
self.device_id = device_id;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn build<E: DetEngine>(self, session: &E) -> Detector<'_> {
|
fn build<E: DetEngine>(self, session: &E) -> Detector<'_> {
|
||||||
Detector {
|
Detector { session }
|
||||||
session,
|
|
||||||
use_gpu: self.use_gpu,
|
|
||||||
device_id: self.device_id,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use std::fmt;
|
|||||||
// use tract_onnx::prelude::{Tensor};
|
// use tract_onnx::prelude::{Tensor};
|
||||||
|
|
||||||
// use ddddocr_tract::det::session::DetSession;
|
// use ddddocr_tract::det::session::DetSession;
|
||||||
use crate::{DetEngine, DetOutput};
|
use crate::{DetBuilder, DetOutput, OcrBuilder};
|
||||||
|
use crate::traits::DetEngine;
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct DetectionResult {
|
pub struct DetectionResult {
|
||||||
pub x1: i32,
|
pub x1: i32,
|
||||||
@@ -29,21 +30,17 @@ impl fmt::Display for DetectionResult {
|
|||||||
|
|
||||||
pub struct Detector<'a> {
|
pub struct Detector<'a> {
|
||||||
pub(crate) session: &'a dyn DetEngine,
|
pub(crate) session: &'a dyn DetEngine,
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) use_gpu: bool,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) device_id: u8,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Detector<'a> {
|
impl<'a> Detector<'a> {
|
||||||
pub fn new(session: &'a dyn DetEngine) -> Self {
|
pub fn new(session: &'a dyn DetEngine) -> Self {
|
||||||
Detector {
|
Detector { session }
|
||||||
session,
|
|
||||||
use_gpu: false,
|
|
||||||
device_id: 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
pub fn builder() -> DetBuilder {
|
||||||
|
DetBuilder::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Detector<'a> {
|
||||||
pub fn predict(&self, image: &DynamicImage) -> Result<Vec<DetectionResult>> {
|
pub fn predict(&self, image: &DynamicImage) -> Result<Vec<DetectionResult>> {
|
||||||
// Rust 中通常在调用层处理文件/PIL转换,这里直接进入核心逻辑
|
// Rust 中通常在调用层处理文件/PIL转换,这里直接进入核心逻辑
|
||||||
Ok(self.get_bbox(image)?)
|
Ok(self.get_bbox(image)?)
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ pub mod error;
|
|||||||
mod ocr;
|
mod ocr;
|
||||||
mod slide;
|
mod slide;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
pub mod types;
|
||||||
use error::{Result, TensorError};
|
pub mod traits;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
pub use crate::det::{DetBuilder, DetectionResult, Detector};
|
pub use crate::det::{DetBuilder, DetectionResult, Detector};
|
||||||
pub use crate::ocr::{Charset, ModelMetadata, Normalization, Ocr, OcrBuilder, OcrResult, Resize};
|
pub use crate::ocr::{Charset, ModelMetadata, Normalization, Ocr, OcrBuilder, OcrResult, Resize};
|
||||||
pub use crate::slide::{SlideResult, Slider};
|
pub use crate::slide::{SlideResult, Slider};
|
||||||
|
|
||||||
|
|
||||||
// DetSession
|
// DetSession
|
||||||
|
|
||||||
pub enum OcrOutput {
|
pub enum OcrOutput {
|
||||||
@@ -22,24 +22,3 @@ pub enum DetOutput {
|
|||||||
Detection(ndarray::Array3<f32>), // 拥有完整所有权的 2维矩阵,可任意传递和返回
|
Detection(ndarray::Array3<f32>), // 拥有完整所有权的 2维矩阵,可任意传递和返回
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 核心层定义的统一推理引擎接口。
|
|
||||||
/// 未来的 ddddocr-tract 和 ddddocr-ort 都必须实现这个 Trait
|
|
||||||
pub trait InferenceEngine {
|
|
||||||
/// 关联类型:具体的 Session 需要声明自己到底产出什么枚举
|
|
||||||
type Output;
|
|
||||||
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait OcrEngine: InferenceEngine<Output = OcrOutput> {
|
|
||||||
fn metadata(&self) -> &ModelMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::ocr::executor::Ocr;
|
use crate::ocr::executor::Ocr;
|
||||||
// use ddddocr_tract::session::OcrSession;
|
// use ddddocr_tract::session::OcrSession;
|
||||||
use crate::OcrEngine;
|
use crate::traits::OcrEngine;
|
||||||
use crate::ocr::color_filter::ColorFilter;
|
use crate::ocr::color_filter::ColorFilter;
|
||||||
use crate::ocr::token_filter::TokenFilter;
|
use crate::ocr::token_filter::TokenFilter;
|
||||||
|
#[derive(Default)]
|
||||||
pub struct OcrBuilder {
|
pub struct OcrBuilder {
|
||||||
/// 是否修复PNG格式问题
|
/// 是否修复PNG格式问题
|
||||||
png_fix: bool,
|
png_fix: bool,
|
||||||
@@ -49,14 +49,14 @@ impl OcrBuilder {
|
|||||||
self.charset_restrict = Some(Box::new(restrict));
|
self.charset_restrict = Some(Box::new(restrict));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn build<E: OcrEngine>(self, session: &E) -> Ocr<'_> {
|
pub fn runner<E: OcrEngine>(self, runtime: &E) -> Ocr<'_> {
|
||||||
// 1. 原地解析颜色过滤器
|
// 1. 原地解析颜色过滤器
|
||||||
let final_color_ranges = match &self.color_filter {
|
let final_color_ranges = match &self.color_filter {
|
||||||
Some(filter) => filter.collect_to_vec(),
|
Some(filter) => filter.collect_to_vec(),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
};
|
};
|
||||||
// 2. 原地解析字符集过滤
|
// 2. 原地解析字符集过滤
|
||||||
let tokens = &session.metadata().charset.tokens;
|
let tokens = &runtime.metadata().charset.tokens;
|
||||||
let final_charset_indices = match &self.charset_restrict {
|
let final_charset_indices = match &self.charset_restrict {
|
||||||
Some(restrict) => restrict.apply_to_charset(tokens),
|
Some(restrict) => restrict.apply_to_charset(tokens),
|
||||||
None => None,
|
None => None,
|
||||||
@@ -64,7 +64,7 @@ impl OcrBuilder {
|
|||||||
|
|
||||||
// Ocr::new(session, self)
|
// Ocr::new(session, self)
|
||||||
Ocr {
|
Ocr {
|
||||||
session,
|
runtime,
|
||||||
png_fix: self.png_fix, // 原地解构出来
|
png_fix: self.png_fix, // 原地解构出来
|
||||||
probability: self.probability,
|
probability: self.probability,
|
||||||
final_color_ranges,
|
final_color_ranges,
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ use ndarray::ArrayView2;
|
|||||||
// Logits(ndarray::Array2<f32>), // 拥有完整所有权的 2维矩阵,可任意传递和返回
|
// Logits(ndarray::Array2<f32>), // 拥有完整所有权的 2维矩阵,可任意传递和返回
|
||||||
// }
|
// }
|
||||||
use crate::error::{ImagePreprocessError, Result, TensorError};
|
use crate::error::{ImagePreprocessError, Result, TensorError};
|
||||||
use crate::{OcrEngine, OcrOutput};
|
use crate::{OcrBuilder, OcrOutput};
|
||||||
|
use crate::traits::OcrEngine;
|
||||||
use tracing::{ warn};
|
use tracing::{ warn};
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum OcrResult {
|
pub enum OcrResult {
|
||||||
@@ -103,7 +104,7 @@ impl fmt::Display for OcrResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Ocr<'a> {
|
pub struct Ocr<'a> {
|
||||||
pub(crate) session: &'a dyn OcrEngine,
|
pub(crate) runtime: &'a dyn OcrEngine,
|
||||||
pub(crate) png_fix: bool,
|
pub(crate) png_fix: bool,
|
||||||
pub(crate) probability: bool,
|
pub(crate) probability: bool,
|
||||||
/// 颜色过滤:保留的颜色列表
|
/// 颜色过滤:保留的颜色列表
|
||||||
@@ -116,15 +117,19 @@ pub struct Ocr<'a> {
|
|||||||
impl<'a> Ocr<'a> {
|
impl<'a> Ocr<'a> {
|
||||||
// 初始化任务,设置默认参数
|
// 初始化任务,设置默认参数
|
||||||
|
|
||||||
pub fn new(session: &'a dyn OcrEngine) -> Self {
|
pub fn new(runtime: &'a dyn OcrEngine) -> Self {
|
||||||
Ocr {
|
Ocr {
|
||||||
session,
|
runtime,
|
||||||
png_fix: false, // 默认值
|
png_fix: false, // 默认值
|
||||||
probability: false,
|
probability: false,
|
||||||
final_color_ranges: Ok(None),
|
final_color_ranges: Ok(None),
|
||||||
final_charset_indices: None,
|
final_charset_indices: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn builder() -> OcrBuilder {
|
||||||
|
OcrBuilder::default()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
impl<'a> Ocr<'a> {
|
impl<'a> Ocr<'a> {
|
||||||
pub fn predict(&self, image: &DynamicImage) -> Result<OcrResult> {
|
pub fn predict(&self, image: &DynamicImage) -> Result<OcrResult> {
|
||||||
@@ -157,7 +162,7 @@ impl<'a> Ocr<'a> {
|
|||||||
};
|
};
|
||||||
let tensor = self.preprocess_image(&img_cow)?;
|
let tensor = self.preprocess_image(&img_cow)?;
|
||||||
|
|
||||||
let raw_tensor = self.session.inference(tensor)?;
|
let raw_tensor = self.runtime.inference(tensor)?;
|
||||||
|
|
||||||
// 3. 后处理分流:直接返回 OcrResult
|
// 3. 后处理分流:直接返回 OcrResult
|
||||||
// let ocr_output = match raw_tensor.datum_type() {
|
// let ocr_output = match raw_tensor.datum_type() {
|
||||||
@@ -178,7 +183,7 @@ impl<'a> Ocr<'a> {
|
|||||||
/// 负责:透明背景修复 -> 灰度化 -> 按比例 Resize -> 归一化 -> 4维张量转换
|
/// 负责:透明背景修复 -> 灰度化 -> 按比例 Resize -> 归一化 -> 4维张量转换
|
||||||
fn preprocess_image(&self, img: &DynamicImage) -> Result<ndarray::Array4<f32>,ImagePreprocessError> {
|
fn preprocess_image(&self, img: &DynamicImage) -> Result<ndarray::Array4<f32>,ImagePreprocessError> {
|
||||||
// 1. 获取模型元数据配置
|
// 1. 获取模型元数据配置
|
||||||
let meta = self.session.metadata();
|
let meta = self.runtime.metadata();
|
||||||
let norm = &meta.normalization; // 获取归一化器
|
let norm = &meta.normalization; // 获取归一化器
|
||||||
|
|
||||||
// A. 修复 PNG 透明背景 (内部逻辑你之前已实现)
|
// A. 修复 PNG 透明背景 (内部逻辑你之前已实现)
|
||||||
@@ -326,7 +331,7 @@ impl<'a> Ocr<'a> {
|
|||||||
}
|
}
|
||||||
impl<'a> Ocr<'a> {
|
impl<'a> Ocr<'a> {
|
||||||
fn is_valid_indices(&self, idx: usize) -> bool {
|
fn is_valid_indices(&self, idx: usize) -> bool {
|
||||||
if idx >= self.session.metadata().charset.size() {
|
if idx >= self.runtime.metadata().charset.size() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +343,7 @@ impl<'a> Ocr<'a> {
|
|||||||
/// 【按需延迟打印】:当用户真的需要“知道当前有哪些限制字符”时,一秒反查并打印
|
/// 【按需延迟打印】:当用户真的需要“知道当前有哪些限制字符”时,一秒反查并打印
|
||||||
/// 这里的 &str 完美借用了自 tokens,依然是彻底的零拷贝!
|
/// 这里的 &str 完美借用了自 tokens,依然是彻底的零拷贝!
|
||||||
pub fn valid_tokens(&self) -> Vec<&str> {
|
pub fn valid_tokens(&self) -> Vec<&str> {
|
||||||
let charset = &self.session.metadata().charset;
|
let charset = &self.runtime.metadata().charset;
|
||||||
let tokens = &charset.tokens;
|
let tokens = &charset.tokens;
|
||||||
match &self.final_charset_indices {
|
match &self.final_charset_indices {
|
||||||
Some(indices) => indices
|
Some(indices) => indices
|
||||||
@@ -352,7 +357,7 @@ impl<'a> Ocr<'a> {
|
|||||||
pub fn valid_size(&self) -> usize {
|
pub fn valid_size(&self) -> usize {
|
||||||
match &self.final_charset_indices {
|
match &self.final_charset_indices {
|
||||||
Some(indices) => indices.len(),
|
Some(indices) => indices.len(),
|
||||||
None => self.session.metadata().charset.tokens.len(),
|
None => self.runtime.metadata().charset.tokens.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// 变体 B 核心处理器:单次遍历 2D 视图,融合计算 Softmax、Argmax、置信度并输出概率大包
|
/// 变体 B 核心处理器:单次遍历 2D 视图,融合计算 Softmax、Argmax、置信度并输出概率大包
|
||||||
@@ -500,7 +505,7 @@ impl<'a> Ocr<'a> {
|
|||||||
/// 获取有效字符索引列表 (用于外部验证或过滤)
|
/// 获取有效字符索引列表 (用于外部验证或过滤)
|
||||||
fn ctc_decode_to_string(&self, predicted_indices: &[i64]) -> String {
|
fn ctc_decode_to_string(&self, predicted_indices: &[i64]) -> String {
|
||||||
println!("indices模型输出原始数据: {:?}", predicted_indices);
|
println!("indices模型输出原始数据: {:?}", predicted_indices);
|
||||||
let charset = &self.session.metadata().charset;
|
let charset = &self.runtime.metadata().charset;
|
||||||
let tokens = &charset.tokens;
|
let tokens = &charset.tokens;
|
||||||
// let valid_indices = &charset.valid_indices;
|
// let valid_indices = &charset.valid_indices;
|
||||||
|
|
||||||
|
|||||||
42
ddddocr-core/src/traits.rs
Normal file
42
ddddocr-core/src/traits.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use crate::error::TensorError;
|
||||||
|
use crate::types::{ModelInfo, TensorInfo};
|
||||||
|
use crate::{DetOutput, ModelMetadata, OcrOutput};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
||||||
|
pub trait Info {
|
||||||
|
fn input_info(&self) -> crate::error::Result<Vec<TensorInfo>>;
|
||||||
|
fn output_info(&self) -> crate::error::Result<Vec<TensorInfo>>;
|
||||||
|
fn model_info(&self) -> crate::error::Result<ModelInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 核心层定义的统一推理引擎接口。
|
||||||
|
/// 未来的 ddddocr-tract 和 ddddocr-ort 都必须实现这个 Trait
|
||||||
|
pub trait InferenceEngine {
|
||||||
|
/// 关联类型:具体的 Session 需要声明自己到底产出什么枚举
|
||||||
|
type Output;
|
||||||
|
fn inference(
|
||||||
|
&self,
|
||||||
|
input_array: ndarray::Array4<f32>,
|
||||||
|
) -> crate::error::Result<Self::Output, TensorError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OcrEngine: InferenceEngine<Output = OcrOutput> + Info {
|
||||||
|
fn metadata(&self) -> &ModelMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DetEngine: InferenceEngine<Output = DetOutput> {}
|
||||||
|
|
||||||
|
pub trait Loader {
|
||||||
|
type Session;
|
||||||
|
type Error;
|
||||||
|
fn build_for_path<P: AsRef<Path>>(
|
||||||
|
&self,
|
||||||
|
model_path: P,
|
||||||
|
) -> crate::error::Result<Self::Session, Self::Error>;
|
||||||
|
|
||||||
|
fn build_from_bytes(
|
||||||
|
&self,
|
||||||
|
model_bytes: &[u8],
|
||||||
|
) -> crate::error::Result<Self::Session, Self::Error>;
|
||||||
|
}
|
||||||
46
ddddocr-core/src/types.rs
Normal file
46
ddddocr-core/src/types.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#[derive(Debug,Clone)]
|
||||||
|
pub enum TensorType{
|
||||||
|
F32,
|
||||||
|
I64,
|
||||||
|
Other
|
||||||
|
}
|
||||||
|
/// 明确命名为 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 tensor_type: TensorType, // 对应 Python 的 type
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 最终返回的模型完整信息
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ModelInfo {
|
||||||
|
pub inputs: Vec<TensorInfo>,
|
||||||
|
pub outputs: Vec<TensorInfo>,
|
||||||
|
/// 硬件执行提供者(采用 Option 兼容不同底层的推理引擎)
|
||||||
|
pub providers: Option<Vec<String>>,
|
||||||
|
}
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
use crate::types::Session;
|
use crate::types::Session;
|
||||||
|
use ddddocr_core::DetOutput;
|
||||||
use ddddocr_core::error::{Result, TensorError};
|
use ddddocr_core::error::{Result, TensorError};
|
||||||
use ddddocr_core::{DetEngine, DetOutput, InferenceEngine};
|
use ddddocr_core::traits::{DetEngine, InferenceEngine};
|
||||||
use ndarray::Ix3;
|
use ndarray::Ix3;
|
||||||
use ort::inputs;
|
use ort::inputs;
|
||||||
use ort::value::TensorRef;
|
use ort::value::TensorRef;
|
||||||
// use tract_onnx::prelude::{tvec, IntoTensor, Tensor};
|
// use tract_onnx::prelude::{tvec, IntoTensor, Tensor};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DetSession {
|
pub struct DetRuntime {
|
||||||
pub session: Session,
|
pub session: Session,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DetSession {
|
impl DetRuntime {
|
||||||
pub fn new(session: Session) -> Self {
|
pub fn new(session: Session) -> Self {
|
||||||
Self { session }
|
Self { session }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InferenceEngine for DetSession {
|
impl InferenceEngine for DetRuntime {
|
||||||
type Output = DetOutput; // 明确绑定 OCR 小枚举
|
type Output = DetOutput; // 明确绑定 OCR 小枚举
|
||||||
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
|
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
|
||||||
// tract 的 run 会返回一个 Vec<TValue>,我们通常只需要第一个输出
|
// tract 的 run 会返回一个 Vec<TValue>,我们通常只需要第一个输出
|
||||||
@@ -28,7 +28,6 @@ impl InferenceEngine for DetSession {
|
|||||||
.lock()
|
.lock()
|
||||||
.map_err(|_| TensorError::Engine("获取 Session 锁失败 (Poisoned)".to_string()))?;
|
.map_err(|_| TensorError::Engine("获取 Session 锁失败 (Poisoned)".to_string()))?;
|
||||||
|
|
||||||
|
|
||||||
let result = session_guard
|
let result = session_guard
|
||||||
.run(inputs![TensorRef::from_array_view(&input_array).map_err(
|
.run(inputs![TensorRef::from_array_view(&input_array).map_err(
|
||||||
|e| TensorError::Engine(format!("构建输入失败: {e}"))
|
|e| TensorError::Engine(format!("构建输入失败: {e}"))
|
||||||
@@ -45,8 +44,7 @@ impl InferenceEngine for DetSession {
|
|||||||
TensorError::Engine("Tract 实体张量无法转换为 ndarray::ArrayD".to_string())
|
TensorError::Engine("Tract 实体张量无法转换为 ndarray::ArrayD".to_string())
|
||||||
})?;
|
})?;
|
||||||
// 提前利用克隆(Clone)备份好当前未转维度前的真实 shape (Vec<usize>)
|
// 提前利用克隆(Clone)备份好当前未转维度前的真实 shape (Vec<usize>)
|
||||||
let shape_vec: Vec<usize> =
|
let shape_vec: Vec<usize> = shape_ref.to_vec().iter().map(|v| *v as usize).collect();
|
||||||
shape_ref.to_vec().iter().map(|v| *v as usize).collect();
|
|
||||||
let shape_vec_slice = shape_vec.as_slice();
|
let shape_vec_slice = shape_vec.as_slice();
|
||||||
|
|
||||||
let view = ndarray::ArrayViewD::from_shape(shape_vec_slice, slice)
|
let view = ndarray::ArrayViewD::from_shape(shape_vec_slice, slice)
|
||||||
@@ -64,4 +62,4 @@ impl InferenceEngine for DetSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DetEngine for DetSession {}
|
impl DetEngine for DetRuntime {}
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ mod types;
|
|||||||
|
|
||||||
|
|
||||||
pub use ddddocr_core::{SlideResult, Slider,OcrBuilder};
|
pub use ddddocr_core::{SlideResult, Slider,OcrBuilder};
|
||||||
pub use det::session::DetSession;
|
pub use det::session::DetRuntime;
|
||||||
pub use ocr::session::OcrSession;
|
pub use ocr::session::OcrRuntime;
|
||||||
@@ -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::{ModelMetadataDto, NormalizationDto, TractModelMetadata};
|
pub use metadata::{ModelMetadataDto, NormalizationDto, Metadata};
|
||||||
pub use model::OrtModelLoader;
|
pub use model::ModelLoader;
|
||||||
@@ -42,7 +42,7 @@ fn default_normalization() -> NormalizationDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Tract 专属扩展trait 或 工具函数
|
/// Tract 专属扩展trait 或 工具函数
|
||||||
pub trait TractModelMetadata: Sized {
|
pub trait Metadata: Sized {
|
||||||
fn from_json_str(json_str: &str) -> Result<Self>;
|
fn from_json_str(json_str: &str) -> Result<Self>;
|
||||||
/// 机制 2:从内存字节流加载(极大地方便 include_bytes! 或网络下载)
|
/// 机制 2:从内存字节流加载(极大地方便 include_bytes! 或网络下载)
|
||||||
fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
|
fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
|
||||||
@@ -50,7 +50,7 @@ pub trait TractModelMetadata: Sized {
|
|||||||
Self::from_json_str(json_str)
|
Self::from_json_str(json_str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl TractModelMetadata for ModelMetadata {
|
impl Metadata for ModelMetadata {
|
||||||
// --- 优雅的工厂模式构造器 ---
|
// --- 优雅的工厂模式构造器 ---
|
||||||
fn from_json_str(json_str: &str) -> Result<ModelMetadata> {
|
fn from_json_str(json_str: &str) -> Result<ModelMetadata> {
|
||||||
let dto: ModelMetadataDto = serde_json::from_str(json_str)?;
|
let dto: ModelMetadataDto = serde_json::from_str(json_str)?;
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
use crate::loader::Error;
|
use crate::loader::Error;
|
||||||
use crate::loader::error::{BuildError, ParseError, Result};
|
use crate::loader::error::{BuildError, ParseError, Result};
|
||||||
use crate::types::Session;
|
use crate::types::Session;
|
||||||
use ddddocr_core::ModelBuilder;
|
use ddddocr_core::traits::Loader;
|
||||||
use ort::session::Session as OrtSession;
|
use ort::session::Session as OrtSession;
|
||||||
use ort::session::builder::SessionBuilder;
|
use ort::session::builder::SessionBuilder;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub struct OrtModelLoader;
|
// pub struct OrtModelLoader;
|
||||||
|
//
|
||||||
impl OrtModelLoader {
|
// impl OrtModelLoader {
|
||||||
/// 获取针对 ORT 后端的链式构建器
|
// /// 获取针对 ORT 后端的链式构建器
|
||||||
pub fn builder() -> OrtModelBuilder {
|
// pub fn builder() -> OrtModelBuilder {
|
||||||
OrtModelBuilder::default()
|
// OrtModelBuilder::default()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
/// ORT 专用的链式构建器
|
/// ORT 专用的链式构建器
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OrtModelBuilder {
|
pub struct ModelLoader {
|
||||||
use_gpu: bool,
|
use_gpu: bool,
|
||||||
device_id: i32,
|
device_id: i32,
|
||||||
intra_threads: Option<usize>,
|
intra_threads: Option<usize>,
|
||||||
}
|
}
|
||||||
impl Default for OrtModelBuilder {
|
impl Default for ModelLoader {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
use_gpu: false,
|
use_gpu: false,
|
||||||
@@ -30,7 +30,7 @@ impl Default for OrtModelBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl OrtModelBuilder {
|
impl ModelLoader {
|
||||||
/// 开启或关闭 GPU 加速
|
/// 开启或关闭 GPU 加速
|
||||||
pub fn use_gpu(mut self, enable: bool) -> Self {
|
pub fn use_gpu(mut self, enable: bool) -> Self {
|
||||||
self.use_gpu = enable;
|
self.use_gpu = enable;
|
||||||
@@ -83,11 +83,11 @@ impl OrtModelBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelBuilder for OrtModelBuilder {
|
impl Loader for ModelLoader {
|
||||||
type Session = Session;
|
type Session = Session;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn model_for_path<P>(&self, model_path: P) -> Result<Session>
|
fn build_for_path<P>(&self, model_path: P) -> Result<Session>
|
||||||
where
|
where
|
||||||
P: AsRef<std::path::Path>,
|
P: AsRef<std::path::Path>,
|
||||||
{
|
{
|
||||||
@@ -102,7 +102,7 @@ impl ModelBuilder for OrtModelBuilder {
|
|||||||
Ok(Arc::new(Mutex::new(session))) // 这里的session需要包装下
|
Ok(Arc::new(Mutex::new(session))) // 这里的session需要包装下
|
||||||
}
|
}
|
||||||
/// 策略 B:从内存字节流加载模型(配合 include_bytes! 使用)
|
/// 策略 B:从内存字节流加载模型(配合 include_bytes! 使用)
|
||||||
fn model_from_bytes(&self, model_bytes: &[u8]) -> Result<Session> {
|
fn build_from_bytes(&self, model_bytes: &[u8]) -> Result<Session> {
|
||||||
let mut builder = self.create_session_builder()?;
|
let mut builder = self.create_session_builder()?;
|
||||||
|
|
||||||
let session = builder
|
let session = builder
|
||||||
|
|||||||
@@ -1,70 +1,69 @@
|
|||||||
use crate::types::Session;
|
use crate::types::Session;
|
||||||
use ddddocr_core::ModelMetadata;
|
use ddddocr_core::ModelMetadata;
|
||||||
|
use ddddocr_core::OcrOutput;
|
||||||
use ddddocr_core::error::{DdddError, Result, TensorError};
|
use ddddocr_core::error::{DdddError, Result, TensorError};
|
||||||
|
use ddddocr_core::traits::{InferenceEngine, Info, OcrEngine};
|
||||||
|
use ddddocr_core::types::{AxisDim, ModelInfo, TensorInfo, TensorType};
|
||||||
use ddddocr_core::utils::normalize_ocr_logits;
|
use ddddocr_core::utils::normalize_ocr_logits;
|
||||||
use ddddocr_core::{InferenceEngine, OcrEngine, OcrOutput};
|
|
||||||
use ort::inputs;
|
use ort::inputs;
|
||||||
use ort::value::{TensorElementType, TensorRef};
|
use ort::value::{TensorElementType, TensorRef};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
// 引入核心层的统一错误类型
|
// 引入核心层的统一错误类型
|
||||||
/// 明确命名为 AxisDim,代表模型某一个轴的维度特征
|
/// 明确命名为 AxisDim,代表模型某一个轴的维度特征
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
// #[derive(Clone, PartialEq, Eq)]
|
||||||
pub enum AxisDim {
|
// pub enum AxisDim {
|
||||||
/// 静态固定维度(如通道数固定为 1,高度固定为 64)
|
// /// 静态固定维度(如通道数固定为 1,高度固定为 64)
|
||||||
Static(usize),
|
// Static(usize),
|
||||||
/// 动态符号维度(如宽度是动态的 "image_width")
|
// /// 动态符号维度(如宽度是动态的 "image_width")
|
||||||
Dynamic(String),
|
// Dynamic(String),
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl AxisDim {
|
// impl AxisDim {
|
||||||
/// 便捷方法:判断是否为动态维度
|
// /// 便捷方法:判断是否为动态维度
|
||||||
pub fn is_dynamic(&self) -> bool {
|
// pub fn is_dynamic(&self) -> bool {
|
||||||
matches!(self, AxisDim::Dynamic(_))
|
// matches!(self, AxisDim::Dynamic(_))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
/// 自定义 Debug 格式化输出,彻底融化套娃外壳,保证日志干净漂亮
|
// /// 自定义 Debug 格式化输出,彻底融化套娃外壳,保证日志干净漂亮
|
||||||
impl std::fmt::Debug for AxisDim {
|
// impl std::fmt::Debug for AxisDim {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
// match self {
|
||||||
AxisDim::Static(size) => write!(f, "{}", size),
|
// AxisDim::Static(size) => write!(f, "{}", size),
|
||||||
AxisDim::Dynamic(expr) => write!(f, "Dynamic(\"{}\")", expr),
|
// AxisDim::Dynamic(expr) => write!(f, "Dynamic(\"{}\")", expr),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
/// 模拟 Python 的 input_info 和 output_info 结构
|
/// 模拟 Python 的 input_info 和 output_info 结构
|
||||||
#[derive(Debug, Clone)]
|
// #[derive(Debug, Clone)]
|
||||||
pub struct TensorInfo {
|
// pub struct TensorInfo {
|
||||||
pub name: String,
|
// pub name: String,
|
||||||
pub shape: Vec<AxisDim>, // 既包含 Fixed 静态维度,也包含 Dynamic 动态符号
|
// pub shape: Vec<AxisDim>, // 既包含 Fixed 静态维度,也包含 Dynamic 动态符号
|
||||||
pub data_type: TensorElementType, // 对应 Python 的 type
|
// pub data_type: TensorElementType, // 对应 Python 的 type
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/// 最终返回的模型完整信息
|
// /// 最终返回的模型完整信息
|
||||||
#[derive(Debug, Clone)]
|
// #[derive(Debug, Clone)]
|
||||||
pub struct ModelInfo {
|
// pub struct ModelInfo {
|
||||||
pub inputs: Vec<TensorInfo>,
|
// pub inputs: Vec<TensorInfo>,
|
||||||
pub outputs: Vec<TensorInfo>,
|
// pub outputs: Vec<TensorInfo>,
|
||||||
/// 硬件执行提供者(采用 Option 兼容不同底层的推理引擎)
|
// /// 硬件执行提供者(采用 Option 兼容不同底层的推理引擎)
|
||||||
pub providers: Option<Vec<String>>,
|
// pub providers: Option<Vec<String>>,
|
||||||
}
|
// }
|
||||||
pub struct OcrSession {
|
pub struct OcrRuntime {
|
||||||
pub session: Session,
|
pub session: Session,
|
||||||
pub model_metadata: ModelMetadata,
|
pub metadata: ModelMetadata,
|
||||||
}
|
}
|
||||||
impl OcrSession {
|
impl OcrRuntime {
|
||||||
pub fn new(session: Session, model_metadata: ModelMetadata) -> Self {
|
pub fn new(session: Session, metadata: ModelMetadata) -> Self {
|
||||||
Self {
|
Self { session, metadata }
|
||||||
session,
|
|
||||||
model_metadata,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl OcrEngine for OcrSession {
|
impl OcrEngine for OcrRuntime {
|
||||||
fn metadata(&self) -> &ModelMetadata {
|
fn metadata(&self) -> &ModelMetadata {
|
||||||
&self.model_metadata
|
&self.metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InferenceEngine for OcrSession {
|
impl InferenceEngine for OcrRuntime {
|
||||||
type Output = OcrOutput;
|
type Output = OcrOutput;
|
||||||
/// 对应 Python 的 _inference
|
/// 对应 Python 的 _inference
|
||||||
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
|
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
|
||||||
@@ -76,18 +75,6 @@ impl InferenceEngine for OcrSession {
|
|||||||
.lock()
|
.lock()
|
||||||
.map_err(|_| TensorError::Engine("获取 Session 锁失败 (Poisoned)".to_string()))?;
|
.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
|
let result = session_guard
|
||||||
.run(inputs![TensorRef::from_array_view(&input_array).map_err(
|
.run(inputs![TensorRef::from_array_view(&input_array).map_err(
|
||||||
|e| TensorError::Engine(format!("构建输入失败: {e}"))
|
|e| TensorError::Engine(format!("构建输入失败: {e}"))
|
||||||
@@ -97,10 +84,7 @@ impl InferenceEngine for OcrSession {
|
|||||||
println!("模型输出原始数据: {:?}", result);
|
println!("模型输出原始数据: {:?}", result);
|
||||||
// Ok(result.swap_remove(0).into_tensor())
|
// Ok(result.swap_remove(0).into_tensor())
|
||||||
let raw_value = &result[0];
|
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() {
|
match raw_value.dtype().tensor_type().unwrap() {
|
||||||
TensorElementType::Int64 => {
|
TensorElementType::Int64 => {
|
||||||
let (array_d, slice) = raw_value
|
let (array_d, slice) = raw_value
|
||||||
@@ -108,7 +92,7 @@ impl InferenceEngine for OcrSession {
|
|||||||
.map_err(|_| TensorError::Engine("Tract 无法获取 i64 内存视图".to_string()))?;
|
.map_err(|_| TensorError::Engine("Tract 无法获取 i64 内存视图".to_string()))?;
|
||||||
// .context("Tract 无法获取 i64 内存视图")?;
|
// .context("Tract 无法获取 i64 内存视图")?;
|
||||||
|
|
||||||
// 🌟 提前提取真实维度
|
// 提前提取真实维度
|
||||||
let actual_shape = array_d
|
let actual_shape = array_d
|
||||||
.to_vec()
|
.to_vec()
|
||||||
.iter()
|
.iter()
|
||||||
@@ -151,75 +135,16 @@ impl InferenceEngine for OcrSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// impl OcrSession {
|
impl Info for OcrRuntime {
|
||||||
// /// 获取模型输入的节点信息列表
|
fn input_info(&self) -> Result<Vec<TensorInfo>> {
|
||||||
// pub fn input_info(&self) -> Result<Vec<TensorInfo>> {
|
todo!()
|
||||||
// let model = self.session.model();
|
}
|
||||||
// let outlets = model.input_outlets().map_err(DdddError::new)?;
|
|
||||||
// self.resolve_tensors(model, outlets)
|
fn output_info(&self) -> Result<Vec<TensorInfo>> {
|
||||||
// }
|
todo!()
|
||||||
//
|
}
|
||||||
// /// 获取模型输出的节点信息列表
|
|
||||||
// pub fn output_info(&self) -> Result<Vec<TensorInfo>> {
|
fn model_info(&self) -> Result<ModelInfo> {
|
||||||
// let model = self.session.model();
|
todo!()
|
||||||
// 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
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use ddddocr_core::{DetectionResult, ModelBuilder};
|
use ddddocr_core::{DetectionResult, Ocr};
|
||||||
|
use ddddocr_core::traits::Loader;
|
||||||
use ddddocr_core::{Detector, ModelMetadata, Normalization, Slider};
|
use ddddocr_core::{Detector, ModelMetadata, Normalization, Slider};
|
||||||
// 假设你的包名是这个
|
// 假设你的包名是这个
|
||||||
use ddddocr_ort::{DetSession, OcrBuilder, OcrSession};
|
use ddddocr_ort::{DetRuntime, OcrBuilder, OcrRuntime};
|
||||||
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;
|
||||||
@@ -11,7 +12,7 @@ mod char_slice;
|
|||||||
use char_slice::CHARSET_BETA;
|
use char_slice::CHARSET_BETA;
|
||||||
use ddddocr_core::Resize;
|
use ddddocr_core::Resize;
|
||||||
|
|
||||||
use ddddocr_ort::loader::OrtModelLoader;
|
use ddddocr_ort::loader::ModelLoader as OrtModelLoader;
|
||||||
|
|
||||||
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 引用
|
||||||
@@ -104,8 +105,9 @@ fn save_rust_result(result: &ImageBuffer<Luma<f32>, Vec<f32>>, filename: &str) {
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_full_classification() {
|
fn test_full_classification() {
|
||||||
let model = OrtModelLoader::builder().use_gpu(true)
|
let model = OrtModelLoader::default().use_gpu(false)
|
||||||
.model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx")
|
.build_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx")
|
||||||
|
// .build_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_old.onnx")
|
||||||
.expect("模型加载失败");
|
.expect("模型加载失败");
|
||||||
let metadata = ModelMetadata::from_static_slice(
|
let metadata = ModelMetadata::from_static_slice(
|
||||||
CHARSET_BETA,
|
CHARSET_BETA,
|
||||||
@@ -115,7 +117,7 @@ fn test_full_classification() {
|
|||||||
Normalization::MinusOneToOne,
|
Normalization::MinusOneToOne,
|
||||||
);
|
);
|
||||||
// 1. 初始化模型
|
// 1. 初始化模型
|
||||||
let ocr = OcrSession::new(model, metadata);
|
let ocr = OcrRuntime::new(model, metadata);
|
||||||
// 2. 加载测试图片
|
// 2. 加载测试图片
|
||||||
let img =
|
let img =
|
||||||
image::open("D:/CNWei/CNW/Rust/ddddocr-rs/samples/code2.png").expect("测试图片不存在");
|
image::open("D:/CNWei/CNW/Rust/ddddocr-rs/samples/code2.png").expect("测试图片不存在");
|
||||||
@@ -125,21 +127,24 @@ fn test_full_classification() {
|
|||||||
// .predict(&img)
|
// .predict(&img)
|
||||||
// .expect("识别过程出错")
|
// .expect("识别过程出错")
|
||||||
// .into_text();
|
// .into_text();
|
||||||
let result = OcrBuilder::new()
|
// let result = OcrBuilder::new()
|
||||||
.build(&ocr)
|
// .build(&ocr)
|
||||||
.predict(&img)
|
// .predict(&img)
|
||||||
.expect("识别过程出错")
|
// .expect("识别过程出错")
|
||||||
.into_text();
|
// .into_text();
|
||||||
|
let res=Ocr::builder().runner(&ocr).predict(&img).expect("s").into_text();
|
||||||
|
|
||||||
println!("识别结果: {}", result);
|
// println!("识别结果: {}", result);
|
||||||
assert!(!result.is_empty());
|
println!("识别结果: {}", res);
|
||||||
|
// assert!(!result.is_empty());
|
||||||
|
assert!(!res.is_empty());
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_det_load() -> anyhow::Result<()> {
|
fn test_det_load() -> anyhow::Result<()> {
|
||||||
let det_model = OrtModelLoader::builder()
|
let det_model = OrtModelLoader::default()
|
||||||
.model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx")
|
.build_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx")
|
||||||
.expect("模型加载失败");
|
.expect("模型加载失败");
|
||||||
let det = DetSession::new(det_model);
|
let det = DetRuntime::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";
|
||||||
let image_bytes =
|
let image_bytes =
|
||||||
fs::read(image_path).map_err(|e| anyhow::anyhow!("无法读取图片 {}: {}", image_path, e))?;
|
fs::read(image_path).map_err(|e| anyhow::anyhow!("无法读取图片 {}: {}", image_path, e))?;
|
||||||
@@ -239,8 +244,8 @@ 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 = OrtModelLoader::builder()
|
let loader = OrtModelLoader::default()
|
||||||
.model_for_path(
|
.build_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",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
use crate::types::Session;
|
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::{ DetOutput};
|
||||||
|
use ddddocr_core::traits::{DetEngine, InferenceEngine};
|
||||||
use ndarray::Ix3;
|
use ndarray::Ix3;
|
||||||
// use tract_onnx::prelude::{tvec, IntoTensor, Tensor};
|
|
||||||
use tract_onnx::prelude::*;
|
use tract_onnx::prelude::*;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DetSession {
|
pub struct DetRuntime {
|
||||||
pub session: Session,
|
pub session: Session,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DetSession {
|
impl DetRuntime {
|
||||||
pub fn new(session: Session) -> Self {
|
pub fn new(session: Session) -> Self {
|
||||||
Self { session }
|
Self { session }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InferenceEngine for DetSession {
|
impl InferenceEngine for DetRuntime {
|
||||||
type Output = DetOutput; // 明确绑定 OCR 小枚举
|
type Output = DetOutput; // 明确绑定 OCR 小枚举
|
||||||
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
|
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
|
||||||
// tract 的 run 会返回一个 Vec<TValue>,我们通常只需要第一个输出
|
// tract 的 run 会返回一个 Vec<TValue>,我们通常只需要第一个输出
|
||||||
@@ -49,4 +49,4 @@ impl InferenceEngine for DetSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DetEngine for DetSession {}
|
impl DetEngine for DetRuntime {}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
mod det;
|
mod det;
|
||||||
|
mod error;
|
||||||
pub mod loader;
|
pub mod loader;
|
||||||
mod ocr;
|
mod ocr;
|
||||||
mod types;
|
mod types;
|
||||||
mod error;
|
|
||||||
|
|
||||||
pub use ddddocr_core::{SlideResult, Slider,OcrBuilder};
|
pub use ddddocr_core::{
|
||||||
pub use det::session::DetSession;
|
DetectionResult, Detector, ModelMetadata, Normalization, Ocr, OcrBuilder, SlideResult, Slider,
|
||||||
pub use ocr::session::OcrSession;
|
};
|
||||||
|
pub use det::session::DetRuntime;
|
||||||
|
pub use ocr::session::OcrRuntime;
|
||||||
|
|||||||
@@ -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::{ModelMetadataDto, NormalizationDto, TractModelMetadata};
|
pub use metadata::{ModelMetadataDto, NormalizationDto, Metadata};
|
||||||
pub use model::TractModelLoader;
|
pub use model::ModelLoader;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ fn default_normalization() -> NormalizationDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Tract 专属扩展trait 或 工具函数
|
/// Tract 专属扩展trait 或 工具函数
|
||||||
pub trait TractModelMetadata: Sized {
|
pub trait Metadata: Sized {
|
||||||
fn from_json_str(json_str: &str) -> Result<Self>;
|
fn from_json_str(json_str: &str) -> Result<Self>;
|
||||||
/// 机制 2:从内存字节流加载(极大地方便 include_bytes! 或网络下载)
|
/// 机制 2:从内存字节流加载(极大地方便 include_bytes! 或网络下载)
|
||||||
fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
|
fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
|
||||||
@@ -50,7 +50,7 @@ pub trait TractModelMetadata: Sized {
|
|||||||
Self::from_json_str(json_str)
|
Self::from_json_str(json_str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl TractModelMetadata for ModelMetadata {
|
impl Metadata for ModelMetadata {
|
||||||
// --- 优雅的工厂模式构造器 ---
|
// --- 优雅的工厂模式构造器 ---
|
||||||
fn from_json_str(json_str: &str) -> Result<ModelMetadata> {
|
fn from_json_str(json_str: &str) -> Result<ModelMetadata> {
|
||||||
let dto: ModelMetadataDto = serde_json::from_str(json_str)?;
|
let dto: ModelMetadataDto = serde_json::from_str(json_str)?;
|
||||||
|
|||||||
@@ -1,26 +1,18 @@
|
|||||||
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 ddddocr_core::traits::Loader;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use tract_linalg::multithread::{Executor, set_default_executor};
|
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 TractModelLoader;
|
|
||||||
impl TractModelLoader {
|
|
||||||
/// 获取针对 Tract 后端的链式构建器
|
|
||||||
pub fn builder() -> TractModelBuilder {
|
|
||||||
TractModelBuilder::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tract 专用的链式构建器
|
/// Tract 专用的链式构建器
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct TractModelBuilder {
|
pub struct ModelLoader {
|
||||||
num_threads: Option<usize>,
|
num_threads: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TractModelBuilder {
|
impl ModelLoader {
|
||||||
/// 可选扩展:设置 CPU 线程数(不提供任何 GPU 相关的 API)
|
/// 可选扩展:设置 CPU 线程数(不提供任何 GPU 相关的 API)
|
||||||
pub fn num_threads(mut self, threads: usize) -> Self {
|
pub fn num_threads(mut self, threads: usize) -> Self {
|
||||||
self.num_threads = Some(threads);
|
self.num_threads = Some(threads);
|
||||||
@@ -41,10 +33,10 @@ impl TractModelBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelBuilder for TractModelBuilder {
|
impl Loader for ModelLoader {
|
||||||
type Session = Session;
|
type Session = Session;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn model_for_path<P>(&self, model_path: P) -> Result<Session>
|
fn build_for_path<P>(&self, model_path: P) -> Result<Session>
|
||||||
where
|
where
|
||||||
P: AsRef<std::path::Path>,
|
P: AsRef<std::path::Path>,
|
||||||
{
|
{
|
||||||
@@ -64,7 +56,7 @@ impl ModelBuilder for TractModelBuilder {
|
|||||||
Ok(session)
|
Ok(session)
|
||||||
}
|
}
|
||||||
/// 策略 B:从内存字节流加载模型(配合 include_bytes! 使用)
|
/// 策略 B:从内存字节流加载模型(配合 include_bytes! 使用)
|
||||||
fn model_from_bytes(&self, model_bytes: &[u8]) -> Result<Session> {
|
fn build_from_bytes(&self, model_bytes: &[u8]) -> Result<Session> {
|
||||||
self.setup_tract_threads();
|
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);
|
||||||
|
|||||||
@@ -1,69 +1,80 @@
|
|||||||
use crate::types::Session;
|
use crate::types::Session;
|
||||||
use ddddocr_core::ModelMetadata;
|
use ddddocr_core::ModelMetadata;
|
||||||
|
use ddddocr_core::OcrOutput;
|
||||||
use ddddocr_core::error::{DdddError, Result, TensorError};
|
use ddddocr_core::error::{DdddError, Result, TensorError};
|
||||||
|
use ddddocr_core::traits::{InferenceEngine, Info, OcrEngine};
|
||||||
|
use ddddocr_core::types::{AxisDim, ModelInfo, TensorInfo, TensorType};
|
||||||
use ddddocr_core::utils::normalize_ocr_logits;
|
use ddddocr_core::utils::normalize_ocr_logits;
|
||||||
use ddddocr_core::{InferenceEngine, OcrEngine, OcrOutput};
|
|
||||||
use tract_onnx::prelude::{DatumType, OutletId, ShapeFact, TypedModel};
|
use tract_onnx::prelude::{DatumType, OutletId, ShapeFact, TypedModel};
|
||||||
use tract_onnx::prelude::{IntoTensor, Tensor, tvec};
|
use tract_onnx::prelude::{IntoTensor, Tensor, tvec};
|
||||||
// 引入核心层的统一错误类型
|
|
||||||
/// 明确命名为 AxisDim,代表模型某一个轴的维度特征
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
|
||||||
pub enum AxisDim {
|
|
||||||
/// 静态固定维度(如通道数固定为 1,高度固定为 64)
|
|
||||||
Static(usize),
|
|
||||||
/// 动态符号维度(如宽度是动态的 "image_width")
|
|
||||||
Dynamic(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AxisDim {
|
pub struct OcrRuntime {
|
||||||
/// 便捷方法:判断是否为动态维度
|
|
||||||
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: DatumType, // 对应 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 session: Session,
|
||||||
pub model_metadata: ModelMetadata,
|
pub metadata: ModelMetadata,
|
||||||
}
|
}
|
||||||
impl OcrSession {
|
impl OcrRuntime {
|
||||||
pub fn new(session: Session, model_metadata: ModelMetadata) -> Self {
|
pub fn new(session: Session, metadata: ModelMetadata) -> Self {
|
||||||
Self {
|
Self { session, metadata }
|
||||||
session,
|
}
|
||||||
model_metadata,
|
/// 获取模型输入的节点信息列表
|
||||||
}
|
|
||||||
|
/// 提取出来的公共转换逻辑:将一组 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();
|
||||||
|
let tensor_type = match fact.datum_type {
|
||||||
|
DatumType::F32 => TensorType::F32,
|
||||||
|
DatumType::I64 => TensorType::I64,
|
||||||
|
_ => TensorType::Other,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(TensorInfo {
|
||||||
|
name: node_name,
|
||||||
|
shape,
|
||||||
|
tensor_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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl OcrEngine for OcrSession {
|
impl OcrEngine for OcrRuntime {
|
||||||
fn metadata(&self) -> &ModelMetadata {
|
fn metadata(&self) -> &ModelMetadata {
|
||||||
&self.model_metadata
|
&self.metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InferenceEngine for OcrSession {
|
impl InferenceEngine for OcrRuntime {
|
||||||
type Output = OcrOutput;
|
type Output = OcrOutput;
|
||||||
/// 对应 Python 的 _inference
|
/// 对应 Python 的 _inference
|
||||||
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
|
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError> {
|
||||||
@@ -115,16 +126,15 @@ impl InferenceEngine for OcrSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl OcrSession {
|
impl Info for OcrRuntime {
|
||||||
/// 获取模型输入的节点信息列表
|
fn input_info(&self) -> Result<Vec<TensorInfo>> {
|
||||||
pub fn input_info(&self) -> Result<Vec<TensorInfo>> {
|
|
||||||
let model = self.session.model();
|
let model = self.session.model();
|
||||||
let outlets = model.input_outlets().map_err(DdddError::new)?;
|
let outlets = model.input_outlets().map_err(DdddError::new)?;
|
||||||
self.resolve_tensors(model, outlets)
|
self.resolve_tensors(model, outlets)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取模型输出的节点信息列表
|
/// 获取模型输出的节点信息列表
|
||||||
pub fn output_info(&self) -> Result<Vec<TensorInfo>> {
|
fn output_info(&self) -> Result<Vec<TensorInfo>> {
|
||||||
let model = self.session.model();
|
let model = self.session.model();
|
||||||
let outlets = model.output_outlets().map_err(DdddError::new)?;
|
let outlets = model.output_outlets().map_err(DdddError::new)?;
|
||||||
self.resolve_tensors(model, outlets)
|
self.resolve_tensors(model, outlets)
|
||||||
@@ -133,57 +143,11 @@ impl OcrSession {
|
|||||||
/// 获取模型详细元数据信息(对标 Python ddddocr 的 get_model_info)
|
/// 获取模型详细元数据信息(对标 Python ddddocr 的 get_model_info)
|
||||||
/// 完美包容 [1, 1, 64, image_width] 这样的变长图像模型
|
/// 完美包容 [1, 1, 64, image_width] 这样的变长图像模型
|
||||||
/// 获取模型详细元数据信息(代码更紧凑、优雅)
|
/// 获取模型详细元数据信息(代码更紧凑、优雅)
|
||||||
pub fn model_info(&self) -> Result<ModelInfo> {
|
fn model_info(&self) -> Result<ModelInfo> {
|
||||||
Ok(ModelInfo {
|
Ok(ModelInfo {
|
||||||
inputs: self.input_info()?,
|
inputs: self.input_info()?,
|
||||||
outputs: self.output_info()?,
|
outputs: self.output_info()?,
|
||||||
providers: None,
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use ddddocr_core::{DetectionResult, ModelBuilder};
|
use ddddocr_core::traits::Loader;
|
||||||
use ddddocr_core::{Detector, ModelMetadata, Normalization, Slider};
|
use ddddocr_tract::{DetectionResult, Ocr};
|
||||||
|
use ddddocr_tract::{Detector, ModelMetadata, Normalization, Slider};
|
||||||
// 假设你的包名是这个
|
// 假设你的包名是这个
|
||||||
use ddddocr_tract::{DetSession, OcrBuilder, OcrSession};
|
use ddddocr_tract::{DetRuntime, OcrRuntime};
|
||||||
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;
|
||||||
@@ -11,7 +12,7 @@ mod char_slice;
|
|||||||
use char_slice::CHARSET_BETA;
|
use char_slice::CHARSET_BETA;
|
||||||
use ddddocr_core::Resize;
|
use ddddocr_core::Resize;
|
||||||
|
|
||||||
use ddddocr_tract::loader::{ TractModelLoader};
|
use ddddocr_tract::loader::ModelLoader as 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 引用
|
||||||
@@ -104,9 +105,10 @@ fn save_rust_result(result: &ImageBuffer<Luma<f32>, Vec<f32>>, filename: &str) {
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_full_classification() {
|
fn test_full_classification() {
|
||||||
let model = TractModelLoader::builder()
|
let session = TractModelLoader::default()
|
||||||
.model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx")
|
.build_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,
|
||||||
false,
|
false,
|
||||||
@@ -115,18 +117,18 @@ fn test_full_classification() {
|
|||||||
Normalization::MinusOneToOne,
|
Normalization::MinusOneToOne,
|
||||||
);
|
);
|
||||||
// 1. 初始化模型
|
// 1. 初始化模型
|
||||||
let ocr = OcrSession::new(model, metadata);
|
let ocr_runtime = OcrRuntime::new(session, metadata);
|
||||||
// 2. 加载测试图片
|
// 2. 加载测试图片
|
||||||
let img =
|
let img =
|
||||||
image::open("D:/CNWei/CNW/Rust/ddddocr-rs/samples/code2.png").expect("测试图片不存在");
|
image::open("D:/CNWei/CNW/Rust/ddddocr-rs/samples/code2.png").expect("测试图片不存在");
|
||||||
|
|
||||||
// 3. 执行识别
|
// 3. 执行识别
|
||||||
// let result = Ocr::new(&ocr)
|
// let result = Ocr::new(&ocr_runtime)
|
||||||
// .predict(&img)
|
// .predict(&img)
|
||||||
// .expect("识别过程出错")
|
// .expect("识别过程出错")
|
||||||
// .into_text();
|
// .into_text();
|
||||||
let result = OcrBuilder::new()
|
let result = Ocr::builder()
|
||||||
.build(&ocr)
|
.runner(&ocr_runtime)
|
||||||
.predict(&img)
|
.predict(&img)
|
||||||
.expect("识别过程出错")
|
.expect("识别过程出错")
|
||||||
.into_text();
|
.into_text();
|
||||||
@@ -136,11 +138,10 @@ fn test_full_classification() {
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_det_load() -> anyhow::Result<()> {
|
fn test_det_load() -> anyhow::Result<()> {
|
||||||
let det_model =
|
let det_model = TractModelLoader::default()
|
||||||
TractModelLoader::builder()
|
.build_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx")
|
||||||
.model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx")
|
.expect("模型加载失败");
|
||||||
.expect("模型加载失败");
|
let det = DetRuntime::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";
|
||||||
let image_bytes =
|
let image_bytes =
|
||||||
fs::read(image_path).map_err(|e| anyhow::anyhow!("无法读取图片 {}: {}", image_path, e))?;
|
fs::read(image_path).map_err(|e| anyhow::anyhow!("无法读取图片 {}: {}", image_path, e))?;
|
||||||
@@ -240,11 +241,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 = TractModelLoader::builder()
|
let loader = TractModelLoader::default()
|
||||||
.model_for_path(
|
.build_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);
|
println!("{:?}", loader.model().inputs);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user