refactor: 抽象解耦推理引擎并重构为多Crate工作空间架构
- 移除 核心层与 tract/Tensor 的强耦合,前/后处理全线转用标准 ndarray - 针对 OCR 与目标检测(Det)分别设计独立的强类型输出小枚举(OcrOutput/DetOutput) - 利用 Trait 关联类型(Associated Type)InferenceEngine,OcrEngine,DetEngine 统一接口,实现多后端解耦 - 引入 thiserror 库,建立完备的强类型错误处理机制(DdddError/Result) - 完成项目结构初拆,剥离为 ddddocr-core 和 ddddocr-tract
This commit is contained in:
125
ddddocr-tract/src/ocr/session.rs
Normal file
125
ddddocr-tract/src/ocr/session.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use crate::loader::ModelLoader;
|
||||
use anyhow::Context;
|
||||
use ddddocr_core::error::{DdddError, Result};
|
||||
use ddddocr_core::{InferenceEngine, ModelMetadata, OcrEngine, OcrOutput};
|
||||
use ndarray::s;
|
||||
use std::path::Path;
|
||||
use tract_onnx::prelude::DatumType;
|
||||
use tract_onnx::prelude::{Graph, IntoTensor, RunnableModel, Tensor, TypedFact, TypedOp, tvec};
|
||||
|
||||
pub struct OcrSession {
|
||||
pub session: RunnableModel<TypedFact, Box<dyn TypedOp>, Graph<TypedFact, Box<dyn TypedOp>>>,
|
||||
pub model_metadata: ModelMetadata,
|
||||
}
|
||||
impl OcrSession {
|
||||
pub fn new<P>(model_path: P, model_metadata: ModelMetadata) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let session = ModelLoader::model_for_path(model_path)?.session;
|
||||
Ok(Self {
|
||||
session,
|
||||
model_metadata,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn model_from_bytes(model_bytes: &[u8], model_metadata: ModelMetadata) -> Result<Self> {
|
||||
let session = ModelLoader::model_from_bytes(model_bytes)?.session;
|
||||
Ok(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> {
|
||||
// tract 的 run 会返回一个 Vec<TValue>,我们通常只需要第一个输出
|
||||
// let result = self.ocr.run(tvec!(tensor.into()))?;
|
||||
let tensor = Tensor::from(input_array);
|
||||
|
||||
let mut result = self
|
||||
.session
|
||||
.run(tvec!(tensor.into()))
|
||||
.context("执行模型推理失败")?;
|
||||
println!("模型输出原始数据: {:?}", result);
|
||||
// Ok(result.swap_remove(0).into_tensor())
|
||||
let raw_tensor = result.swap_remove(0).into_tensor();
|
||||
// 在引擎内部消化掉 DatumType 强耦合
|
||||
match raw_tensor.datum_type() {
|
||||
DatumType::I64 => {
|
||||
let array_d = raw_tensor
|
||||
.into_array::<i64>()
|
||||
.context("Tract 无法获取 i64 内存视图")?;
|
||||
// 🌟 提前提取真实维度
|
||||
let actual_shape = array_d.shape().to_vec();
|
||||
// 转成标准的 Array1 传给 core
|
||||
let array1 = array_d
|
||||
.to_owned()
|
||||
.into_dimensionality::<ndarray::Ix1>()
|
||||
.map_err(|_| DdddError::DimensionMismatch {
|
||||
expected: "1D 字符索引静态矩阵".to_string(),
|
||||
actual: actual_shape,
|
||||
})?;
|
||||
Ok(OcrOutput::Indices(array1))
|
||||
}
|
||||
DatumType::F32 => {
|
||||
let shape = raw_tensor.shape();
|
||||
println!("模型输出shape数据: {:?}", shape);
|
||||
let view = raw_tensor
|
||||
.to_array_view::<f32>()
|
||||
.context("Tract 无法获取 f32 内存视图")?;
|
||||
|
||||
// 1. 极其纯粹的、无拷贝的多维 Shape 压扁清洗
|
||||
let (steps, classes, data_dyn_view) = match shape.len() {
|
||||
3 => {
|
||||
if shape[1] == 1 {
|
||||
// 形状: [Steps, 1, Classes] -> 你的原有逻辑
|
||||
(shape[0], shape[2], view.into_dyn())
|
||||
} else if shape[0] == 1 {
|
||||
// 形状: [1, Steps, Classes] -> 另一种常见导出格式
|
||||
(shape[1], shape[2], view.into_dyn())
|
||||
} else {
|
||||
// 默认取第一个 batch: [Batch, Steps, Classes]
|
||||
// 使用 slice 对应 Python 的 output[0, :, :]
|
||||
let sliced = view.slice(s![0, .., ..]);
|
||||
(shape[1], shape[2], sliced.into_dyn())
|
||||
}
|
||||
}
|
||||
// 形状: [Steps, Classes] -> 已经剥离了 Batch 维度
|
||||
2 => (shape[0], shape[1], view.into_dyn()),
|
||||
// 形状: [Classes] -> 单字符输出(对应 Python 的 ndim == 0 保护逻辑)
|
||||
// 我们把它虚构成一个 [1, Classes] 的 2D 矩阵来复用后面的 argmax 逻辑
|
||||
1 => (1, shape[0], view.into_dyn()),
|
||||
_ => {
|
||||
return Err(DdddError::DimensionMismatch {
|
||||
expected: "1D, 2D, or 3D OCR Logits".to_string(),
|
||||
actual: shape.to_vec(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 转换为标准的 2D 静态矩阵 [Steps, Classes]
|
||||
let matrix_cow = data_dyn_view
|
||||
.to_shape(ndarray::Ix2(steps, classes))
|
||||
.map_err(|_| DdddError::DimensionMismatch {
|
||||
expected: format!("无法将形状调整为 [{}, {}]", steps, classes),
|
||||
actual: shape.to_vec(),
|
||||
})?
|
||||
.to_owned(); // 转换为 Owned,断开与 tract 内存生命周期的绑定,方便传递给 core
|
||||
|
||||
Ok(OcrOutput::Logits(matrix_cow))
|
||||
}
|
||||
_ => Err(
|
||||
// anyhow::anyhow!("不支持的模型输出数据类型: {:?}",raw_tensor.datum_type())
|
||||
DdddError::UnknownOutputFormat,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user